Browse Source

Merge branch 'main' into CLDC-3723-Add-validation-on-location-number-of-units

pull/2798/head
Manny Dinssa 1 year ago committed by GitHub
parent
commit
24261437b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      Gemfile
  2. 5
      app/components/create_log_actions_component.html.erb
  3. 8
      app/components/create_log_actions_component.rb
  4. 27
      app/controllers/csv_downloads_controller.rb
  5. 14
      app/controllers/lettings_logs_controller.rb
  6. 1
      app/controllers/merge_requests_controller.rb
  7. 14
      app/controllers/sales_logs_controller.rb
  8. 2
      app/controllers/schemes_controller.rb
  9. 4
      app/helpers/merge_requests_helper.rb
  10. 13
      app/jobs/email_csv_job.rb
  11. 13
      app/jobs/scheme_email_csv_job.rb
  12. 10
      app/models/csv_download.rb
  13. 5
      app/models/derived_variables/lettings_log_variables.rb
  14. 5
      app/models/derived_variables/sales_log_variables.rb
  15. 5
      app/models/form/lettings/pages/no_address_found.rb
  16. 1
      app/models/form/lettings/subsections/household_needs.rb
  17. 2
      app/models/form/page.rb
  18. 2
      app/models/form/question.rb
  19. 2
      app/models/form/sales/pages/buyer_interview.rb
  20. 1
      app/models/form/sales/pages/deposit.rb
  21. 1
      app/models/form/sales/pages/deposit_discount.rb
  22. 1
      app/models/form/sales/pages/discount.rb
  23. 1
      app/models/form/sales/pages/equity.rb
  24. 13
      app/models/form/sales/pages/estate_management_fee.rb
  25. 1
      app/models/form/sales/pages/extra_borrowing.rb
  26. 1
      app/models/form/sales/pages/grant.rb
  27. 12
      app/models/form/sales/pages/handover_date_check.rb
  28. 12
      app/models/form/sales/pages/living_before_purchase.rb
  29. 1
      app/models/form/sales/pages/monthly_rent.rb
  30. 1
      app/models/form/sales/pages/mortgage_amount.rb
  31. 1
      app/models/form/sales/pages/mortgage_lender.rb
  32. 1
      app/models/form/sales/pages/mortgage_lender_other.rb
  33. 1
      app/models/form/sales/pages/mortgage_length.rb
  34. 1
      app/models/form/sales/pages/mortgageused.rb
  35. 1
      app/models/form/sales/pages/previous_bedrooms.rb
  36. 1
      app/models/form/sales/pages/previous_property_type.rb
  37. 1
      app/models/form/sales/pages/previous_tenure.rb
  38. 2
      app/models/form/sales/pages/privacy_notice.rb
  39. 1
      app/models/form/sales/pages/resale.rb
  40. 2
      app/models/form/sales/pages/staircase.rb
  41. 1
      app/models/form/sales/pages/value_shared_ownership.rb
  42. 2
      app/models/form/sales/questions/buyer_interview.rb
  43. 1
      app/models/form/sales/questions/deposit_amount.rb
  44. 1
      app/models/form/sales/questions/deposit_discount.rb
  45. 1
      app/models/form/sales/questions/discount.rb
  46. 1
      app/models/form/sales/questions/equity.rb
  47. 1
      app/models/form/sales/questions/extra_borrowing.rb
  48. 1
      app/models/form/sales/questions/fromprop.rb
  49. 1
      app/models/form/sales/questions/grant.rb
  50. 24
      app/models/form/sales/questions/has_management_fee.rb
  51. 12
      app/models/form/sales/questions/management_fee.rb
  52. 1
      app/models/form/sales/questions/monthly_rent.rb
  53. 1
      app/models/form/sales/questions/mortgage_amount.rb
  54. 1
      app/models/form/sales/questions/mortgage_lender.rb
  55. 1
      app/models/form/sales/questions/mortgage_lender_other.rb
  56. 1
      app/models/form/sales/questions/mortgage_length.rb
  57. 1
      app/models/form/sales/questions/mortgageused.rb
  58. 1
      app/models/form/sales/questions/previous_bedrooms.rb
  59. 1
      app/models/form/sales/questions/previous_tenure.rb
  60. 2
      app/models/form/sales/questions/privacy_notice.rb
  61. 1
      app/models/form/sales/questions/resale.rb
  62. 1
      app/models/form/sales/questions/value.rb
  63. 10
      app/models/form/sales/sections/sale_information.rb
  64. 1
      app/models/form/sales/subsections/discounted_ownership_scheme.rb
  65. 15
      app/models/form/sales/subsections/household_situation.rb
  66. 15
      app/models/form/sales/subsections/income_benefits_and_savings.rb
  67. 15
      app/models/form/sales/subsections/other_household_information.rb
  68. 1
      app/models/form/sales/subsections/outright_sale.rb
  69. 48
      app/models/form/sales/subsections/shared_ownership_initial_purchase.rb
  70. 2
      app/models/form/sales/subsections/shared_ownership_scheme.rb
  71. 4
      app/models/form/section.rb
  72. 1
      app/models/log.rb
  73. 7
      app/models/merge_request_organisation.rb
  74. 27
      app/models/validations/property_validations.rb
  75. 16
      app/models/validations/sales/property_validations.rb
  76. 5
      app/models/validations/sales/sale_information_validations.rb
  77. 6
      app/models/validations/sales/soft_validations.rb
  78. 16
      app/policies/csv_download_policy.rb
  79. 2
      app/services/bulk_upload/lettings/year2024/csv_parser.rb
  80. 2
      app/services/bulk_upload/sales/year2024/csv_parser.rb
  81. 50
      app/services/csv/downloader.rb
  82. 4
      app/services/feature_toggle.rb
  83. 2
      app/services/mandatory_collection_resources_service.rb
  84. 10
      app/views/csv_downloads/show.html.erb
  85. 8
      app/views/errors/download_link_expired.html.erb
  86. 6
      app/views/form/guidance/_financial_calculations_shared_ownership.html.erb
  87. 5
      app/views/logs/_tasklist.html.erb
  88. 8
      app/views/merge_requests/_notification_banners.html.erb
  89. 2
      app/views/merge_requests/show.html.erb
  90. 4
      config/locales/en.yml
  91. 2
      config/locales/forms/2024/lettings/setup.en.yml
  92. 8
      config/locales/forms/2024/lettings/soft_validations.en.yml
  93. 21
      config/locales/forms/2025/sales/sale_information.en.yml
  94. 17
      config/locales/validations/lettings/property_information.en.yml
  95. 11
      config/locales/validations/sales/property_information.en.yml
  96. 2
      config/locales/validations/sales/sale_information.en.yml
  97. 12
      config/routes.rb
  98. 8
      db/migrate/20241114154215_add_management_fee_fields.rb
  99. 12
      db/migrate/20241118104046_add_csv_download_table.rb
  100. 16
      db/schema.rb
  101. Some files were not shown because too many files have changed in this diff Show More

4
Gemfile

@ -62,6 +62,8 @@ gem "possessive"
# Strip whitespace from active record attributes
gem "auto_strip_attributes"
# Use sidekiq for background processing
gem "factory_bot_rails"
gem "faker"
gem "method_source", "~> 1.1"
gem "rails_admin", "~> 3.1"
gem "ruby-openai"
@ -75,8 +77,6 @@ group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem "byebug", platforms: %i[mri mingw x64_mingw]
gem "dotenv-rails"
gem "factory_bot_rails"
gem "faker"
gem "pry-byebug"
gem "parallel_tests"

5
app/components/create_log_actions_component.html.erb

@ -7,5 +7,10 @@
<% if user.support? %>
<%= govuk_button_link_to view_uploads_button_copy, view_uploads_button_href, secondary: true %>
<% end %>
<% if FeatureToggle.create_test_logs_enabled? %>
<%= govuk_button_link_to "Create test log", create_test_log_href, secondary: true %>
<%= govuk_button_link_to "Create test log (setup only)", create_setup_test_log_href, secondary: true %>
<% end %>
<% end %>
</div>

