Browse Source

Merge branch 'main' into CLDC-3740-Replace-you-didnt-answer-with-link

# Conflicts:
#	config/locales/forms/2025/sales/sale_information.en.yml
CLDC-3740-Replace-you-didnt-answer-with-link
Manny Dinssa 4 weeks ago
parent
commit
5f212e7ebc
  1. 2
      Dockerfile
  2. 36
      app/controllers/form_controller.rb
  3. 3
      app/models/derived_variables/sales_log_variables.rb
  4. 2
      app/models/form/sales/pages/about_staircase.rb
  5. 2
      app/models/form/sales/pages/equity.rb
  6. 17
      app/models/form/sales/pages/monthly_rent_staircasing.rb
  7. 17
      app/models/form/sales/pages/monthly_rent_staircasing_owned.rb
  8. 15
      app/models/form/sales/pages/staircase_first_time.rb
  9. 16
      app/models/form/sales/pages/staircase_initial_date.rb
  10. 18
      app/models/form/sales/pages/staircase_previous.rb
  11. 17
      app/models/form/sales/pages/staircase_sale.rb
  12. 2
      app/models/form/sales/pages/value_shared_ownership.rb
  13. 1
      app/models/form/sales/questions/equity.rb
  14. 15
      app/models/form/sales/questions/monthly_rent_after_staircasing.rb
  15. 15
      app/models/form/sales/questions/monthly_rent_before_staircasing.rb
  16. 2
      app/models/form/sales/questions/mortgageused.rb
  17. 15
      app/models/form/sales/questions/staircase_count.rb
  18. 16
      app/models/form/sales/questions/staircase_first_time.rb
  19. 11
      app/models/form/sales/questions/staircase_initial_date.rb
  20. 11
      app/models/form/sales/questions/staircase_last_date.rb
  21. 2
      app/models/form/sales/questions/staircase_sale.rb
  22. 1
      app/models/form/sales/questions/value.rb
  23. 16
      app/models/form/sales/sections/sale_information.rb
  24. 5
      app/models/form/sales/subsections/shared_ownership_initial_purchase.rb
  25. 4
      app/models/form/sales/subsections/shared_ownership_scheme.rb
  26. 35
      app/models/form/sales/subsections/shared_ownership_staircasing_transaction.rb
  27. 3
      app/models/location.rb
  28. 4
      app/models/sales_log.rb
  29. 17
      app/models/validations/sales/sale_information_validations.rb
  30. 4
      app/views/devise/sessions/new.html.erb
  31. 2
      app/views/devise/shared/_links.html.erb
  32. 5
      app/views/form/_checkbox_question.html.erb
  33. 8
      app/views/form/_radio_question.html.erb
  34. 4
      config/locales/en.yml
  35. 74
      config/locales/forms/2025/sales/sale_information.en.yml
  36. 6
      config/locales/validations/sales/sale_information.en.yml
  37. 11
      db/migrate/20241114173226_add_fields_to_sales_log.rb
  38. 5
      db/schema.rb
  39. 4
      spec/features/user_spec.rb
  40. 4
      spec/models/form/sales/pages/about_staircase_spec.rb
  41. 4
      spec/models/form/sales/pages/equity_spec.rb
  42. 31
      spec/models/form/sales/pages/monthly_rent_staircasing_owned_spec.rb
  43. 31
      spec/models/form/sales/pages/monthly_rent_staircasing_spec.rb
  44. 31
      spec/models/form/sales/pages/staircase_first_time_spec.rb
  45. 31
      spec/models/form/sales/pages/staircase_initial_date_spec.rb
  46. 31
      spec/models/form/sales/pages/staircase_previous_spec.rb
  47. 38
      spec/models/form/sales/pages/staircase_sale_spec.rb
  48. 2
      spec/models/form/sales/pages/value_shared_ownership_spec.rb
  49. 2
      spec/models/form/sales/questions/equity_spec.rb
  50. 37
      spec/models/form/sales/questions/monthly_rent_after_staircasing_spec.rb
  51. 37
      spec/models/form/sales/questions/monthly_rent_before_staircasing_spec.rb
  52. 98
      spec/models/form/sales/questions/mortgageused_spec.rb
  53. 33
      spec/models/form/sales/questions/staircase_count_spec.rb
  54. 40
      spec/models/form/sales/questions/staircase_first_time_spec.rb
  55. 33
      spec/models/form/sales/questions/staircase_initial_date_spec.rb
  56. 33
      spec/models/form/sales/questions/staircase_last_date_spec.rb
  57. 4
      spec/models/form/sales/questions/staircase_sale_spec.rb
  58. 6
      spec/models/form/sales/questions/value_spec.rb
  59. 11
      spec/models/form/sales/sections/sale_information_spec.rb
  60. 2
      spec/models/form/sales/subsections/shared_ownership_initial_purchase_spec.rb
  61. 36
      spec/models/location_spec.rb
  62. 40
      spec/models/validations/sales/sale_information_validations_spec.rb
  63. 2
      spec/models/validations/setup_validations_spec.rb
  64. 11
      spec/requests/form_controller_spec.rb

2
Dockerfile

@ -10,7 +10,7 @@ RUN apk add --update --no-cache tzdata && \
# build-base: compilation tools for bundle
# yarn: node package manager
# postgresql-dev: postgres driver and libraries
RUN apk add --no-cache build-base=0.5-r3 busybox=1.36.1-r7 nodejs-current=20.8.1-r0 yarn=1.22.19-r0 postgresql13-dev=13.17-r0 git=2.40.3-r0 bash=5.2.15-r5
RUN apk add --no-cache build-base=0.5-r3 busybox=1.36.1-r7 nodejs-current=20.8.1-r0 yarn=1.22.19-r0 postgresql13-dev=13.18-r0 git=2.40.3-r0 bash=5.2.15-r5
# Bundler version should be the same version as what the Gemfile.lock was bundled with
RUN gem install bundler:2.3.14 --no-document

36
app/controllers/form_controller.rb

@ -5,6 +5,7 @@ class FormController < ApplicationController
before_action :find_resource, only: %i[review]
before_action :find_resource_by_named_id, except: %i[review]
before_action :check_collection_period, only: %i[submit_form show_page]
before_action :set_cache_headers, only: [:show_page]
def submit_form
if @log
@ -37,8 +38,9 @@ class FormController < ApplicationController
error_attributes = @log.errors.map(&:attribute)
Rails.logger.info "User triggered validation(s) on: #{error_attributes.join(', ')}"
@subsection = form.subsection_for_page(@page)
restore_error_field_values(@page&.questions)
render "form/page"
flash[:errors] = @log.errors
flash[:log_data] = responses_for_page
redirect_to send("#{@log.class.name.underscore}_#{@page.id}_path", @log, { referrer: request.params["referrer"], original_page_id: request.params["original_page_id"], related_question_ids: request.params["related_question_ids"] })
end
else
render_not_found
@ -84,6 +86,10 @@ class FormController < ApplicationController
@questions = request.params["related_question_ids"].map { |id| @log.form.get_question(id, @log) }
render "form/check_errors"
else
if flash[:errors].present?
restore_previous_errors(flash[:errors])
restore_error_field_values(flash[:log_data])
end
render "form/page"
end
else
@ -96,13 +102,21 @@ class FormController < ApplicationController
private
def restore_error_field_values(questions)
return unless questions
def restore_error_field_values(previous_responses)
return unless previous_responses
questions.each do |question|
if question&.type == "date" && @log.attributes.key?(question.id)
@log[question.id] = @log.send("#{question.id}_was")
end
previous_responses_to_reset = previous_responses.reject do |key, _|
@log.form.get_question(key, @log)&.type == "date"
end
@log.assign_attributes(previous_responses_to_reset)
end
def restore_previous_errors(previous_errors)
return unless previous_errors
previous_errors.each do |attribute, message|
@log.errors.add attribute, message.first
end
end
@ -431,4 +445,10 @@ private
def updated_answer_from_check_errors_page?
params["check_errors"]
end
def set_cache_headers
response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0"
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "Mon, 01 Jan 1990 00:00:00 GMT"
end
end

3
app/models/derived_variables/sales_log_variables.rb

@ -80,6 +80,9 @@ module DerivedVariables::SalesLogVariables
self.is_la_inferred = false
end
self.numstair = is_firststair? ? 1 : nil if numstair == 1 && firststair_changed?
self.mrent = 0 if stairowned_100?
set_encoded_derived_values!(DEPENDENCIES)
end

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

@ -18,7 +18,7 @@ class Form::Sales::Pages::AboutStaircase < ::Form::Page
end
def staircase_sale_question
if form.start_date.year >= 2023
if [2023, 2024].include?(form.start_date.year)
Form::Sales::Questions::StaircaseSale.new(nil, nil, self)
end
end

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

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

17
app/models/form/sales/pages/monthly_rent_staircasing.rb