8
app/components/create_log_actions_component.rb

@ -34,6 +34,14 @@ class CreateLogActionsComponent < ViewComponent::Base
send("bulk_upload_#{log_type}_log_path", id: "start")
end
def create_test_log_href
send("create_test_#{log_type}_log_path")
end
def create_setup_test_log_href
send("create_setup_test_#{log_type}_log_path")
end
def view_uploads_button_copy
"View #{log_type} bulk uploads"
end

27
app/controllers/csv_downloads_controller.rb

@ -0,0 +1,27 @@
class CsvDownloadsController < ApplicationController
before_action :authenticate_user!
def show
@csv_download = CsvDownload.find(params[:id])
authorize @csv_download
return render "errors/download_link_expired" if @csv_download.expired?
end
def download
csv_download = CsvDownload.find(params[:id])
authorize csv_download
return render "errors/download_link_expired" if csv_download.expired?
downloader = Csv::Downloader.new(csv_download:)
if Rails.env.development?
downloader.call
send_file downloader.path, filename: csv_download.filename, type: "text/csv"
else
presigned_url = downloader.presigned_url
redirect_to presigned_url, allow_other_host: true
end
end
end

14
app/controllers/lettings_logs_controller.rb

@ -149,6 +149,20 @@ class LettingsLogsController < LogsController
end
end
def create_test_log
return render_not_found unless FeatureToggle.create_test_logs_enabled?
log = FactoryBot.create(:lettings_log, :completed, assigned_to: current_user, ppostcode_full: "SW1A 1AA")
redirect_to lettings_log_path(log)
end
def create_setup_test_log
return render_not_found unless FeatureToggle.create_test_logs_enabled?
log = FactoryBot.create(:lettings_log, :setup_completed, assigned_to: current_user)
redirect_to lettings_log_path(log)
end
private
def session_filters

1
app/controllers/merge_requests_controller.rb

@ -143,6 +143,7 @@ private
if [day, month, year].none?(&:blank?) && Date.valid_date?(year.to_i, month.to_i, day.to_i)
merge_request_params["merge_date"] = Time.zone.local(year.to_i, month.to_i, day.to_i)
@merge_request.errors.add(:merge_date, :more_than_year_from_today) if Time.zone.local(year.to_i, month.to_i, day.to_i) - 1.year > Time.zone.today
else
@merge_request.errors.add(:merge_date, :invalid)
end

14
app/controllers/sales_logs_controller.rb

@ -119,6 +119,20 @@ class SalesLogsController < LogsController
end
end
def create_test_log
return render_not_found unless FeatureToggle.create_test_logs_enabled?
log = FactoryBot.create(:sales_log, :completed, assigned_to: current_user)
redirect_to sales_log_path(log)
end
def create_setup_test_log
return render_not_found unless FeatureToggle.create_test_logs_enabled?
log = FactoryBot.create(:sales_log, :shared_ownership_setup_complete, assigned_to: current_user)
redirect_to sales_log_path(log)
end
private
def session_filters

2
app/controllers/schemes_controller.rb

@ -152,7 +152,7 @@ class SchemesController < ApplicationController
flash[:notice] = if scheme_previously_confirmed
"#{@scheme.service_name} has been updated."
else
"#{@scheme.service_name} has been created. It does not require helpdesk approval."
"#{@scheme.service_name} has been created."
end
redirect_to scheme_path(@scheme)
end

4
app/helpers/merge_requests_helper.rb

@ -276,4 +276,8 @@ module MergeRequestsHelper
def any_organisations_share_logs?(organisations, type)
organisations.any? { |organisation| organisation.send("#{type}_logs").filter_by_managing_organisation(organisations.where.not(id: organisation.id)).exists? }
end
def begin_merge_disabled?(merge_request)
merge_request.status != "ready_to_merge" || merge_request.merge_date.future?
end
end

13
app/jobs/email_csv_job.rb

@ -1,9 +1,10 @@
class EmailCsvJob < ApplicationJob
include Rails.application.routes.url_helpers
queue_as :default
BYTE_ORDER_MARK = "\uFEFF".freeze # Required to ensure Excel always reads CSV as UTF-8
EXPIRATION_TIME = 24.hours.to_i
EXPIRATION_TIME = 48.hours.to_i
def perform(user, search_term = nil, filters = {}, all_orgs = false, organisation = nil, codes_only_export = false, log_type = "lettings", year = nil) # rubocop:disable Style/OptionalBooleanParameter - sidekiq can't serialise named params
export_type = codes_only_export ? "codes" : "labels"
@ -20,10 +21,16 @@ class EmailCsvJob < ApplicationJob
filename = "#{[log_type, 'logs', organisation&.name, Time.zone.now].compact.join('-')}.csv"
storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"])
storage_service = if FeatureToggle.upload_enabled?
Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"])
else
Storage::LocalDiskService.new
end
storage_service.write_file(filename, BYTE_ORDER_MARK + csv_string)
csv_download = CsvDownload.create!(user:, organisation: user.organisation, filename:, download_type: log_type, expiration_time: EXPIRATION_TIME)
url = storage_service.get_presigned_url(filename, EXPIRATION_TIME)
url = csv_download_url(csv_download.id, host: ENV["APP_HOST"])
CsvDownloadMailer.new.send_csv_download_mail(user, url, EXPIRATION_TIME)
end

13
app/jobs/scheme_email_csv_job.rb

@ -1,9 +1,10 @@
class SchemeEmailCsvJob < ApplicationJob
include Rails.application.routes.url_helpers
queue_as :default
BYTE_ORDER_MARK = "\uFEFF".freeze # Required to ensure Excel always reads CSV as UTF-8
EXPIRATION_TIME = 24.hours.to_i
EXPIRATION_TIME = 48.hours.to_i
def perform(user, search_term = nil, filters = {}, all_orgs = false, organisation = nil, download_type = "combined") # rubocop:disable Style/OptionalBooleanParameter - sidekiq can't serialise named params
unfiltered_schemes = if organisation.present? && user.support?
@ -23,10 +24,16 @@ class SchemeEmailCsvJob < ApplicationJob
filename = "#{['schemes-and-locations', organisation&.name, Time.zone.now].compact.join('-')}.csv"
end
storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"])
storage_service = if FeatureToggle.upload_enabled?
Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"])
else
Storage::LocalDiskService.new
end
storage_service.write_file(filename, BYTE_ORDER_MARK + csv_string)
csv_download = CsvDownload.create!(user:, organisation: user.organisation, filename:, download_type:, expiration_time: EXPIRATION_TIME)
url = storage_service.get_presigned_url(filename, EXPIRATION_TIME)
url = csv_download_url(csv_download.id, host: ENV["APP_HOST"])
CsvDownloadMailer.new.send_csv_download_mail(user, url, EXPIRATION_TIME)
end

10
app/models/csv_download.rb

@ -0,0 +1,10 @@
class CsvDownload < ApplicationRecord
enum download_type: { lettings: "lettings", sales: "sales", schemes: "schemes", locations: "locations", combined: "combined" }
belongs_to :user
belongs_to :organisation
def expired?
created_at < expiration_time.seconds.ago
end
end

5
app/models/derived_variables/lettings_log_variables.rb

@ -124,6 +124,11 @@ module DerivedVariables::LettingsLogVariables
self.nationality_all = nationality_all_group if nationality_uk_or_prefers_not_to_say?
if startdate_changed? && !LocalAuthority.active(startdate).where(code: la).exists?
self.la = nil
self.is_la_inferred = false
end
reset_address_fields! if is_supported_housing?
end

5
app/models/derived_variables/sales_log_variables.rb

@ -75,6 +75,11 @@ module DerivedVariables::SalesLogVariables
self.nationality_all = nationality_all_group if nationality_uk_or_prefers_not_to_say?
self.nationality_all_buyer2 = nationality_all_buyer2_group if nationality2_uk_or_prefers_not_to_say?
if saledate_changed? && !LocalAuthority.active(saledate).where(code: la).exists?
self.la = nil
self.is_la_inferred = false
end
set_encoded_derived_values!(DEPENDENCIES)
end