@ -0,0 +1,17 @@
class Form::Sales::Pages::MonthlyRentStaircasing < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "monthly_rent_staircasing"
@copy_key = "sales.sale_information.mrent_staircasing"
@depends_on = [{
"stairowned_100?" => false,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::MonthlyRentBeforeStaircasing.new(nil, nil, self),
Form::Sales::Questions::MonthlyRentAfterStaircasing.new(nil, nil, self),
]
end
end

17
app/models/form/sales/pages/monthly_rent_staircasing_owned.rb

@ -0,0 +1,17 @@
class Form::Sales::Pages::MonthlyRentStaircasingOwned < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "monthly_rent_staircasing_owned"
@copy_key = "sales.sale_information.mrent_staircasing"
@header = ""
@depends_on = [{
"stairowned_100?" => true,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::MonthlyRentBeforeStaircasing.new(nil, nil, self),
]
end
end

15
app/models/form/sales/pages/staircase_first_time.rb

@ -0,0 +1,15 @@
class Form::Sales::Pages::StaircaseFirstTime < ::Form::Page
def initialize(id, hsh, subsection)
super(id, hsh, subsection)
@id = "staircase_first_time"
@depends_on = [{
"staircase" => 1,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::StaircaseFirstTime.new(nil, nil, self),
]
end
end

16
app/models/form/sales/pages/staircase_initial_date.rb

@ -0,0 +1,16 @@
class Form::Sales::Pages::StaircaseInitialDate < ::Form::Page
def initialize(id, hsh, subsection)
super(id, hsh, subsection)
@id = "staircase_initial_date"
@header = ""
@depends_on = [{
"is_firststair?" => true,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::StaircaseInitialDate.new(nil, nil, self),
]
end
end

18
app/models/form/sales/pages/staircase_previous.rb

@ -0,0 +1,18 @@
class Form::Sales::Pages::StaircasePrevious < ::Form::Page
def initialize(id, hsh, subsection)
super(id, hsh, subsection)
@id = "staircase_previous"
@copy_key = "sales.sale_information.stairprevious"
@depends_on = [{
"is_firststair?" => false,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::StaircaseCount.new(nil, nil, self),
Form::Sales::Questions::StaircaseLastDate.new(nil, nil, self),
Form::Sales::Questions::StaircaseInitialDate.new(nil, nil, self),
]
end
end

17
app/models/form/sales/pages/staircase_sale.rb

@ -0,0 +1,17 @@
class Form::Sales::Pages::StaircaseSale < ::Form::Page
def initialize(id, hsh, subsection)
super(id, hsh, subsection)
@id = "staircase_sale"
@copy_key = form.start_year_2025_or_later? ? "sales.sale_information.staircasesale" : "sales.sale_information.about_staircasing.staircasesale"
@depends_on = [{
"staircase" => 1,
"stairowned" => 100,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::StaircaseSale.new(nil, nil, self),
]
end
end

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

@ -1,7 +1,7 @@
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

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

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

15
app/models/form/sales/questions/monthly_rent_after_staircasing.rb

@ -0,0 +1,15 @@
class Form::Sales::Questions::MonthlyRentAfterStaircasing < ::Form::Question
def initialize(id, hsh, page)
super
@id = "mrent"
@copy_key = "sales.sale_information.mrent_staircasing.poststaircasing"
@type = "numeric"
@min = 0
@step = 0.01
@width = 5
@prefix = "£"
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end
QUESTION_NUMBER_FROM_YEAR = { 2025 => 99 }.freeze
end

15
app/models/form/sales/questions/monthly_rent_before_staircasing.rb

@ -0,0 +1,15 @@
class Form::Sales::Questions::MonthlyRentBeforeStaircasing < ::Form::Question
def initialize(id, hsh, page)
super
@id = "mrentprestaircasing"
@copy_key = "sales.sale_information.mrent_staircasing.prestaircasing"
@type = "numeric"
@min = 0
@step = 0.01
@width = 5
@prefix = "£"
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end
QUESTION_NUMBER_FROM_YEAR = { 2025 => 98 }.freeze
end

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

@ -12,7 +12,7 @@ class Form::Sales::Questions::Mortgageused < ::Form::Question
def displayed_answer_options(log, _user = nil)
if log.outright_sale? && log.saledate && !form.start_year_2024_or_later?
answer_options_without_dont_know
elsif log.stairowned == 100 || log.outright_sale?
elsif log.stairowned_100? || log.outright_sale? || (log.is_staircase? && form.start_year_2025_or_later?)
ANSWER_OPTIONS
else
answer_options_without_dont_know

15
app/models/form/sales/questions/staircase_count.rb

@ -0,0 +1,15 @@
class Form::Sales::Questions::StaircaseCount < ::Form::Question
def initialize(id, hsh, page)
super
@id = "numstair"
@copy_key = "sales.sale_information.stairprevious.numstair"
@type = "numeric"
@width = 2
@min = 2
@max = 10
@step = 1
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end
QUESTION_NUMBER_FROM_YEAR = { 2025 => 82 }.freeze
end

16
app/models/form/sales/questions/staircase_first_time.rb

@ -0,0 +1,16 @@
class Form::Sales::Questions::StaircaseFirstTime < ::Form::Question
def initialize(id, hsh, page)
super
@id = "firststair"
@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]
end
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
}.freeze
QUESTION_NUMBER_FROM_YEAR = { 2025 => 81 }.freeze
end

11
app/models/form/sales/questions/staircase_initial_date.rb

@ -0,0 +1,11 @@
class Form::Sales::Questions::StaircaseInitialDate < ::Form::Question
def initialize(id, hsh, page)
super
@id = "initialpurchase"
@copy_key = "sales.sale_information.stairprevious.initialpurchase"
@type = "date"
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end
QUESTION_NUMBER_FROM_YEAR = { 2025 => 83 }.freeze
end

11
app/models/form/sales/questions/staircase_last_date.rb

@ -0,0 +1,11 @@
class Form::Sales::Questions::StaircaseLastDate < ::Form::Question
def initialize(id, hsh, page)
super
@id = "lasttransaction"
@copy_key = "sales.sale_information.stairprevious.lasttransaction"
@type = "date"
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end
QUESTION_NUMBER_FROM_YEAR = { 2025 => 83 }.freeze
end

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

@ -2,7 +2,7 @@ class Form::Sales::Questions::StaircaseSale < ::Form::Question
def initialize(id, hsh, page)
super
@id = "staircasesale"
@copy_key = "sales.sale_information.about_staircasing.staircasesale"
@copy_key = form.start_year_2025_or_later? ? "sales.sale_information.staircasesale" : "sales.sale_information.about_staircasing.staircasesale"
@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,6 +2,7 @@ class Form::Sales::Questions::Value < ::Form::Question
def initialize(id, hsh, page)
super
@id = "value"
@copy_key = "sales.sale_information.value.#{page.id}"
@type = "numeric"
@min = 0
@step = 1

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

@ -4,18 +4,20 @@ class Form::Sales::Sections::SaleInformation < ::Form::Section
@id = "sale_information"
@label = "Sale information"
@description = ""
@subsections = [
shared_ownership_scheme_subsection,
Form::Sales::Subsections::DiscountedOwnershipScheme.new(nil, nil, self),
Form::Sales::Subsections::OutrightSale.new(nil, nil, self),
] || []
@subsections = []
@subsections.concat(shared_ownership_scheme_subsection)
@subsections << Form::Sales::Subsections::DiscountedOwnershipScheme.new(nil, nil, self)
@subsections << 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)
[
Form::Sales::Subsections::SharedOwnershipInitialPurchase.new(nil, nil, self),
Form::Sales::Subsections::SharedOwnershipStaircasingTransaction.new(nil, nil, self),
]
else
Form::Sales::Subsections::SharedOwnershipScheme.new(nil, nil, self)
[Form::Sales::Subsections::SharedOwnershipScheme.new(nil, nil, self)]
end
end
end

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

@ -4,6 +4,7 @@ class Form::Sales::Subsections::SharedOwnershipInitialPurchase < ::Form::Subsect
@id = "shared_ownership_initial_purchase"
@label = "Shared ownership - initial purchase"
@depends_on = [{ "ownershipsch" => 1, "setup_completed?" => true, "staircase" => 2 }]
@copy_key = "sale_information"
end
def pages
@ -18,9 +19,9 @@ class Form::Sales::Subsections::SharedOwnershipInitialPurchase < ::Form::Subsect
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::ValueSharedOwnership.new("value_shared_ownership", 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::Equity.new("initial_equity", 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),

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

@ -27,9 +27,9 @@ class Form::Sales::Subsections::SharedOwnershipScheme < ::Form::Subsection
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::ValueSharedOwnership.new("value_shared_ownership", 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::Equity.new("equity", 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),

35
app/models/form/sales/subsections/shared_ownership_staircasing_transaction.rb

@ -0,0 +1,35 @@
class Form::Sales::Subsections::SharedOwnershipStaircasingTransaction < ::Form::Subsection
def initialize(id, hsh, section)
super
@id = "shared_ownership_staircasing_transaction"
@label = "Shared ownership - staircasing transaction"
@depends_on = [{ "ownershipsch" => 1, "setup_completed?" => true, "staircase" => 1 }]
@copy_key = "sale_information"
end
def pages
@pages ||= [
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::StaircaseSale.new(nil, nil, self),
Form::Sales::Pages::StaircaseBoughtValueCheck.new(nil, nil, self),
Form::Sales::Pages::StaircaseOwnedValueCheck.new("staircase_owned_value_check_joint_purchase", nil, self, joint_purchase: true),
Form::Sales::Pages::StaircaseOwnedValueCheck.new("staircase_owned_value_check_not_joint_purchase", nil, self, joint_purchase: false),
Form::Sales::Pages::StaircaseFirstTime.new(nil, nil, self),
Form::Sales::Pages::StaircasePrevious.new(nil, nil, self),
Form::Sales::Pages::StaircaseInitialDate.new(nil, nil, self),
Form::Sales::Pages::ValueSharedOwnership.new("value_shared_ownership_staircase", nil, self),
Form::Sales::Pages::AboutPriceValueCheck.new("about_price_shared_ownership_value_check", nil, self),
Form::Sales::Pages::Equity.new("staircase_equity", nil, self),
Form::Sales::Pages::SharedOwnershipDepositValueCheck.new("shared_ownership_equity_value_check", nil, self),
Form::Sales::Pages::Mortgageused.new("staircase_mortgage_used_shared_ownership", nil, self, ownershipsch: 1),
Form::Sales::Pages::MonthlyRentStaircasingOwned.new(nil, nil, self),
Form::Sales::Pages::MonthlyRentStaircasing.new(nil, nil, self),
Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_shared_ownership_value_check", nil, self),
].compact
end
def displayed_in_tasklist?(log)
log.staircase == 1 && (log.ownershipsch.nil? || log.ownershipsch == 1)
end
end

3
app/models/location.rb

@ -2,7 +2,8 @@ class Location < ApplicationRecord
validates :postcode, on: :postcode, presence: { message: I18n.t("validations.location.postcode_blank") }
validate :validate_postcode, on: :postcode, if: proc { |model| model.postcode.presence }
validates :location_admin_district, on: :location_admin_district, presence: { message: I18n.t("validations.location_admin_district") }
validates :units, on: :units, presence: { message: I18n.t("validations.location.units") }
validates :units, on: :units, presence: { message: I18n.t("validations.location.units.must_be_number") }
validates :units, on: :units, numericality: { greater_than_or_equal_to: 1, message: I18n.t("validations.location.units.must_be_one_or_more") }
validates :type_of_unit, on: :type_of_unit, presence: { message: I18n.t("validations.location.type_of_unit") }
validates :mobility_type, on: :mobility_type, presence: { message: I18n.t("validations.location.mobility_standards") }
validates :startdate, on: :startdate, presence: { message: I18n.t("validations.location.startdate_invalid") }

4
app/models/sales_log.rb

@ -557,4 +557,8 @@ class SalesLog < Log
def is_resale?
resale == 1
end
def is_firststair?
firststair == 1
end
end

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

@ -35,6 +35,14 @@ module Validations::Sales::SaleInformationValidations
end
end
def validate_staircasing_initial_purchase_date(record)
return unless record.initialpurchase
if record.initialpurchase < Time.zone.local(1980, 1, 1)
record.errors.add :initialpurchase, I18n.t("validations.sales.sale_information.initialpurchase.must_be_after_1980")
end
end
def validate_previous_property_unit_type(record)
return unless record.fromprop && record.frombeds
@ -351,6 +359,15 @@ module Validations::Sales::SaleInformationValidations
end
end
def validate_number_of_staircase_transactions(record)
return unless record.numstair
if record.firststair == 2 && record.numstair < 2
record.errors.add :numstair, I18n.t("validations.sales.sale_information.numstair.must_be_greater_than_one")
record.errors.add :firststair, I18n.t("validations.sales.sale_information.firststair.cannot_be_no")
end
end
def over_tolerance?(expected, actual, tolerance, strict: false)
if strict
(expected - actual).abs > tolerance

4
app/views/devise/sessions/new.html.erb

@ -12,6 +12,8 @@
<%= content_for(:title) %>
</h1>
<%= render "devise/shared/links" %>
<%= f.govuk_email_field :email,
label: { text: "Email address" },
autocomplete: "email",
@ -25,5 +27,3 @@
</div>
</div>
<% end %>
<%= render "devise/shared/links" %>

2
app/views/devise/shared/_links.html.erb

@ -7,7 +7,7 @@
<% end %>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<p class="govuk-body">You can <%= govuk_link_to "reset your password", new_password_path(resource_name) %> if you’ve forgotten it.</p>
<p class="govuk-body"><%= govuk_link_to "Forgot password", new_password_path(resource_name) %></p>
<% end %>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>

5
app/views/form/_checkbox_question.html.erb

@ -6,16 +6,17 @@
hint: { text: question.hint_text&.html_safe } do %>
<% after_divider = false %>
<% question.displayed_answer_options(@log).map do |key, option| %>
<% question.displayed_answer_options(@log).each_with_index do |(key, option), index| %>
<% if key.starts_with?("divider") %>
<% after_divider = true %>
<%= f.govuk_check_box_divider %>
<% else %>
<%= f.govuk_check_box question.id, key,
<%= f.govuk_check_box question.id.to_sym, key,
label: { text: option["value"] },
hint: { text: option["hint"] },
checked: @log[key] == 1,
exclusive: after_divider,
link_errors: index.zero? ? true : nil,
**stimulus_html_attributes(question) %>
<% end %>
<% end %>

8
app/views/form/_radio_question.html.erb

@ -18,22 +18,24 @@
legend: legend(question, page_header, conditional),
hint: { text: question.hint_text&.html_safe } do %>
<% question.displayed_answer_options(@log, current_user).map do |key, options| %>
<% question.displayed_answer_options(@log, current_user).each_with_index do |(key, options), index| %>
<% if key.starts_with?("divider") %>
<%= f.govuk_radio_divider %>
<% else %>
<% conditional_question = find_conditional_question(@page, question, key) %>
<% if conditional_question.nil? %>
<%= f.govuk_radio_button question.id,
<%= f.govuk_radio_button question.id.to_sym,
key,
label: { text: options["value"] },
hint: { text: options["hint"] },
link_errors: index.zero? ? true : nil,
**stimulus_html_attributes(question) %>
<% else %>
<%= f.govuk_radio_button question.id,
<%= f.govuk_radio_button question.id.to_sym,
key,
label: { text: options["value"] },
hint: { text: options["hint"] },
link_errors: index.zero? ? true : nil,
**stimulus_html_attributes(question) do %>
<%= render partial: "#{conditional_question.type}_question", locals: {
question: conditional_question,

4
config/locales/en.yml

@ -361,7 +361,9 @@ en:
location:
postcode_blank: "Enter a postcode."
units: "The units at this location must be a number."
units:
must_be_number: "The units at this location must be a number."
must_be_one_or_more: "Number of units must be at least 1."
type_of_unit: "Select the most common type of unit at this location."
mobility_standards: "Select the mobility standard for the majority of the units at this location."
startdate_invalid: "Enter a valid day, month and year when the first property became available at this location."

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

@ -47,11 +47,38 @@ en:
check_answer_prompt: ""
hint_text: ""
question_text: "What percentage of the property does the buyer now own in total?"
staircasesale:
check_answer_label: "Part of a back-to-back staircasing transaction"
staircasesale:
page_header: ""
check_answer_label: "Part of a back-to-back staircasing transaction"
check_answer_prompt: ""
hint_text: ""
question_text: "Is this transaction part of a back-to-back staircasing transaction to facilitate sale of the home on the open market?"
firststair:
page_header: ""
check_answer_label: "First time staircasing"
check_answer_prompt: ""
hint_text: ""
question_text: "Is this the first time the shared owner has engaged in staircasing in the home?"
stairprevious:
page_header: "About previous staircasing transactions"
numstair:
check_answer_label: "Number of staircasing transactions"
check_answer_prompt: ""
hint_text: ""
question_text: "Including this time, how many times has the shared owner engaged in staircasing in the home?"
initialpurchase:
check_answer_label: "Initial staircasing transaction"
check_answer_prompt: ""
hint_text: ""
question_text: "Is this transaction part of a back-to-back staircasing transaction to facilitate sale of the home on the open market?"
question_text: "What was the date of the initial purchase of a share in the property?"
lasttransaction:
check_answer_label: "Last staircasing transaction"
check_answer_prompt: ""
hint_text: ""
question_text: "What was the date of the last staircasing transaction?"
resale:
page_header: ""
@ -118,17 +145,29 @@ en:
value:
page_header: "About the price of the property"
check_answer_label: "Full purchase price"
check_answer_prompt: ""
hint_text: "Enter the full purchase price of the property before any discounts are applied. For shared ownership, enter the full purchase price paid for 100% equity (this is equal to the value of the share owned by the PRP plus the value bought by the purchaser)"
question_text: "What was the full purchase price?"
value_shared_ownership:
check_answer_label: "Full purchase price"
check_answer_prompt: ""
hint_text: "Enter the full purchase price of the property before any discounts are applied. For shared ownership, enter the full purchase price paid for 100% equity (this is equal to the value of the share owned by the PRP plus the value bought by the purchaser)."
question_text: "What was the full purchase price?"
value_shared_ownership_staircase:
check_answer_label: "Full purchase price"
check_answer_prompt: ""
hint_text: "Enter the full purchase price paid for the equity bought in this staircasing transaction (this is equal to the value of the share bought by the purchaser)."
question_text: "What was the full purchase price for this staircasing transaction?"
equity:
page_header: "About the price of the property"
check_answer_label: "Initial percentage equity share"
check_answer_prompt: ""
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?"
initial_equity:
check_answer_label: "Initial percentage equity share"
check_answer_prompt: ""
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?"
staircase_equity:
check_answer_label: "Initial percentage equity share"
check_answer_prompt: ""
hint_text: "Enter the amount of initial equity share held by the purchaser (for example, 25% or 50%)"
question_text: "What was the percentage shared purchased in the initial transaction?"
mortgageused:
page_header: "Mortgage Amount"
@ -193,6 +232,19 @@ en:
hint_text: "Amount paid before any charges"
question_text: "What is the basic monthly rent?"
mrent_staircasing:
page_header: "Monthly rent"
prestaircasing:
check_answer_label: "Monthly rent prior to staircasing"
check_answer_prompt: ""
hint_text: "Amount paid before any charges"
question_text: "What was the basic monthly rent prior to staircasing?"
poststaircasing:
check_answer_label: "Monthly rent after staircasing"
check_answer_prompt: ""
hint_text: "Amount paid before any charges"
question_text: "What is the basic monthly rent after staircasing?"
leaseholdcharges:
page_header: ""
has_mscharge:

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

@ -19,6 +19,8 @@ en:
exdate:
must_be_before_saledate: "Contract exchange date must be before sale completion date."
must_be_less_than_1_year_from_saledate: "Contract exchange date must be less than 1 year before sale completion date."
initialpurchase:
must_be_after_1980: "The initial purchase date must be after January 1, 1980."
fromprop:
previous_property_type_bedsit: "A bedsit cannot have more than 1 bedroom."
frombeds:
@ -125,3 +127,7 @@ en:
postcode_full:
value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £136,400 for properties in London."
value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £102,400 for properties outside of London."
numstair:
must_be_greater_than_one: "The number of staircasing transactions must be greater than 1 when this is not the first staircasing transaction."
firststair:
cannot_be_no: "The answer to 'Is this the first staircasing transaction?' cannot be 'no' if the number of staircasing transactions is 1."

11
db/migrate/20241114173226_add_fields_to_sales_log.rb

@ -0,0 +1,11 @@
class AddFieldsToSalesLog < ActiveRecord::Migration[7.0]
def change
change_table :sales_logs, bulk: true do |t|
t.column :firststair, :integer
t.column :numstair, :integer
t.column :mrentprestaircasing, :decimal, precision: 10, scale: 2
t.column :lasttransaction, :datetime
t.column :initialpurchase, :datetime
end
end
end

5
db/schema.rb

@ -761,6 +761,11 @@ ActiveRecord::Schema[7.0].define(version: 2024_11_22_154743) do
t.bigint "created_by_id"
t.integer "has_management_fee"
t.decimal "management_fee", precision: 10, scale: 2
t.integer "firststair"
t.integer "numstair"
t.decimal "mrentprestaircasing", precision: 10, scale: 2
t.datetime "lasttransaction"
t.datetime "initialpurchase"
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"

4
spec/features/user_spec.rb

@ -61,7 +61,7 @@ RSpec.describe "User Features" do
context "when the user has forgotten their password" do
it "is redirected to the reset password page when they click the reset password link" do
visit("/lettings-logs")
click_link("reset your password")
click_link("Forgot password")
expect(page).to have_current_path("/account/password/new")
end
@ -744,7 +744,7 @@ RSpec.describe "User Features" do
it "is redirected to the reset password page when they click the reset password link" do
visit("/account/sign-in")
click_link("reset your password")
click_link("Forgot password")
expect(page).to have_current_path("/account/password/new")
end

4
spec/models/form/sales/pages/about_staircase_spec.rb

@ -15,6 +15,10 @@ RSpec.describe Form::Sales::Pages::AboutStaircase, type: :model do
describe "questions" do
let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date:)) }
before do
allow(subsection.form).to receive(:start_year_2025_or_later?).and_return(false)
end
context "when 2022" do
let(:start_date) { Time.utc(2022, 2, 8) }

4
spec/models/form/sales/pages/equity_spec.rb

@ -15,10 +15,6 @@ RSpec.describe Form::Sales::Pages::Equity, type: :model do
expect(page.questions.map(&:id)).to eq(%w[equity])
end
it "has the correct id" do
expect(page.id).to eq("equity")
end
it "has the correct description" do
expect(page.description).to be_nil
end

31
spec/models/form/sales/pages/monthly_rent_staircasing_owned_spec.rb

@ -0,0 +1,31 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::MonthlyRentStaircasingOwned, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[mrentprestaircasing])
end
it "has the correct id" do
expect(page.id).to eq("monthly_rent_staircasing_owned")
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([
{ "stairowned_100?" => true },
])
end
end

31
spec/models/form/sales/pages/monthly_rent_staircasing_spec.rb

@ -0,0 +1,31 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::MonthlyRentStaircasing, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[mrentprestaircasing mrent])
end
it "has the correct id" do
expect(page.id).to eq("monthly_rent_staircasing")
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([
{ "stairowned_100?" => false },
])
end
end

31
spec/models/form/sales/pages/staircase_first_time_spec.rb

@ -0,0 +1,31 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::StaircaseFirstTime, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[firststair])
end
it "has the correct id" do
expect(page.id).to eq("staircase_first_time")
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([
{ "staircase" => 1 },
])
end
end

31
spec/models/form/sales/pages/staircase_initial_date_spec.rb

@ -0,0 +1,31 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::StaircaseInitialDate, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[initialpurchase])
end
it "has the correct id" do
expect(page.id).to eq("staircase_initial_date")
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([
{ "is_firststair?" => true },
])
end
end

31
spec/models/form/sales/pages/staircase_previous_spec.rb

@ -0,0 +1,31 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::StaircasePrevious, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[numstair lasttransaction initialpurchase])
end
it "has the correct id" do
expect(page.id).to eq("staircase_previous")
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([
{ "is_firststair?" => false },
])
end
end

38
spec/models/form/sales/pages/staircase_sale_spec.rb

@ -0,0 +1,38 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::StaircaseSale, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) }
before do
allow(subsection.form).to receive(:start_year_2025_or_later?).and_return(true)
end
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[staircasesale])
end
it "has the correct id" do
expect(page.id).to eq("staircase_sale")
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([
{
"staircase" => 1,
"stairowned" => 100,
},
])
end
end

2
spec/models/form/sales/pages/value_shared_ownership_spec.rb

@ -3,7 +3,7 @@ require "rails_helper"
RSpec.describe Form::Sales::Pages::ValueSharedOwnership, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_id) { "value_shared_ownership" }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1))) }

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