5
app/models/form/lettings/pages/no_address_found.rb

@ -3,12 +3,13 @@ class Form::Lettings::Pages::NoAddressFound < ::Form::Page
super
@id = "no_address_found"
@type = "interruption_screen"
@copy_key = "lettings.soft_validations.no_address_found"
@title_text = {
"translation" => "soft_validations.no_address_found.title_text",
"translation" => "forms.#{form.start_date.year}.#{@copy_key}.title_text",
"arguments" => [],
}
@informative_text = {
"translation" => "soft_validations.no_address_found.informative_text",
"translation" => "forms.#{form.start_date.year}.#{@copy_key}.informative_text",
"arguments" => [],
}
@depends_on = [

1
app/models/form/lettings/subsections/household_needs.rb

@ -2,7 +2,6 @@ class Form::Lettings::Subsections::HouseholdNeeds < ::Form::Subsection
def initialize(id, hsh, section)
super
@id = "household_needs"
@copy_key = "lettings.household_needs.housingneeds_type"
@label = "Household needs"
@depends_on = [{ "non_location_setup_questions_completed?" => true }]
end

2
app/models/form/page.rb

@ -25,7 +25,7 @@ class Form::Page
delegate :form, to: :subsection
def copy_key
@copy_key ||= "#{form.type}.#{subsection.id}.#{questions[0].id}"
@copy_key ||= "#{form.type}.#{subsection.copy_key}.#{questions[0].id}"
end
def header

2
app/models/form/question.rb

@ -51,7 +51,7 @@ class Form::Question
delegate :form, to: :subsection
def copy_key
@copy_key ||= "#{form.type}.#{subsection.id}.#{id}"
@copy_key ||= "#{form.type}.#{subsection.copy_key}.#{id}"
end
def check_answer_label

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

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

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

@ -3,7 +3,6 @@ class Form::Sales::Pages::Deposit < ::Form::Page
super(id, hsh, subsection)
@ownershipsch = ownershipsch
@optional = optional
@copy_key = "sales.sale_information.deposit"
end
def questions

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

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

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

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

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

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

13
app/models/form/sales/pages/estate_management_fee.rb

@ -0,0 +1,13 @@
class Form::Sales::Pages::EstateManagementFee < ::Form::Page
def initialize(id, hsh, subsection)
super
@copy_key = "sales.sale_information.management_fee"
end
def questions
@questions ||= [
Form::Sales::Questions::HasManagementFee.new(nil, nil, self),
Form::Sales::Questions::ManagementFee.new(nil, nil, self),
]
end
end

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

@ -2,7 +2,6 @@ class Form::Sales::Pages::ExtraBorrowing < ::Form::Page
def initialize(id, hsh, subsection, ownershipsch:)
super(id, hsh, subsection)
@ownershipsch = ownershipsch
@copy_key = "sales.sale_information.extrabor"
@description = ""
@subsection = subsection
@depends_on = [{

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

@ -2,7 +2,6 @@ class Form::Sales::Pages::Grant < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "grant"
@copy_key = "sales.sale_information.grant"
@depends_on = [{
"right_to_buy?" => false,
"rent_to_buy_full_ownership?" => false,

12
app/models/form/sales/pages/handover_date_check.rb

@ -3,8 +3,6 @@ class Form::Sales::Pages::HandoverDateCheck < ::Form::Page
super
@id = "handover_date_check"
@copy_key = "sales.soft_validations.hodate_check"
@depends_on = [{ "saledate_check" => nil, "hodate_3_years_or_more_saledate?" => true },
{ "saledate_check" => 1, "hodate_3_years_or_more_saledate?" => true }]
@informative_text = {}
@title_text = {
"translation" => "forms.#{form.start_date.year}.#{@copy_key}.title_text",
@ -12,6 +10,16 @@ class Form::Sales::Pages::HandoverDateCheck < ::Form::Page
}
end
def depends_on
if form.start_year_2025_or_later?
[{ "saledate_check" => nil, "hodate_5_years_or_more_saledate?" => true },
{ "saledate_check" => 1, "hodate_5_years_or_more_saledate?" => true }]
else
[{ "saledate_check" => nil, "hodate_3_years_or_more_saledate?" => true },
{ "saledate_check" => 1, "hodate_3_years_or_more_saledate?" => true }]
end
end
def questions
@questions ||= [
Form::Sales::Questions::HandoverDateCheck.new(nil, nil, self),

12
app/models/form/sales/pages/living_before_purchase.rb

@ -19,11 +19,17 @@ class Form::Sales::Pages::LivingBeforePurchase < ::Form::Page
end
end
def depends_on
def routed_to?(log, _user)
super && page_routed_to?(log)
end
def page_routed_to?(log)
return false if form.start_year_2025_or_later? && log.resale != 2
if @joint_purchase
[{ "joint_purchase?" => true }]
log.joint_purchase?
else
[{ "not_joint_purchase?" => true }, { "jointpur" => nil }]
log.not_joint_purchase? || log.jointpur.nil?
end
end
end

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

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

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

@ -2,7 +2,6 @@ class Form::Sales::Pages::MortgageAmount < ::Form::Page
def initialize(id, hsh, subsection, ownershipsch:)
super(id, hsh, subsection)
@ownershipsch = ownershipsch
@copy_key = "sales.sale_information.mortgage"
@depends_on = [{ "mortgage_used?" => true }]
end

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

@ -2,7 +2,6 @@ class Form::Sales::Pages::MortgageLender < ::Form::Page
def initialize(id, hsh, subsection, ownershipsch:)
super(id, hsh, subsection)
@ownershipsch = ownershipsch
@copy_key = "sales.sale_information.mortgagelender"
@description = ""
@subsection = subsection
@depends_on = [{

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

@ -2,7 +2,6 @@ class Form::Sales::Pages::MortgageLenderOther < ::Form::Page
def initialize(id, hsh, subsection, ownershipsch:)
super(id, hsh, subsection)
@ownershipsch = ownershipsch
@copy_key = "sales.sale_information.mortgagelenderother"
@description = ""
@subsection = subsection
@depends_on = [{

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

@ -2,7 +2,6 @@ class Form::Sales::Pages::MortgageLength < ::Form::Page
def initialize(id, hsh, subsection, ownershipsch:)
super(id, hsh, subsection)
@ownershipsch = ownershipsch
@copy_key = "sales.sale_information.mortlen"
@depends_on = [{
"mortgageused" => 1,
}]

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

@ -1,7 +1,6 @@
class Form::Sales::Pages::Mortgageused < ::Form::Page
def initialize(id, hsh, subsection, ownershipsch:)
super(id, hsh, subsection)
@copy_key = "sales.sale_information.mortgageused"
@ownershipsch = ownershipsch
end

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

@ -2,7 +2,6 @@ class Form::Sales::Pages::PreviousBedrooms < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "previous_bedrooms"
@copy_key = "sales.sale_information.frombeds"
@depends_on = [
{
"soctenant" => 1,

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

@ -2,7 +2,6 @@ class Form::Sales::Pages::PreviousPropertyType < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "previous_property_type"
@copy_key = "sales.sale_information.fromprop"
@description = ""
@subsection = subsection
@depends_on = [

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

@ -2,7 +2,6 @@ class Form::Sales::Pages::PreviousTenure < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "shared_ownership_previous_tenure"
@copy_key = "sales.sale_information.socprevten"
@header = ""
@description = ""
@subsection = subsection

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

@ -1,7 +1,7 @@
class Form::Sales::Pages::PrivacyNotice < ::Form::Page
def initialize(id, hsh, subsection, joint_purchase:)
super(id, hsh, subsection)
@copy_key = "sales.#{subsection.id}.privacynotice.#{joint_purchase ? 'joint_purchase' : 'not_joint_purchase'}"
@copy_key = "sales.#{subsection.copy_key}.privacynotice.#{joint_purchase ? 'joint_purchase' : 'not_joint_purchase'}"
@joint_purchase = joint_purchase
end

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

@ -2,7 +2,6 @@ class Form::Sales::Pages::Resale < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "resale"
@copy_key = "sales.sale_information.resale"
@depends_on = [
{
"staircase" => 2,

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

@ -3,7 +3,7 @@ class Form::Sales::Pages::Staircase < ::Form::Page
super
@id = "staircasing"
@depends_on = [{ "ownershipsch" => 1 }]
@copy_key = "sales.#{subsection.id}.staircasing"
@copy_key = "sales.#{subsection.copy_key}.staircasing"
end
def questions

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

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

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

@ -2,7 +2,7 @@ class Form::Sales::Questions::BuyerInterview < ::Form::Question
def initialize(id, hsh, page, joint_purchase:)
super(id, hsh, page)
@id = "noint"
@copy_key = "sales.#{subsection.id}.noint.#{joint_purchase ? 'joint_purchase' : 'not_joint_purchase'}"
@copy_key = "sales.#{subsection.copy_key}.noint.#{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]

1
app/models/form/sales/questions/deposit_amount.rb

@ -2,7 +2,6 @@ class Form::Sales::Questions::DepositAmount < ::Form::Question
def initialize(id, hsh, subsection, ownershipsch:, optional:)
super(id, hsh, subsection)
@id = "deposit"
@copy_key = "sales.sale_information.deposit"
@type = "numeric"
@min = 0
@max = 999_999

1
app/models/form/sales/questions/deposit_discount.rb

@ -2,7 +2,6 @@ class Form::Sales::Questions::DepositDiscount < ::Form::Question
def initialize(id, hsh, page)
super
@id = "cashdis"
@copy_key = "sales.sale_information.cashdis"
@type = "numeric"
@min = 0
@max = 999_999

1
app/models/form/sales/questions/discount.rb

@ -3,7 +3,6 @@ class Form::Sales::Questions::Discount < ::Form::Question
super
@id = "discount"
@type = "numeric"
@copy_key = "sales.sale_information.discount"
@min = 0
@max = form.start_year_2024_or_later? ? 70 : 100
@step = 0.1

1
app/models/form/sales/questions/equity.rb

@ -2,7 +2,6 @@ class Form::Sales::Questions::Equity < ::Form::Question
def initialize(id, hsh, page)
super
@id = "equity"
@copy_key = "sales.sale_information.equity"
@type = "numeric"
@min = 0
@max = 100

1
app/models/form/sales/questions/extra_borrowing.rb

@ -2,7 +2,6 @@ class Form::Sales::Questions::ExtraBorrowing < ::Form::Question
def initialize(id, hsh, subsection, ownershipsch:)
super(id, hsh, subsection)
@id = "extrabor"
@copy_key = "sales.sale_information.extrabor"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@page = page

1
app/models/form/sales/questions/fromprop.rb

@ -2,7 +2,6 @@ class Form::Sales::Questions::Fromprop < ::Form::Question
def initialize(id, hsh, page)
super
@id = "fromprop"
@copy_key = "sales.sale_information.fromprop"
@type = "radio"
@page = page
@answer_options = ANSWER_OPTIONS

1
app/models/form/sales/questions/grant.rb

@ -2,7 +2,6 @@ class Form::Sales::Questions::Grant < ::Form::Question
def initialize(id, hsh, page)
super
@id = "grant"
@copy_key = "sales.sale_information.grant"
@type = "numeric"
@min = 0
@max = 999_999

24
app/models/form/sales/questions/has_management_fee.rb

@ -0,0 +1,24 @@
class Form::Sales::Questions::HasManagementFee < ::Form::Question
def initialize(id, hsh, subsection)
super
@id = "has_management_fee"
@copy_key = "sales.sale_information.management_fee.has_management_fee"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@conditional_for = {
"management_fee" => [1],
}
@hidden_in_check_answers = {
"depends_on" => [
{
"has_management_fee" => 1,
},
],
}
end
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"0" => { "value" => "No" },
}.freeze
end

12
app/models/form/sales/questions/management_fee.rb

@ -0,0 +1,12 @@
class Form::Sales::Questions::ManagementFee < ::Form::Question
def initialize(id, hsh, subsection)
super
@id = "management_fee"
@copy_key = "sales.sale_information.management_fee.management_fee"
@type = "numeric"
@min = 1
@step = 0.01
@width = 5
@prefix = "£"
end
end

1
app/models/form/sales/questions/monthly_rent.rb

@ -2,7 +2,6 @@ class Form::Sales::Questions::MonthlyRent < ::Form::Question
def initialize(id, hsh, page)
super
@id = "mrent"
@copy_key = "sales.sale_information.mrent"
@type = "numeric"
@min = 0
@step = 0.01

1
app/models/form/sales/questions/mortgage_amount.rb

@ -2,7 +2,6 @@ class Form::Sales::Questions::MortgageAmount < ::Form::Question
def initialize(id, hsh, subsection, ownershipsch:)
super(id, hsh, subsection)
@id = "mortgage"
@copy_key = "sales.sale_information.mortgage"
@type = "numeric"
@min = 1
@step = 1

1
app/models/form/sales/questions/mortgage_lender.rb

@ -2,7 +2,6 @@ class Form::Sales::Questions::MortgageLender < ::Form::Question
def initialize(id, hsh, subsection, ownershipsch:)
super(id, hsh, subsection)
@id = "mortgagelender"
@copy_key = "sales.sale_information.mortgagelender"
@type = "select"
@page = page
@bottom_guidance_partial = "mortgage_lender"

1
app/models/form/sales/questions/mortgage_lender_other.rb

@ -2,7 +2,6 @@ class Form::Sales::Questions::MortgageLenderOther < ::Form::Question
def initialize(id, hsh, subsection, ownershipsch:)
super(id, hsh, subsection)
@id = "mortgagelenderother"
@copy_key = "sales.sale_information.mortgagelenderother"
@type = "text"
@page = page
@ownershipsch = ownershipsch

1
app/models/form/sales/questions/mortgage_length.rb

@ -2,7 +2,6 @@ class Form::Sales::Questions::MortgageLength < ::Form::Question
def initialize(id, hsh, subsection, ownershipsch:)
super(id, hsh, subsection)
@id = "mortlen"
@copy_key = "sales.sale_information.mortlen"
@type = "numeric"
@min = 0
@max = 60

1
app/models/form/sales/questions/mortgageused.rb

@ -2,7 +2,6 @@ class Form::Sales::Questions::Mortgageused < ::Form::Question
def initialize(id, hsh, subsection, ownershipsch:)
super(id, hsh, subsection)
@id = "mortgageused"
@copy_key = "sales.sale_information.mortgageused"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@ownershipsch = ownershipsch

1
app/models/form/sales/questions/previous_bedrooms.rb

@ -2,7 +2,6 @@ class Form::Sales::Questions::PreviousBedrooms < ::Form::Question
def initialize(id, hsh, page)
super
@id = "frombeds"
@copy_key = "sales.sale_information.frombeds"
@type = "numeric"
@width = 5
@min = 1

1
app/models/form/sales/questions/previous_tenure.rb

@ -2,7 +2,6 @@ class Form::Sales::Questions::PreviousTenure < ::Form::Question
def initialize(id, hsh, page)
super
@id = "socprevten"
@copy_key = "sales.sale_information.socprevten"
@type = "radio"
@page = page
@answer_options = ANSWER_OPTIONS

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

@ -2,7 +2,7 @@ class Form::Sales::Questions::PrivacyNotice < ::Form::Question
def initialize(id, hsh, page, joint_purchase:)
super(id, hsh, page)
@id = "privacynotice"
@copy_key = "sales.#{subsection.id}.privacynotice.#{joint_purchase ? 'joint_purchase' : 'not_joint_purchase'}"
@copy_key = "sales.#{subsection.copy_key}.privacynotice.#{joint_purchase ? 'joint_purchase' : 'not_joint_purchase'}"
@type = "checkbox"
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
@joint_purchase = joint_purchase

1
app/models/form/sales/questions/resale.rb

@ -2,7 +2,6 @@ class Form::Sales::Questions::Resale < ::Form::Question
def initialize(id, hsh, page)
super
@id = "resale"
@copy_key = "sales.sale_information.resale"
@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]

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

@ -2,7 +2,6 @@ class Form::Sales::Questions::Value < ::Form::Question
def initialize(id, hsh, page)
super
@id = "value"
@copy_key = "sales.sale_information.value"
@type = "numeric"
@min = 0
@step = 1

10
app/models/form/sales/sections/sale_information.rb

@ -5,9 +5,17 @@ class Form::Sales::Sections::SaleInformation < ::Form::Section
@label = "Sale information"
@description = ""
@subsections = [
Form::Sales::Subsections::SharedOwnershipScheme.new(nil, nil, self),
shared_ownership_scheme_subsection,
Form::Sales::Subsections::DiscountedOwnershipScheme.new(nil, nil, self),
Form::Sales::Subsections::OutrightSale.new(nil, nil, self),
] || []
end
def shared_ownership_scheme_subsection
if form.start_year_2025_or_later?
Form::Sales::Subsections::SharedOwnershipInitialPurchase.new(nil, nil, self)
else
Form::Sales::Subsections::SharedOwnershipScheme.new(nil, nil, self)
end
end
end

1
app/models/form/sales/subsections/discounted_ownership_scheme.rb

@ -4,6 +4,7 @@ class Form::Sales::Subsections::DiscountedOwnershipScheme < ::Form::Subsection
@id = "discounted_ownership_scheme"
@label = "Discounted ownership scheme"
@depends_on = [{ "ownershipsch" => 2, "setup_completed?" => true }]
@copy_key = "sale_information"
end
def pages

15
app/models/form/sales/subsections/household_situation.rb

@ -3,7 +3,14 @@ class Form::Sales::Subsections::HouseholdSituation < ::Form::Subsection
super
@id = "household_situation"
@label = "Household situation"
@depends_on = [{ "setup_completed?" => true }]
end
def depends_on
if form.start_year_2025_or_later?
[{ "setup_completed?" => true, "is_staircase?" => false }]
else
[{ "setup_completed?" => true }]
end
end
def pages
@ -16,4 +23,10 @@ class Form::Sales::Subsections::HouseholdSituation < ::Form::Subsection
Form::Sales::Pages::Buyer2PreviousHousingSituation.new(nil, nil, self),
].flatten.compact
end
def displayed_in_tasklist?(log)
return true unless form.start_year_2025_or_later?
log.staircase != 1
end
end

15
app/models/form/sales/subsections/income_benefits_and_savings.rb

@ -3,7 +3,14 @@ class Form::Sales::Subsections::IncomeBenefitsAndSavings < ::Form::Subsection
super
@id = "income_benefits_and_savings"
@label = "Income, benefits and savings"
@depends_on = [{ "setup_completed?" => true }]
end
def depends_on
if form.start_year_2025_or_later?
[{ "setup_completed?" => true, "is_staircase?" => false }]
else
[{ "setup_completed?" => true }]
end
end
def pages
@ -36,6 +43,12 @@ class Form::Sales::Subsections::IncomeBenefitsAndSavings < ::Form::Subsection
].compact
end
def displayed_in_tasklist?(log)
return true unless form.start_year_2025_or_later?
log.staircase != 1
end
private
def previous_shared_page

15
app/models/form/sales/subsections/other_household_information.rb

@ -3,7 +3,14 @@ class Form::Sales::Subsections::OtherHouseholdInformation < ::Form::Subsection
super
@id = "other_household_information"
@label = "Other household information"
@depends_on = [{ "setup_completed?" => true }]
end
def depends_on
if form.start_year_2025_or_later?
[{ "setup_completed?" => true, "is_staircase?" => false }]
else
[{ "setup_completed?" => true }]
end
end
def pages
@ -17,4 +24,10 @@ class Form::Sales::Subsections::OtherHouseholdInformation < ::Form::Subsection
Form::Sales::Pages::HouseholdWheelchairCheck.new("wheelchair_check", nil, self),
]
end
def displayed_in_tasklist?(log)
return true unless form.start_year_2025_or_later?
log.staircase != 1
end
end

1
app/models/form/sales/subsections/outright_sale.rb

@ -4,6 +4,7 @@ class Form::Sales::Subsections::OutrightSale < ::Form::Subsection
@id = "outright_sale"
@label = "Outright sale"
@depends_on = [{ "ownershipsch" => 3, "setup_completed?" => true }]
@copy_key = "sale_information"
end
def pages

48
app/models/form/sales/subsections/shared_ownership_initial_purchase.rb

@ -0,0 +1,48 @@
class Form::Sales::Subsections::SharedOwnershipInitialPurchase < ::Form::Subsection
def initialize(id, hsh, section)
super
@id = "shared_ownership_initial_purchase"
@label = "Shared ownership - initial purchase"
@depends_on = [{ "ownershipsch" => 1, "setup_completed?" => true, "staircase" => 2 }]
end
def pages
@pages ||= [
Form::Sales::Pages::Resale.new(nil, nil, self),
Form::Sales::Pages::LivingBeforePurchase.new("living_before_purchase_shared_ownership_joint_purchase", nil, self, ownershipsch: 1, joint_purchase: true),
Form::Sales::Pages::LivingBeforePurchase.new("living_before_purchase_shared_ownership", nil, self, ownershipsch: 1, joint_purchase: false),
Form::Sales::Pages::HandoverDate.new(nil, nil, self),
Form::Sales::Pages::HandoverDateCheck.new(nil, nil, self),
Form::Sales::Pages::BuyerPrevious.new("buyer_previous_joint_purchase", nil, self, joint_purchase: true),
Form::Sales::Pages::BuyerPrevious.new("buyer_previous_not_joint_purchase", nil, self, joint_purchase: false),
Form::Sales::Pages::PreviousBedrooms.new(nil, nil, self),
Form::Sales::Pages::PreviousPropertyType.new(nil, nil, self),
Form::Sales::Pages::PreviousTenure.new(nil, nil, self),
Form::Sales::Pages::ValueSharedOwnership.new(nil, nil, self),
Form::Sales::Pages::AboutPriceValueCheck.new("about_price_shared_ownership_value_check", nil, self),
Form::Sales::Pages::Equity.new(nil, nil, self),
Form::Sales::Pages::SharedOwnershipDepositValueCheck.new("shared_ownership_equity_value_check", nil, self),
Form::Sales::Pages::Mortgageused.new("mortgage_used_shared_ownership", nil, self, ownershipsch: 1),
Form::Sales::Pages::MortgageValueCheck.new("mortgage_used_mortgage_value_check", nil, self),
Form::Sales::Pages::MortgageAmount.new("mortgage_amount_shared_ownership", nil, self, ownershipsch: 1),
Form::Sales::Pages::SharedOwnershipDepositValueCheck.new("shared_ownership_mortgage_amount_value_check", nil, self),
Form::Sales::Pages::MortgageValueCheck.new("mortgage_amount_mortgage_value_check", nil, self),
Form::Sales::Pages::MortgageLength.new("mortgage_length_shared_ownership", nil, self, ownershipsch: 1),
Form::Sales::Pages::Deposit.new("deposit_shared_ownership", nil, self, ownershipsch: 1, optional: false),
Form::Sales::Pages::Deposit.new("deposit_shared_ownership_optional", nil, self, ownershipsch: 1, optional: true),
Form::Sales::Pages::DepositValueCheck.new("deposit_joint_purchase_value_check", nil, self, joint_purchase: true),
Form::Sales::Pages::DepositValueCheck.new("deposit_value_check", nil, self, joint_purchase: false),
Form::Sales::Pages::DepositDiscount.new("deposit_discount", nil, self, optional: false),
Form::Sales::Pages::DepositDiscount.new("deposit_discount_optional", nil, self, optional: true),
Form::Sales::Pages::SharedOwnershipDepositValueCheck.new("shared_ownership_deposit_value_check", nil, self),
Form::Sales::Pages::MonthlyRent.new(nil, nil, self),
Form::Sales::Pages::LeaseholdCharges.new("leasehold_charges_shared_ownership", nil, self, ownershipsch: 1),
Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_shared_ownership_value_check", nil, self),
Form::Sales::Pages::EstateManagementFee.new("estate_management_fee", nil, self),
].compact
end
def displayed_in_tasklist?(log)
log.staircase == 2 && (log.ownershipsch.nil? || log.ownershipsch == 1)
end
end

2
app/models/form/sales/subsections/shared_ownership_scheme.rb

@ -11,7 +11,7 @@ class Form::Sales::Subsections::SharedOwnershipScheme < ::Form::Subsection
@pages ||= [
Form::Sales::Pages::LivingBeforePurchase.new("living_before_purchase_shared_ownership_joint_purchase", nil, self, ownershipsch: 1, joint_purchase: true),
Form::Sales::Pages::LivingBeforePurchase.new("living_before_purchase_shared_ownership", nil, self, ownershipsch: 1, joint_purchase: false),
(Form::Sales::Pages::Staircase.new(nil, nil, self) unless form.start_year_2025_or_later?),
Form::Sales::Pages::Staircase.new(nil, nil, self),
Form::Sales::Pages::AboutStaircase.new("about_staircasing_joint_purchase", nil, self, joint_purchase: true),
Form::Sales::Pages::AboutStaircase.new("about_staircasing_not_joint_purchase", nil, self, joint_purchase: false),
Form::Sales::Pages::StaircaseBoughtValueCheck.new(nil, nil, self),

4
app/models/form/section.rb

@ -10,4 +10,8 @@ class Form::Section
@subsections = hsh["subsections"].map { |s_id, s| Form::Subsection.new(s_id, s, self) }
end
end
def displayed_in_tasklist?(log)
subsections.any? { |subsection| subsection.displayed_in_tasklist?(log) }
end
end

1
app/models/log.rb

@ -111,6 +111,7 @@ class Log < ApplicationRecord
self.town_or_city = nil
self.county = nil
self.postcode_full = postcode_full_input
process_postcode_changes!
else
self.uprn = uprn_selection
self.uprn_confirmed = 1

7
app/models/merge_request_organisation.rb

@ -29,5 +29,12 @@ private
if merging_organisation_id.blank? || !Organisation.where(id: merging_organisation_id).exists?
merge_request.errors.add(:merging_organisation, I18n.t("validations.merge_request.organisation_not_selected"))
end
existing_merges = MergeRequestOrganisation.with_merging_organisation(merging_organisation)
if existing_merges.count.positive?
existing_merge_request = existing_merges.first.merge_request
errors.add(:merging_organisation, I18n.t("validations.merge_request.organisation_part_of_another_merge"))
merge_request.errors.add(:merging_organisation, I18n.t("validations.merge_request.organisation_part_of_another_incomplete_merge", organisation: merging_organisation.name, absorbing_organisation: existing_merge_request.absorbing_organisation&.name, merge_date: existing_merge_request.merge_date&.to_fs(:govuk_date)))
end
end
end

27
app/models/validations/property_validations.rb

@ -46,4 +46,31 @@ module Validations::PropertyValidations
record.errors.add :postcode_full, :wrong_format, message: error_message
end
end
def validate_la_in_england(record)
return unless record.form.start_year_2025_or_later?
if record.is_general_needs?
return unless record.la
return if record.la.in?(LocalAuthority.england.pluck(:code))
record.errors.add :la, I18n.t("validations.lettings.property.la.not_in_england")
record.errors.add :postcode_full, I18n.t("validations.lettings.property.postcode_full.not_in_england")
record.errors.add :uprn, I18n.t("validations.lettings.property.uprn.not_in_england")
record.errors.add :uprn_confirmation, I18n.t("validations.lettings.property.uprn_confirmation.not_in_england")
record.errors.add :uprn_selection, I18n.t("validations.lettings.property.uprn_selection.not_in_england")
if record.uprn.present?
record.errors.add :startdate, I18n.t("validations.lettings.property.startdate.address_not_in_england")
else
record.errors.add :startdate, I18n.t("validations.lettings.property.startdate.postcode_not_in_england")
end
elsif record.is_supported_housing?
return unless record.location
return if record.location.location_code.in?(LocalAuthority.england.pluck(:code))
record.errors.add :location_id, I18n.t("validations.lettings.property.location_id.not_in_england")
record.errors.add :scheme_id, I18n.t("validations.lettings.property.scheme_id.not_in_england")
record.errors.add :startdate, I18n.t("validations.lettings.property.startdate.location_not_in_england")
end
end
end

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

@ -36,4 +36,20 @@ module Validations::Sales::PropertyValidations
record.errors.add :postcode_full, :wrong_format, message: error_message
end
end
def validate_la_in_england(record)
return unless record.form.start_year_2025_or_later? && record.la.present?
return if record.la.in?(LocalAuthority.england.pluck(:code))
record.errors.add :la, I18n.t("validations.sales.property_information.la.not_in_england")
record.errors.add :postcode_full, I18n.t("validations.sales.property_information.postcode_full.not_in_england")
record.errors.add :uprn, I18n.t("validations.sales.property_information.uprn.not_in_england")
record.errors.add :uprn_confirmation, I18n.t("validations.sales.property_information.uprn_confirmation.not_in_england")
record.errors.add :uprn_selection, I18n.t("validations.sales.property_information.uprn_selection.not_in_england")
if record.uprn.present?
record.errors.add :saledate, I18n.t("validations.sales.property_information.saledate.address_not_in_england")
else
record.errors.add :saledate, I18n.t("validations.sales.property_information.saledate.postcode_not_in_england")
end
end
end

5
app/models/validations/sales/sale_information_validations.rb

@ -12,7 +12,10 @@ module Validations::Sales::SaleInformationValidations
record.errors.add :saledate, I18n.t("validations.sales.sale_information.saledate.must_be_after_hodate")
end
if record.saledate - record.hodate >= 3.years && record.form.start_year_2024_or_later?
if (record.saledate - 5.years) >= record.hodate && record.form.start_year_2025_or_later?
record.errors.add :hodate, I18n.t("validations.sales.sale_information.hodate.must_be_less_than_5_years_from_saledate")
record.errors.add :saledate, I18n.t("validations.sales.sale_information.saledate.must_be_less_than_5_years_from_hodate")
elsif (record.saledate - 3.years) >= record.hodate && record.startdate.year <= 2024
record.errors.add :hodate, I18n.t("validations.sales.sale_information.hodate.must_be_less_than_3_years_from_saledate")
record.errors.add :saledate, I18n.t("validations.sales.sale_information.saledate.must_be_less_than_3_years_from_hodate")
end

6
app/models/validations/sales/soft_validations.rb

@ -110,6 +110,12 @@ module Validations::Sales::SoftValidations
saledate - hodate >= 3.years
end
def hodate_5_years_or_more_saledate?
return unless hodate && saledate
saledate - hodate >= 5.years
end
def purchase_price_higher_or_lower_text
value < sale_range.soft_min ? "lower" : "higher"
end

16
app/policies/csv_download_policy.rb

@ -0,0 +1,16 @@
class CsvDownloadPolicy
attr_reader :current_user, :csv_download
def initialize(current_user, csv_download)
@current_user = current_user
@csv_download = csv_download
end
def show?
@current_user == @csv_download.user || @current_user.support? || @current_user.organisation == @csv_download.organisation
end
def download?
@current_user == @csv_download.user || @current_user.support? || @current_user.organisation == @csv_download.organisation
end
end

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

@ -15,7 +15,7 @@ class BulkUpload::Lettings::Year2024::CsvParser
def row_offset
if with_headers?
rows.find_index { |row| row[0].match(/field number/i) } + 1
rows.find_index { |row| row[0].present? && row[0].match(/field number/i) } + 1
else
0
end

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

@ -15,7 +15,7 @@ class BulkUpload::Sales::Year2024::CsvParser
def row_offset
if with_headers?
rows.find_index { |row| row[0].match(/field number/i) } + 1
rows.find_index { |row| row[0].present? && row[0].match(/field number/i) } + 1
else
0
end

50
app/services/csv/downloader.rb

@ -0,0 +1,50 @@
class Csv::Downloader
attr_reader :csv_download
delegate :path, to: :file
def initialize(csv_download:)
@csv_download = csv_download
end
def call
download
end
def delete_local_file!
file.unlink
end
def presigned_url
s3_storage_service.get_presigned_url(csv_download.filename, 60, response_content_disposition: "attachment; filename=#{csv_download.filename}")
end
private
def download
io = storage_service.get_file_io(csv_download.filename)
file.write(io.read)
io.close
file.close
end
def file
@file ||= Tempfile.new
end
def storage_service
@storage_service ||= if FeatureToggle.upload_enabled?
s3_storage_service
else
local_disk_storage_service
end
end
def s3_storage_service
Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"])
end
def local_disk_storage_service
Storage::LocalDiskService.new
end
end

4
app/services/feature_toggle.rb

@ -46,4 +46,8 @@ class FeatureToggle
def self.managing_resources_enabled?
!Rails.env.production?
end
def self.create_test_logs_enabled?
Rails.env.development? || Rails.env.review?
end
end

2
app/services/mandatory_collection_resources_service.rb

@ -46,7 +46,7 @@ class MandatoryCollectionResourcesService
year_range = "#{year} to #{year + 1}"
case resource
when "paper_form"
"#{log_type} log for tenants (#{year_range})"
"#{log_type} paper form (#{year_range})"
when "bulk_upload_template"
"#{log_type} bulk upload template (#{year_range})"
when "bulk_upload_specification"

10
app/views/csv_downloads/show.html.erb

@ -0,0 +1,10 @@
<% title = "Downlaod CSV file" %>
<% content_for :title, title %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<h1 class="govuk-heading-l govuk-!-margin-bottom-6">You are about to download a CSV file</h1>
<p class="govuk-body-m">Filename: <%= @csv_download.filename %></p>
<%= govuk_button_link_to "Download CSV", download_csv_download_path(@csv_download) %>
</div>
</div>

8
app/views/errors/download_link_expired.html.erb

@ -0,0 +1,8 @@
<% content_for :title, "This link has expired" %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<h1 class="govuk-heading-xl govuk-!-margin-bottom-6">This link has expired.</h1>
<p class="govuk-body-m">Download the logs again to get a new link.</p>
</div>
</div>

6
app/views/form/guidance/_financial_calculations_shared_ownership.html.erb

@ -20,11 +20,11 @@
<% end %>
must equal
the purchase price <%= question_link("value", log, current_user) %>
<% stairbought_page = log.form.get_question("stairbought", log).page %>
<% if stairbought_page.routed_to?(log, current_user) %>
<% stairbought_page = log.form.get_question("stairbought", log)&.page %>
<% if stairbought_page&.routed_to?(log, current_user) %>
multiplied by the percentage bought <%= question_link("stairbought", log, current_user) %>
<% else %>
multiplied by the percentage equity stake <%= question_link("equity", log, current_user) %>
multiplied by the percentage equity share <%= question_link("equity", log, current_user) %>
<% end %>
</p>
<% end %>

5
app/views/logs/_tasklist.html.erb

@ -1,5 +1,6 @@
<ol class="app-task-list govuk-!-margin-top-8">
<% @log.form.sections.map do |section| %>
<% @log.form.sections.each do |section| %>
<% next unless section.displayed_in_tasklist?(@log) %>
<li>
<h2 class="app-task-list__section-heading">
<%= section.label %>
@ -8,7 +9,7 @@
<p class="govuk-body"><%= section.description.html_safe %></p>
<% end %>
<ul class="app-task-list__items">
<% section.subsections.map do |subsection| %>
<% section.subsections.each do |subsection| %>
<% if subsection.displayed_in_tasklist?(@log) && (subsection.applicable_questions(@log).count > 0 || !subsection.enabled?(@log)) %>
<% subsection_status = subsection.status(@log) %>
<li class="app-task-list__item">

8
app/views/merge_requests/_notification_banners.html.erb

@ -19,3 +19,11 @@
No changes have been made. Try beginning the merge again.
<% end %>
<% end %>
<% if @merge_request.merge_date&.future? %>
<%= govuk_notification_banner(title_text: "Important") do %>
<p class="govuk-notification-banner__heading govuk-!-width-full" style="max-width: fit-content">
This merge is happening in the future. Wait until the merge date to begin this merge.
</p>
<% end %>
<% end %>

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

@ -12,7 +12,7 @@
</h1>
<% unless @merge_request.status == "request_merged" || @merge_request.status == "processing" %>
<div class="govuk-button-group">
<%= govuk_button_link_to "Begin merge", merge_start_confirmation_merge_request_path(@merge_request), disabled: @merge_request.status != "ready_to_merge" %>
<%= govuk_button_link_to "Begin merge", merge_start_confirmation_merge_request_path(@merge_request), disabled: begin_merge_disabled?(@merge_request) %>
<%= govuk_button_link_to "Delete merge request", delete_confirmation_merge_request_path(@merge_request), warning: true %>
</div>
<% end %>

4
config/locales/en.yml

@ -183,8 +183,11 @@ en:
merge_date:
blank: "Enter a merge date."
invalid: "Enter a valid merge date."
more_than_year_from_today: "The merge date must not be later than a year from today’s date."
existing_absorbing_organisation:
blank: "You must answer absorbing organisation already active?"
merging_organisation_id:
part_of_another_merge: "Another merge request records %{organisation} as merging into %{absorbing_organisation} on %{merge_date}. Select another organisation or remove this organisation from the other merge request."
notification:
attributes:
title:
@ -372,6 +375,7 @@ en:
during_deactivated_period: "The location is already deactivated during this date, please enter a different date."
merge_request:
organisation_part_of_another_merge: "This organisation is part of another merge - select a different one."
organisation_part_of_another_incomplete_merge: "Another merge request records %{organisation} as merging into %{absorbing_organisation} on %{merge_date}. Select another organisation or remove this organisation from the other merge request."
organisation_not_selected: "Select an organisation from the search list."
soft_validations:

2
config/locales/forms/2024/lettings/setup.en.yml

@ -30,7 +30,7 @@ en:
scheme_id:
page_header: "Scheme"
check_answer_label: "Scheme name"
hint_text: "Enter postcode or scheme name.<br><br>A supported housing scheme provides shared or self-contained housing for a particular client group, for example younger or vulnerable people."
hint_text: "Enter postcode, scheme name, or scheme code (for example, S123).<br><br>A supported housing scheme provides shared or self-contained housing for a particular client group, for example younger or vulnerable people."
question_text: "What scheme is this log for?"
location_id:

8
config/locales/forms/2024/lettings/soft_validations.en.yml

@ -130,3 +130,11 @@ en:
question_text: "Are you sure the property has been vacant for this long?"
title_text: "You told us the property has been vacant for 2 years."
informative_text: "This is longer than we would expect."
no_address_found:
page_header: ""
check_answer_label: "No address found"
hint_text: ""
question_text: "We could not find an address that matches your search. You can search again or continue to enter the address manually."
title_text: "No address found"
informative_text: "We could not find an address that matches your search. You can search again or continue to enter the address manually."

21
config/locales/forms/2025/sales/sale_information.en.yml

@ -107,9 +107,9 @@ en:
equity:
page_header: "About the price of the property"
check_answer_label: "Initial percentage equity stake"
hint_text: "Enter the amount of initial equity held by the purchaser (for example, 25% or 50%)"
question_text: "What was the initial percentage equity stake purchased?"
check_answer_label: "Initial percentage equity share"
hint_text: "Enter the amount of initial equity share held by the purchaser (for example, 25% or 50%)"
question_text: "What was the initial percentage share purchased?"
mortgageused:
page_header: "Mortgage Amount"
@ -168,9 +168,9 @@ en:
leaseholdcharges:
page_header: ""
has_mscharge:
check_answer_label: "Does the property have any monthly leasehold charges?"
check_answer_label: "Does the property have any service charges?"
hint_text: "For example, service and management charges"
question_text: "Does the property have any monthly leasehold charges?"
question_text: "Does the property have any service charges?"
mscharge:
check_answer_label: "Monthly leasehold charges"
hint_text: ""
@ -199,3 +199,14 @@ en:
check_answer_label: "Amount of any loan, grant or subsidy"
hint_text: "For all schemes except Right to Buy (RTB), Preserved Right to Buy (PRTB), Voluntary Right to Buy (VRTB) and Rent to Buy"
question_text: "What was the amount of any loan, grant, discount or subsidy given?"
management_fee:
page_header: ""
has_management_fee:
check_answer_label: "Does the property have an estate management fee?"
hint_text: "Estate management fees are typically used for the maintenance of communal gardens, payments, private roads, car parks and/or play areas within new build estates."
question_text: "Does the property have an estate management fee?"
management_fee:
check_answer_label: "Monthly estate management fee"
hint_text: ""
question_text: "Enter the total monthly management fee"

17
config/locales/validations/lettings/property_information.en.yml

@ -4,6 +4,7 @@ en:
property:
postcode_full:
invalid: "Enter a postcode in the correct format, for example AA1 1AA."
not_in_england: "It looks like you have an entered a postcode outside of England. Only create logs for lettings in England."
rsnvac:
non_temp_accommodation: "Answer cannot be re-let to tenant who occupied the same property as temporary accommodation as this accommodation is not temporary."
referral_invalid: "Answer cannot be re-let to tenant who occupied the same property as temporary accommodation as a different source of referral for this letting."
@ -18,4 +19,18 @@ en:
one_seven_bedroom_shared: "A shared house must have 1 to 7 bedrooms."
uprn:
invalid: "UPRN must be 12 digits or less."
not_in_england: "It looks like you have entered an address outside of England. Only create logs for lettings in England."
uprn_confirmation:
not_in_england: "It looks like you have entered an address outside of England. Only create logs for lettings in England."
uprn_selection:
not_in_england: "It looks like you have entered an address outside of England. Only create logs for lettings in England."
la:
not_in_england: "It looks like you have entered an address outside of England. Only create logs for lettings in England."
scheme_id:
not_in_england: "This scheme’s only location is outside of England. Only create logs for lettings in England."
location_id:
not_in_england: "It looks like you have selected a location outside of England. Only create logs for lettings in England."
startdate:
postcode_not_in_england: "It looks like you have an entered a postcode outside of England. Only create logs for lettings in England."
address_not_in_england: "It looks like you have entered an address outside of England. Only create logs for lettings in England."
location_not_in_england: "It looks like you have selected a location outside of England. Only create logs for lettings in England."

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

@ -7,6 +7,7 @@ en:
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: "Enter a postcode in the correct format, for example AA1 1AA."
not_in_england: "It looks like you have entered a postcode outside of England - only submit Lettings forms for Lettings that occur in England"
ppostcode_full:
postcode_must_match_previous:
joint_purchase: "Buyers’ last accommodation and discounted ownership postcodes must match."
@ -20,9 +21,19 @@ en:
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."
not_in_england: "It looks like you have an entered a postcode outside of England. Only create logs for lettings in England."
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?"
la:
not_in_england: "It looks like you have entered an address outside of England. Only create logs for lettings in England."
uprn_confirmation:
not_in_england: "It looks like you have entered an address outside of England. Only create logs for lettings in England."
uprn_selection:
not_in_england: "It looks like you have entered an address outside of England. Only create logs for lettings in England."
saledate:
postcode_not_in_england: "It looks like you have an entered a postcode outside of England. Only create logs for lettings in England."
address_not_in_england: "It looks like you have entered an address outside of England. Only create logs for lettings in England."

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

@ -8,9 +8,11 @@ en:
hodate:
must_be_before_saledate: "Practical completion or handover date must be before sale completion date."
must_be_less_than_3_years_from_saledate: "Practical completion or handover date must be less than 3 years before sale completion date."
must_be_less_than_5_years_from_saledate: "Practical completion or handover date must be less than 5 years before sale completion date."
saledate:
must_be_after_hodate: "Sale completion date must be after practical completion or handover date."
must_be_less_than_3_years_from_hodate: "Sale completion date must be less than 3 years after practical completion or handover date."
must_be_less_than_5_years_from_hodate: "Sale completion date must be less than 5 years after practical completion or handover date."
must_be_after_exdate: "Sale completion date must be after contract exchange date."
must_be_less_than_1_year_from_exdate: "Sale completion date must be less than 1 year after contract exchange date."
mortgage_used_year: "You must answer either ‘yes’ or ‘no’ to the question ‘was a mortgage used’ for the selected year."

12
config/routes.rb

@ -382,6 +382,18 @@ Rails.application.routes.draw do
end
end
resources :csv_downloads, path: "csv-downloads" do
member do
get "/", to: "csv_downloads#show", as: "show"
get "download", to: "csv_downloads#download"
end
end
get "create-test-lettings-log", to: "lettings_logs#create_test_log"
get "create-test-sales-log", to: "sales_logs#create_test_log"
get "create-setup-test-lettings-log", to: "lettings_logs#create_setup_test_log"
get "create-setup-test-sales-log", to: "sales_logs#create_setup_test_log"
scope via: :all do
match "/404", to: "errors#not_found"
match "/429", to: "errors#too_many_requests", status: 429

8
db/migrate/20241114154215_add_management_fee_fields.rb

@ -0,0 +1,8 @@
class AddManagementFeeFields < ActiveRecord::Migration[7.0]
def change
change_table :sales_logs, bulk: true do |t|
t.column :has_management_fee, :integer
t.column :management_fee, :decimal, precision: 10, scale: 2
end
end
end

12
db/migrate/20241118104046_add_csv_download_table.rb

@ -0,0 +1,12 @@
class AddCsvDownloadTable < ActiveRecord::Migration[7.0]
def change
create_table :csv_downloads do |t|
t.column :download_type, :string
t.column :filename, :string
t.column :expiration_time, :integer
t.timestamps
t.references :user
t.references :organisation
end
end
end

16
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_31_102744) do
ActiveRecord::Schema[7.0].define(version: 2024_11_18_104046) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -64,6 +64,18 @@ ActiveRecord::Schema[7.0].define(version: 2024_10_31_102744) do
t.datetime "discarded_at"
end
create_table "csv_downloads", force: :cascade do |t|
t.string "download_type"
t.string "filename"
t.integer "expiration_time"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "user_id"
t.bigint "organisation_id"
t.index ["organisation_id"], name: "index_csv_downloads_on_organisation_id"
t.index ["user_id"], name: "index_csv_downloads_on_user_id"
end
create_table "csv_variable_definitions", force: :cascade do |t|
t.string "variable", null: false
t.string "definition", null: false
@ -746,6 +758,8 @@ ActiveRecord::Schema[7.0].define(version: 2024_10_31_102744) do
t.integer "partner_under_16_value_check"
t.integer "multiple_partners_value_check"
t.bigint "created_by_id"
t.integer "has_management_fee"
t.decimal "management_fee", precision: 10, scale: 2
t.index ["assigned_to_id"], name: "index_sales_logs_on_assigned_to_id"
t.index ["bulk_upload_id"], name: "index_sales_logs_on_bulk_upload_id"
t.index ["created_by_id"], name: "index_sales_logs_on_created_by_id"

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

Loading…
Cancel
Save