@ -5,7 +5,7 @@ RSpec.describe Form::Sales::Questions::Equity, type: :model do
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) }
let(:page) { instance_double(Form::Page, id: "initial_equity", subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) }
it "has correct page" do
expect(question.page).to eq(page)

37
spec/models/form/sales/questions/monthly_rent_after_staircasing_spec.rb

@ -0,0 +1,37 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::MonthlyRentAfterStaircasing, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) }
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("mrent")
end
it "has the correct type" do
expect(question.type).to eq("numeric")
end
it "is not marked as derived" do
expect(question.derived?(nil)).to be false
end
it "has correct width" do
expect(question.width).to eq(5)
end
it "has correct prefix" do
expect(question.prefix).to eq("£")
end
it "has correct min" do
expect(question.min).to eq(0)
end
end

37
spec/models/form/sales/questions/monthly_rent_before_staircasing_spec.rb

@ -0,0 +1,37 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::MonthlyRentBeforeStaircasing, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) }
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("mrentprestaircasing")
end
it "has the correct type" do
expect(question.type).to eq("numeric")
end
it "is not marked as derived" do
expect(question.derived?(nil)).to be false
end
it "has correct width" do
expect(question.width).to eq(5)
end
it "has correct prefix" do
expect(question.prefix).to eq("£")
end
it "has correct min" do
expect(question.min).to eq(0)
end
end

98
spec/models/form/sales/questions/mortgageused_spec.rb

@ -3,28 +3,39 @@ require "rails_helper"
RSpec.describe Form::Sales::Questions::Mortgageused, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page, ownershipsch:) }
let(:ownershipsch) { 1 }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form:)) }
let(:stairowned) { nil }
let(:staircase) { nil }
let(:saledate) { Time.zone.today }
let(:log) { build(:sales_log, :in_progress, ownershipsch:, stairowned:, staircase:) }
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don’t know" },
})
end
context "when the form start year is 2024" do
let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form:)) }
let(:saledate) { Time.zone.local(2024, 5, 1) }
let(:ownershipsch) { 1 }
before do
allow(form).to receive(:start_year_2024_or_later?).and_return true
allow(form).to receive(:start_year_2025_or_later?).and_return false
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don’t know" },
})
end
describe "the displayed answer options" do
context "when it is a discounted ownership sale" do
let(:ownershipsch) { 2 }
it "shows the correct question number" do
expect(question.question_number).to eq 104
end
it "does not show the don't know option" do
expect_the_question_not_to_show_dont_know
end
@ -34,20 +45,14 @@ RSpec.describe Form::Sales::Questions::Mortgageused, type: :model do
let(:ownershipsch) { 3 }
context "and the saledate is before 24/25" do
before do
allow(form).to receive(:start_year_2024_or_later?).and_return false
end
let(:saledate) { Time.zone.local(2023, 5, 1) }\
it "does not show the don't know option" do
expect_the_question_not_to_show_dont_know
it "does show the don't know option" do
expect_the_question_to_show_dont_know
end
end
context "and the saledate is 24/25 or after" do
before do
allow(form).to receive(:start_year_2024_or_later?).and_return true
end
context "and the saledate is 24/25" do
it "shows the don't know option" do
expect_the_question_to_show_dont_know
end
@ -87,6 +92,57 @@ RSpec.describe Form::Sales::Questions::Mortgageused, type: :model do
end
end
context "when the form start year is 2025" do
let(:form) { instance_double(Form, start_date: Time.zone.local(2025, 4, 1)) }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form:)) }
let(:saledate) { Time.zone.local(2025, 5, 1) }
before do
allow(form).to receive(:start_year_2024_or_later?).and_return true
allow(form).to receive(:start_year_2025_or_later?).and_return true
end
context "when it is a discounted ownership sale" do
let(:ownershipsch) { 2 }
it "shows the correct question number" do
expect(question.question_number).to eq 104
end
it "does not show the don't know option" do
expect_the_question_not_to_show_dont_know
end
end
context "when it is a shared ownership scheme" do
let(:ownershipsch) { 1 }
context "and it is a staircasing transaction" do
let(:staircase) { 1 }
it "does show the don't know option" do
expect_the_question_to_show_dont_know
end
context "and stairowned is 100" do
let(:stairowned) { 100 }
it "shows the don't know option" do
expect_the_question_to_show_dont_know
end
end
end
context "and it is not a staircasing transaction" do
let(:staircase) { 2 }
it "does not show the don't know option" do
expect_the_question_not_to_show_dont_know
end
end
end
end
private
def expect_the_question_not_to_show_dont_know

33
spec/models/form/sales/questions/staircase_count_spec.rb

@ -0,0 +1,33 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::StaircaseCount, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1)))) }
before do
allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(true)
end
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("numstair")
end
it "has the correct type" do
expect(question.type).to eq("numeric")
end
it "is not marked as derived" do
expect(question.derived?(nil)).to be false
end
it "has correct conditional for" do
expect(question.conditional_for).to eq(nil)
end
end

40
spec/models/form/sales/questions/staircase_first_time_spec.rb

@ -0,0 +1,40 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::StaircaseFirstTime, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1)))) }
before do
allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(true)
end
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("firststair")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?(nil)).to be false
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
})
end
it "has correct conditional for" do
expect(question.conditional_for).to eq(nil)
end
end

33
spec/models/form/sales/questions/staircase_initial_date_spec.rb

@ -0,0 +1,33 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::StaircaseInitialDate, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1)))) }
before do
allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(true)
end
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("initialpurchase")
end
it "has the correct type" do
expect(question.type).to eq("date")
end
it "is not marked as derived" do
expect(question.derived?(nil)).to be false
end
it "has correct conditional for" do
expect(question.conditional_for).to eq(nil)
end
end

33
spec/models/form/sales/questions/staircase_last_date_spec.rb

@ -0,0 +1,33 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::StaircaseLastDate, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1)))) }
before do
allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(true)
end
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("lasttransaction")
end
it "has the correct type" do
expect(question.type).to eq("date")
end
it "is not marked as derived" do
expect(question.derived?(nil)).to be false
end
it "has correct conditional for" do
expect(question.conditional_for).to eq(nil)
end
end

4
spec/models/form/sales/questions/staircase_sale_spec.rb

@ -7,6 +7,10 @@ RSpec.describe Form::Sales::Questions::StaircaseSale, type: :model do
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) }
before do
allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(false)
end
it "has correct page" do
expect(question.page).to eq(page)
end

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

@ -5,7 +5,11 @@ RSpec.describe Form::Sales::Questions::Value, type: :model do
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) }
let(:page) { instance_double(Form::Page, id: "value_shared_ownership", subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) }
before do
allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(false)
end
it "has correct page" do
expect(question.page).to eq(page)

11
spec/models/form/sales/sections/sale_information_spec.rb

@ -7,6 +7,10 @@ RSpec.describe Form::Sales::Sections::SaleInformation, type: :model do
let(:section_definition) { nil }
let(:form) { instance_double(Form, start_year_2025_or_later?: false) }
before do
allow(form).to receive(:start_year_2025_or_later?).and_return(false)
end
it "has correct form" do
expect(sale_information.form).to eq(form)
end
@ -22,11 +26,16 @@ RSpec.describe Form::Sales::Sections::SaleInformation, type: :model do
end
context "when form is 2025 or later" do
let(:form) { instance_double(Form, start_year_2025_or_later?: true) }
let(:form) { instance_double(Form) }
before do
allow(form).to receive(:start_year_2025_or_later?).and_return(true)
end
it "has correct subsections" do
expect(sale_information.subsections.map(&:id)).to eq(%w[
shared_ownership_initial_purchase
shared_ownership_staircasing_transaction
discounted_ownership_scheme
outright_sale
])

2
spec/models/form/sales/subsections/shared_ownership_initial_purchase_spec.rb

@ -30,7 +30,7 @@ RSpec.describe Form::Sales::Subsections::SharedOwnershipInitialPurchase, type: :
shared_ownership_previous_tenure
value_shared_ownership
about_price_shared_ownership_value_check
equity
initial_equity
shared_ownership_equity_value_check
mortgage_used_shared_ownership
mortgage_used_mortgage_value_check

36
spec/models/location_spec.rb

@ -740,10 +740,38 @@ RSpec.describe Location, type: :model do
describe "#units" do
let(:location) { FactoryBot.build(:location) }
it "does add an error when the number of units is invalid" do
location.units = nil
location.valid?(:units)
expect(location.errors.count).to eq(1)
context "when the number of units is invalid" do
it "adds an error when units is nil" do
location.units = nil
location.valid?(:units)
expect(location.errors.count).to eq(2)
end
it "adds an error when units is 0" do
location.units = 0
location.valid?(:units)
expect(location.errors.count).to eq(1)
end
end
context "when the number of units is valid" do
it "does not add an error when units is 1" do
location.units = 1
location.valid?(:units)
expect(location.errors.count).to eq(0)
end
it "does not add an error when units is 10" do
location.units = 10
location.valid?(:units)
expect(location.errors.count).to eq(0)
end
it "does not add an error when units is 200" do
location.units = 200
location.valid?(:units)
expect(location.errors.count).to eq(0)
end
end
end

40
spec/models/validations/sales/sale_information_validations_spec.rb

@ -1407,4 +1407,44 @@ RSpec.describe Validations::Sales::SaleInformationValidations do
end
end
end
describe "#validate_number_of_staircase_transactions" do
let(:record) { build(:sales_log, numstair:, firststair:) }
before do
sale_information_validator.validate_number_of_staircase_transactions(record)
end
context "when it is not the first staircasing transaction" do
context "and the number of staircasing transactions is between 2 and 10" do
let(:numstair) { 6 }
let(:firststair) { 2 }
it "does not add an error" do
expect(record.errors).to be_empty
end
end
context "and the number of staircasing transactions is less than 2" do
let(:numstair) { 1 }
let(:firststair) { 2 }
it "adds an error" do
expect(record.errors[:numstair]).to include(I18n.t("validations.sales.sale_information.numstair.must_be_greater_than_one"))
expect(record.errors[:firststair]).to include(I18n.t("validations.sales.sale_information.firststair.cannot_be_no"))
end
end
end
context "when it is the first staircasing transaction" do
context "and numstair is also 1" do
let(:numstair) { 1 }
let(:firststair) { 1 }
it "does not add an error" do
expect(record.errors).to be_empty
end
end
end
end
end

2
spec/models/validations/setup_validations_spec.rb

@ -703,7 +703,7 @@ RSpec.describe Validations::SetupValidations do
.to include("This location is incomplete. Select another location or update this one.")
end
it "produces no error when location is completes" do
it "produces no error when location is complete" do
location.update!(units: 1)
location.reload
record.location = location

11
spec/requests/form_controller_spec.rb

@ -582,8 +582,9 @@ RSpec.describe FormController, type: :request do
allow(Rails.logger).to receive(:info)
end
it "re-renders the same page with errors if validation fails" do
it "redirects to the same page with errors if validation fails" do
post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params
follow_redirect!
expect(page).to have_content("There is a problem")
expect(page).to have_content("Error: What is the tenant’s age?")
end
@ -591,6 +592,8 @@ RSpec.describe FormController, type: :request do
it "resets errors when fixed" do
post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params
post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: valid_params
follow_redirect!
expect(page).not_to have_content("There is a problem")
get "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}"
expect(page).not_to have_content("There is a problem")
end
@ -616,6 +619,7 @@ RSpec.describe FormController, type: :request do
it "validates the date correctly" do
post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params
follow_redirect!
expect(page).to have_content("There is a problem")
end
end
@ -693,6 +697,7 @@ RSpec.describe FormController, type: :request do
it "validates the date correctly" do
post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params
follow_redirect!
expect(page).to have_content("There is a problem")
end
end
@ -713,6 +718,7 @@ RSpec.describe FormController, type: :request do
it "validates the date correctly" do
post "/sales-logs/#{sales_log.id}/#{page_id.dasherize}", params: params
follow_redirect!
expect(page).to have_content("There is a problem")
end
end
@ -748,8 +754,9 @@ RSpec.describe FormController, type: :request do
Timecop.unfreeze
end
it "re-renders the same page with errors if validation fails" do
it "redirects the same page with errors if validation fails" do
post "/lettings-logs/#{lettings_log.id}/managing-organisation", params: params
follow_redirect!
expect(page).to have_content("There is a problem")
expect(page).to have_content("Error: Which organisation manages this letting?")
end

Loading…
Cancel
Save