Browse Source

CLDC-4167, CLDC-4168: Add don't know option to mortgage length questions (#3195)

* CLDC-4168: Add check for mortgage length known

* CLDC-4168: Add mortgage length known to question page

* CLDC-4168: Make mortgage length conditional on interview

requires a little changing to question logic to account for if the conditional question is not visible right now

* CLDC-4168: Add R override to BU

* CLDC-4168: Enforce mortlen is not R if buyer not interviewed in BU

* CLDC-4168: Update tests

* CLDC-4168: Add a lint task that autocorrects

* CLDC-4168: Add mortgage length known to sales log factory

* CLDC-4168: Allow mortgage not know if buyer wasn't interviewed

previously had this the wrong way around. this makes sense because if you interviewed the buyer you should know this

* CLDC-4168: Rename mortgage_length_known to mortlen_known

matches the mortlen field

* fixup! CLDC-4168: Add mortgage length known to question page

remove old year numbers

* fixup! CLDC-4168: Rename mortgage_length_known to mortlen_known

* fixup! CLDC-4168: Allow mortgage not know if buyer wasn't interviewed

missed test fix

* CLDC-4168: Revert changes to outright_sale section

* fixup! CLDC-4168: Revert changes to outright_sale section

* fixup! CLDC-4168: Update tests

* CLDC-4168: Allow for "r" in mortlen
CLDC-4160-sentry-migration v0.6.3
Samuel Young 2 weeks ago committed by GitHub
parent
commit
8e7e9f1b71
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      app/models/derived_variables/sales_log_variables.rb
  2. 5
      app/models/form/question.rb
  3. 15
      app/models/form/sales/pages/mortgage_length_interviewed.rb
  4. 16
      app/models/form/sales/pages/mortgage_length_not_interviewed.rb
  5. 1
      app/models/form/sales/questions/mortgage_length.rb
  6. 21
      app/models/form/sales/questions/mortgage_length_known.rb
  7. 4
      app/models/form/sales/subsections/discounted_ownership_scheme.rb
  8. 2
      app/models/form/sales/subsections/outright_sale.rb
  9. 4
      app/models/form/sales/subsections/shared_ownership_initial_purchase.rb
  10. 4
      app/models/sales_log.rb
  11. 54
      app/services/bulk_upload/sales/year2026/row_parser.rb
  12. 7
      config/locales/forms/2026/sales/sale_information.en.yml
  13. 3
      config/locales/validations/sales/2026/bulk_upload.en.yml
  14. 5
      db/migrate/20260224141705_add_mortlen_known_to_sales_logs.rb
  15. 3
      db/schema.rb
  16. 5
      lib/tasks/lint.rake
  17. 1
      spec/factories/sales_log.rb
  18. 122
      spec/models/form/sales/subsections/discounted_ownership_scheme_spec.rb
  19. 101
      spec/models/form/sales/subsections/shared_ownership_initial_purchase_spec.rb
  20. 122
      spec/services/bulk_upload/sales/year2026/row_parser_spec.rb

4
app/models/derived_variables/sales_log_variables.rb

@ -98,6 +98,10 @@ module DerivedVariables::SalesLogVariables
self.numstair = is_firststair? ? 1 : nil if numstair == 1 && firststair_changed? self.numstair = is_firststair? ? 1 : nil if numstair == 1 && firststair_changed?
self.mrent = 0 if stairowned_100? self.mrent = 0 if stairowned_100?
if buyer_interviewed_changed_to_not_interviewed_and_mortlen_set?
self.mortlen_known = 0
end
set_encoded_derived_values!(DEPENDENCIES) set_encoded_derived_values!(DEPENDENCIES)
end end

5
app/models/form/question.rb

@ -340,7 +340,10 @@ private
end end
def evaluate_condition(condition, log) def evaluate_condition(condition, log)
case page.questions.find { |q| q.id == condition[:from] }.type conditional_question = page.questions.find { |q| q.id == condition[:from] }
return true unless conditional_question
case conditional_question.type
when "numeric" when "numeric"
operator = condition[:cond][/[<>=]+/].to_sym operator = condition[:cond][/[<>=]+/].to_sym
operand = condition[:cond][/\d+/].to_i operand = condition[:cond][/\d+/].to_i

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

@ -0,0 +1,15 @@
class Form::Sales::Pages::MortgageLengthInterviewed < ::Form::Page
def initialize(id, hsh, subsection, ownershipsch:)
super(id, hsh, subsection)
@ownershipsch = ownershipsch
@depends_on = [{
"mortgageused" => 1, "buyer_not_interviewed?" => false
}]
end
def questions
@questions ||= [
Form::Sales::Questions::MortgageLength.new(nil, nil, self, ownershipsch: @ownershipsch),
]
end
end

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

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

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

@ -19,5 +19,6 @@ class Form::Sales::Questions::MortgageLength < ::Form::Question
2023 => { 1 => 93, 2 => 106, 3 => 114 }, 2023 => { 1 => 93, 2 => 106, 3 => 114 },
2024 => { 1 => 94, 2 => 107, 3 => 114 }, 2024 => { 1 => 94, 2 => 107, 3 => 114 },
2025 => { 1 => 84, 2 => 108 }, 2025 => { 1 => 84, 2 => 108 },
2026 => { 1 => 84, 2 => 108 },
}.freeze }.freeze
end end

21
app/models/form/sales/questions/mortgage_length_known.rb

@ -0,0 +1,21 @@
class Form::Sales::Questions::MortgageLengthKnown < ::Form::Question
def initialize(id, hsh, page, ownershipsch:)
super(id, hsh, page)
@id = "mortlen_known"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@conditional_for = { "mortlen" => [0] }
@hidden_in_check_answers = {
"depends_on" => [
{ "mortlen_known" => 0 },
],
}
@question_number = QUESTION_NUMBER_FROM_YEAR_AND_OWNERSHIP.fetch(form.start_date.year, QUESTION_NUMBER_FROM_YEAR_AND_OWNERSHIP.max_by { |k, _v| k }.last)[ownershipsch]
end
ANSWER_OPTIONS = { "0" => { "value" => "Yes" }, "1" => { "value" => "No" } }.freeze
QUESTION_NUMBER_FROM_YEAR_AND_OWNERSHIP = {
2026 => { 1 => 84, 2 => 108 },
}.freeze
end

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

@ -30,7 +30,9 @@ class Form::Sales::Subsections::DiscountedOwnershipScheme < ::Form::Subsection
Form::Sales::Pages::ExtraBorrowingValueCheck.new("extra_borrowing_mortgage_value_check", nil, self), Form::Sales::Pages::ExtraBorrowingValueCheck.new("extra_borrowing_mortgage_value_check", nil, self),
Form::Sales::Pages::DepositAndMortgageValueCheck.new("discounted_ownership_deposit_and_mortgage_value_check_after_mortgage", nil, self), Form::Sales::Pages::DepositAndMortgageValueCheck.new("discounted_ownership_deposit_and_mortgage_value_check_after_mortgage", nil, self),
mortgage_lender_questions, mortgage_lender_questions,
Form::Sales::Pages::MortgageLength.new("mortgage_length_discounted_ownership", nil, self, ownershipsch: 2), (Form::Sales::Pages::MortgageLength.new("mortgage_length_discounted_ownership", nil, self, ownershipsch: 2) unless form.start_year_2026_or_later?),
(Form::Sales::Pages::MortgageLengthNotInterviewed.new("mortgage_length_discounted_ownership_not_interviewed", nil, self, ownershipsch: 2) if form.start_year_2026_or_later?),
(Form::Sales::Pages::MortgageLengthInterviewed.new("mortgage_length_discounted_ownership_interviewed", nil, self, ownershipsch: 2) if form.start_year_2026_or_later?),
Form::Sales::Pages::ExtraBorrowing.new("extra_borrowing_discounted_ownership", nil, self, ownershipsch: 2), Form::Sales::Pages::ExtraBorrowing.new("extra_borrowing_discounted_ownership", nil, self, ownershipsch: 2),
Form::Sales::Pages::ExtraBorrowingValueCheck.new("extra_borrowing_value_check", nil, self), Form::Sales::Pages::ExtraBorrowingValueCheck.new("extra_borrowing_value_check", nil, self),
Form::Sales::Pages::Deposit.new("deposit_discounted_ownership", nil, self, ownershipsch: 2, optional: false), Form::Sales::Pages::Deposit.new("deposit_discounted_ownership", nil, self, ownershipsch: 2, optional: false),

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

@ -1,3 +1,5 @@
# NOTE: ownershipsch of 3 was last possible in 2024
# for 2025 logs and later this section is not used
class Form::Sales::Subsections::OutrightSale < ::Form::Subsection class Form::Sales::Subsections::OutrightSale < ::Form::Subsection
def initialize(id, hsh, section) def initialize(id, hsh, section)
super super

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

@ -28,7 +28,9 @@ class Form::Sales::Subsections::SharedOwnershipInitialPurchase < ::Form::Subsect
Form::Sales::Pages::MortgageAmount.new("mortgage_amount_shared_ownership", nil, self, ownershipsch: 1), 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::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::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::MortgageLength.new("mortgage_length_shared_ownership", nil, self, ownershipsch: 1) unless form.start_year_2026_or_later?),
(Form::Sales::Pages::MortgageLengthNotInterviewed.new("mortgage_length_shared_ownership_not_interviewed", nil, self, ownershipsch: 1) if form.start_year_2026_or_later?),
(Form::Sales::Pages::MortgageLengthInterviewed.new("mortgage_length_shared_ownership_interviewed", nil, self, ownershipsch: 1) if form.start_year_2026_or_later?),
Form::Sales::Pages::Deposit.new("deposit_shared_ownership", nil, self, ownershipsch: 1, optional: false), 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::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_joint_purchase_value_check", nil, self, joint_purchase: true),

4
app/models/sales_log.rb

@ -390,6 +390,10 @@ class SalesLog < Log
proptype_changed? && proptype_was == 2 proptype_changed? && proptype_was == 2
end end
def buyer_interviewed_changed_to_not_interviewed_and_mortlen_set?
noint_changed? && noint_was == 2 && buyer_not_interviewed? && mortlen.present?
end
def shared_ownership_scheme? def shared_ownership_scheme?
ownershipsch == 1 ownershipsch == 1
end end

54
app/services/bulk_upload/sales/year2026/row_parser.rb

@ -161,6 +161,9 @@ class BulkUpload::Sales::Year2026::RowParser
:field_75, # What is the total amount the buyers had in savings before they paid any deposit for the property? :field_75, # What is the total amount the buyers had in savings before they paid any deposit for the property?
:field_70, # What is buyer 1’s gross annual income? :field_70, # What is buyer 1’s gross annual income?
:field_72, # What is buyer 2’s gross annual income? :field_72, # What is buyer 2’s gross annual income?
:field_90, # What is the length of the mortgage in years? - Shared ownership
:field_118, # What is the length of the mortgage in years? - Discounted ownership
].freeze ].freeze
attribute :bulk_upload attribute :bulk_upload
@ -264,7 +267,7 @@ class BulkUpload::Sales::Year2026::RowParser
attribute :field_87, :decimal attribute :field_87, :decimal
attribute :field_88, :integer attribute :field_88, :integer
attribute :field_89, :decimal attribute :field_89, :decimal
attribute :field_90, :integer attribute :field_90, :string
attribute :field_91, :decimal attribute :field_91, :decimal
attribute :field_92, :decimal attribute :field_92, :decimal
attribute :field_93, :decimal attribute :field_93, :decimal
@ -294,7 +297,7 @@ class BulkUpload::Sales::Year2026::RowParser
attribute :field_115, :decimal attribute :field_115, :decimal
attribute :field_116, :integer attribute :field_116, :integer
attribute :field_117, :decimal attribute :field_117, :decimal
attribute :field_118, :integer attribute :field_118, :string
attribute :field_119, :integer attribute :field_119, :integer
attribute :field_120, :decimal attribute :field_120, :decimal
attribute :field_121, :decimal attribute :field_121, :decimal
@ -427,6 +430,22 @@ class BulkUpload::Sales::Year2026::RowParser
}, },
on: :before_log on: :before_log
validates :field_90,
if: :shared_ownership?,
format: {
with: /\A(\d+|R)\z/,
message: I18n.t("#{ERROR_BASE_KEY}.mortlen.invalid"),
},
on: :after_log
validates :field_118,
if: :discounted_ownership?,
format: {
with: /\A(\d+|R)\z/,
message: I18n.t("#{ERROR_BASE_KEY}.mortlen.invalid"),
},
on: :after_log
validate :validate_buyer1_economic_status, on: :before_log validate :validate_buyer1_economic_status, on: :before_log
validate :validate_buyer2_economic_status, on: :before_log validate :validate_buyer2_economic_status, on: :before_log
validate :validate_valid_radio_option, on: :before_log validate :validate_valid_radio_option, on: :before_log
@ -449,6 +468,7 @@ class BulkUpload::Sales::Year2026::RowParser
validate :validate_nationality, on: :after_log validate :validate_nationality, on: :after_log
validate :validate_buyer_2_nationality, on: :after_log validate :validate_buyer_2_nationality, on: :after_log
validate :validate_mortlen_field_if_buyer_interviewed, on: :after_log
validate :validate_nulls, on: :after_log validate :validate_nulls, on: :after_log
@ -674,6 +694,10 @@ private
field_99 == 1 field_99 == 1
end end
def buyer_interviewed?
field_14 == 2
end
def rtb_like_sale_type? def rtb_like_sale_type?
[9, 14, 27, 29].include?(field_11) [9, 14, 27, 29].include?(field_11)
end end
@ -767,6 +791,7 @@ private
hb: %i[field_74], hb: %i[field_74],
mortlen: mortlen_fields, mortlen: mortlen_fields,
mortlen_known: mortlen_fields,
proplen: proplen_fields, proplen: proplen_fields,
jointmore: %i[field_13], jointmore: %i[field_13],
@ -938,7 +963,8 @@ private
attributes["hb"] = field_74 attributes["hb"] = field_74
attributes["mortlen"] = mortlen attributes["mortlen"] = mortlen != "R" ? mortlen : nil
attributes["mortlen_known"] = mortlen_known
attributes["proplen"] = proplen if proplen&.positive? attributes["proplen"] = proplen if proplen&.positive?
attributes["proplen_asked"] = attributes["proplen"].present? ? 0 : 1 attributes["proplen_asked"] = attributes["proplen"].present? ? 0 : 1
@ -1163,6 +1189,16 @@ private
field_118 if discounted_ownership? field_118 if discounted_ownership?
end end
def mortlen_known
return nil if buyer_interviewed?
if mortlen == "R"
1
else
0
end
end
def proplen def proplen
return field_79 if shared_ownership? return field_79 if shared_ownership?
@ -1528,14 +1564,10 @@ private
%w[0] + GlobalConstants::COUNTRIES_ANSWER_OPTIONS.keys # 0 is "Prefers not to say" %w[0] + GlobalConstants::COUNTRIES_ANSWER_OPTIONS.keys # 0 is "Prefers not to say"
end end
def validate_relat_fields def validate_mortlen_field_if_buyer_interviewed
%i[field_34 field_42 field_46 field_50 field_54].each do |field| if buyer_interviewed? && mortlen == "R"
value = send(field) errors.add(:field_90, I18n.t("#{ERROR_BASE_KEY}.mortlen.invalid_for_interviewed")) if shared_ownership?
next if value.blank? errors.add(:field_118, I18n.t("#{ERROR_BASE_KEY}.mortlen.invalid_for_interviewed")) if discounted_ownership?
unless (1..3).cover?(value)
errors.add(field, I18n.t("#{ERROR_BASE_KEY}.invalid_option", question: format_ending(QUESTIONS[field])))
end
end end
end end

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

@ -204,6 +204,13 @@ en:
hint_text: "You should round up to the nearest year. Value should not exceed 60 years." hint_text: "You should round up to the nearest year. Value should not exceed 60 years."
question_text: "What is the length of the mortgage?" question_text: "What is the length of the mortgage?"
mortlen_known:
page_header: ""
check_answer_label: "Length of mortgage known"
check_answer_prompt: ""
hint_text: ""
question_text: "Do you know the length of the mortgage?"
extrabor: extrabor:
page_header: "" page_header: ""
check_answer_label: "Any other borrowing" check_answer_label: "Any other borrowing"

3
config/locales/validations/sales/2026/bulk_upload.en.yml

@ -44,3 +44,6 @@ en:
not_answered: "Enter either the UPRN or the full address." not_answered: "Enter either the UPRN or the full address."
nationality: nationality:
invalid: "Select a valid nationality." invalid: "Select a valid nationality."
mortlen:
invalid: "Mortgage length must be a number or the letter R"
invalid_for_interviewed: "You indicated that the buyer was interviewed, but selected “Don’t know” for mortgage length. Please provide the mortgage length or update your response."

5
db/migrate/20260224141705_add_mortlen_known_to_sales_logs.rb

@ -0,0 +1,5 @@
class AddMortlenKnownToSalesLogs < ActiveRecord::Migration[7.2]
def change
add_column :sales_logs, :mortlen_known, :integer
end
end

3
db/schema.rb

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.2].define(version: 2026_02_25_162121) do ActiveRecord::Schema[7.2].define(version: 2026_02_25_135309) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -824,6 +824,7 @@ ActiveRecord::Schema[7.2].define(version: 2026_02_25_162121) do
t.string "sexrab4" t.string "sexrab4"
t.string "sexrab5" t.string "sexrab5"
t.string "sexrab6" t.string "sexrab6"
t.integer "mortlen_known"
t.integer "buildheightclass" t.integer "buildheightclass"
t.index ["assigned_to_id"], name: "index_sales_logs_on_assigned_to_id" 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 ["bulk_upload_id"], name: "index_sales_logs_on_bulk_upload_id"

5
lib/tasks/lint.rake

@ -3,6 +3,11 @@ task rubocop: :environment do
sh "bundle exec rubocop" sh "bundle exec rubocop"
end end
desc "Run Rubocop Autocorrect"
task rubocop_autocorrect: :environment do
sh "bundle exec rubocop -A"
end
desc "Run ERB Lint" desc "Run ERB Lint"
task erblint: :environment do task erblint: :environment do
sh "bundle exec erb_lint --lint-all" sh "bundle exec erb_lint --lint-all"

1
spec/factories/sales_log.rb

@ -167,6 +167,7 @@ FactoryBot.define do
has_mscharge { 1 } has_mscharge { 1 }
mscharge { 100 } mscharge { 100 }
mortlen { 10 } mortlen { 10 }
mortlen_known { 0 }
pcodenk { 0 } pcodenk { 0 }
postcode_full { "SW1A 1AA" } postcode_full { "SW1A 1AA" }
is_la_inferred { false } is_la_inferred { false }

122
spec/models/form/sales/subsections/discounted_ownership_scheme_spec.rb

@ -1,17 +1,61 @@
require "rails_helper" require "rails_helper"
RSpec.describe Form::Sales::Subsections::DiscountedOwnershipScheme, type: :model do RSpec.describe Form::Sales::Subsections::DiscountedOwnershipScheme, type: :model do
include CollectionTimeHelper
subject(:discounted_ownership_scheme) { described_class.new(subsection_id, subsection_definition, section) } subject(:discounted_ownership_scheme) { described_class.new(subsection_id, subsection_definition, section) }
let(:subsection_id) { nil } let(:subsection_id) { nil }
let(:subsection_definition) { nil } let(:subsection_definition) { nil }
let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1), start_year_2025_or_later?: false) } let(:start_year_2025_or_later?) { true }
let(:start_year_2026_or_later?) { true }
let(:form) { instance_double(Form, start_date: current_collection_start_date, start_year_2025_or_later?: start_year_2025_or_later?, start_year_2026_or_later?: start_year_2026_or_later?) }
let(:section) { instance_double(Form::Sales::Sections::SaleInformation, form:) } let(:section) { instance_double(Form::Sales::Sections::SaleInformation, form:) }
it "has correct section" do it "has correct section" do
expect(discounted_ownership_scheme.section).to eq(section) expect(discounted_ownership_scheme.section).to eq(section)
end end
it "has the correct id" do
expect(discounted_ownership_scheme.id).to eq("discounted_ownership_scheme")
end
it "has the correct copy key" do
expect(discounted_ownership_scheme.copy_key).to eq("sale_information")
end
it "has the correct label" do
expect(discounted_ownership_scheme.label).to eq("Discounted ownership scheme")
end
it "has the correct depends_on" do
expect(discounted_ownership_scheme.depends_on).to eq([
{
"ownershipsch" => 2, "setup_completed?" => true
},
])
end
context "when it is a discounted ownership scheme" do
let(:log) { FactoryBot.build(:sales_log, ownershipsch: 2) }
it "is displayed in tasklist" do
expect(discounted_ownership_scheme.displayed_in_tasklist?(log)).to be(true)
end
end
context "when it is not a discounted ownership scheme" do
let(:log) { FactoryBot.build(:sales_log, ownershipsch: 1) }
it "is displayed in tasklist" do
expect(discounted_ownership_scheme.displayed_in_tasklist?(log)).to be(false)
end
end
context "when 2024", metadata: { year: 24 } do
let(:start_year_2025_or_later?) { false }
let(:start_year_2026_or_later?) { false }
it "has correct pages" do it "has correct pages" do
expect(discounted_ownership_scheme.pages.map(&:id)).to eq( expect(discounted_ownership_scheme.pages.map(&:id)).to eq(
%w[ %w[
@ -51,46 +95,51 @@ RSpec.describe Form::Sales::Subsections::DiscountedOwnershipScheme, type: :model
], ],
) )
end end
it "has the correct id" do
expect(discounted_ownership_scheme.id).to eq("discounted_ownership_scheme")
end end
it "has the correct copy key" do context "when 2025", metadata: { year: 25 } do
expect(discounted_ownership_scheme.copy_key).to eq("sale_information") let(:start_year_2026_or_later?) { false }
end
it "has the correct label" do
expect(discounted_ownership_scheme.label).to eq("Discounted ownership scheme")
end
it "has the correct depends_on" do it "has correct pages" do
expect(discounted_ownership_scheme.depends_on).to eq([ expect(discounted_ownership_scheme.pages.map(&:id)).to eq(
{ %w[
"ownershipsch" => 2, "setup_completed?" => true living_before_purchase_discounted_ownership_joint_purchase
}, living_before_purchase_discounted_ownership
]) purchase_price
end discount
extra_borrowing_price_value_check
context "when it is a discounted ownership scheme" do percentage_discount_value_check
let(:log) { FactoryBot.build(:sales_log, ownershipsch: 2) } grant
grant_value_check
it "is displayed in tasklist" do purchase_price_discounted_ownership
expect(discounted_ownership_scheme.displayed_in_tasklist?(log)).to be(true) discounted_sale_grant_value_check
end about_price_discounted_ownership_value_check
end discounted_ownership_deposit_and_mortgage_value_check_after_value_and_discount
mortgage_used_discounted_ownership
context "when it is not a discounted ownership scheme" do discounted_ownership_mortgage_used_mortgage_value_check
let(:log) { FactoryBot.build(:sales_log, ownershipsch: 1) } discounted_sale_mortgage_used_value_check
mortgage_amount_discounted_ownership
it "is displayed in tasklist" do discounted_ownership_mortgage_amount_mortgage_value_check
expect(discounted_ownership_scheme.displayed_in_tasklist?(log)).to be(false) discounted_sale_mortgage_value_check
extra_borrowing_mortgage_value_check
discounted_ownership_deposit_and_mortgage_value_check_after_mortgage
mortgage_length_discounted_ownership
extra_borrowing_discounted_ownership
extra_borrowing_value_check
deposit_discounted_ownership
extra_borrowing_deposit_value_check
discounted_ownership_deposit_joint_purchase_value_check
discounted_ownership_deposit_value_check
discounted_ownership_deposit_and_mortgage_value_check_after_deposit
discounted_sale_deposit_value_check
leasehold_charges_discounted_ownership
monthly_charges_discounted_ownership_value_check
],
)
end end
end end
context "with form on or after 2025" do context "when 2026", metadata: { year: 26 } do
let(:form) { instance_double(Form, start_date: Time.zone.local(2025, 4, 1), start_year_2025_or_later?: true) }
it "has correct pages" do it "has correct pages" do
expect(discounted_ownership_scheme.pages.map(&:id)).to eq( expect(discounted_ownership_scheme.pages.map(&:id)).to eq(
%w[ %w[
@ -114,7 +163,8 @@ RSpec.describe Form::Sales::Subsections::DiscountedOwnershipScheme, type: :model
discounted_sale_mortgage_value_check discounted_sale_mortgage_value_check
extra_borrowing_mortgage_value_check extra_borrowing_mortgage_value_check
discounted_ownership_deposit_and_mortgage_value_check_after_mortgage discounted_ownership_deposit_and_mortgage_value_check_after_mortgage
mortgage_length_discounted_ownership mortgage_length_discounted_ownership_not_interviewed
mortgage_length_discounted_ownership_interviewed
extra_borrowing_discounted_ownership extra_borrowing_discounted_ownership
extra_borrowing_value_check extra_borrowing_value_check
deposit_discounted_ownership deposit_discounted_ownership

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

@ -1,20 +1,75 @@
require "rails_helper" require "rails_helper"
RSpec.describe Form::Sales::Subsections::SharedOwnershipInitialPurchase, type: :model do RSpec.describe Form::Sales::Subsections::SharedOwnershipInitialPurchase, type: :model do
include CollectionTimeHelper
subject(:shared_ownership_initial_purchase) { described_class.new(subsection_id, subsection_definition, section) } subject(:shared_ownership_initial_purchase) { described_class.new(subsection_id, subsection_definition, section) }
let(:subsection_id) { nil } let(:subsection_id) { nil }
let(:subsection_definition) { nil } let(:subsection_definition) { nil }
let(:start_year_2024_or_later?) { true }
let(:start_year_2025_or_later?) { true }
let(:start_year_2026_or_later?) { true }
let(:start_date) { current_collection_start_date }
let(:form) { instance_double(Form, start_date:, start_year_2024_or_later?: start_year_2024_or_later?, start_year_2025_or_later?: start_year_2025_or_later?, start_year_2026_or_later?: start_year_2026_or_later?) }
let(:section) { instance_double(Form::Sales::Sections::SaleInformation) } let(:section) { instance_double(Form::Sales::Sections::SaleInformation) }
before do before do
allow(section).to receive(:form).and_return(instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) allow(section).to receive(:form).and_return(form)
end end
it "has correct section" do it "has correct section" do
expect(shared_ownership_initial_purchase.section).to eq(section) expect(shared_ownership_initial_purchase.section).to eq(section)
end end
context "when 2024", metadata: { year: 24 } do
let(:start_year_2025_or_later?) { false }
let(:start_year_2026_or_later?) { false }
let(:start_date) { collection_start_date_for_year(2024) }
it "has correct pages" do
expect(shared_ownership_initial_purchase.pages.map(&:id)).to eq(
%w[
resale
living_before_purchase_shared_ownership_joint_purchase
living_before_purchase_shared_ownership
handover_date
handover_date_check
buyer_previous_joint_purchase
buyer_previous_not_joint_purchase
previous_bedrooms
previous_property_type
shared_ownership_previous_tenure
value_shared_ownership
about_price_shared_ownership_value_check
initial_equity
shared_ownership_equity_value_check
mortgage_used_shared_ownership
mortgage_used_mortgage_value_check
mortgage_amount_shared_ownership
shared_ownership_mortgage_amount_value_check
mortgage_amount_mortgage_value_check
mortgage_length_shared_ownership
deposit_shared_ownership
deposit_shared_ownership_optional
deposit_joint_purchase_value_check
deposit_value_check
deposit_discount
deposit_discount_optional
shared_ownership_deposit_value_check
monthly_rent
service_charge
monthly_charges_shared_ownership_value_check
estate_management_fee
],
)
end
end
context "when 2025", metadata: { year: 25 } do
let(:start_year_2026_or_later?) { false }
let(:start_date) { collection_start_date_for_year(2025) }
it "has correct pages" do it "has correct pages" do
expect(shared_ownership_initial_purchase.pages.map(&:id)).to eq( expect(shared_ownership_initial_purchase.pages.map(&:id)).to eq(
%w[ %w[
@ -52,6 +107,50 @@ RSpec.describe Form::Sales::Subsections::SharedOwnershipInitialPurchase, type: :
], ],
) )
end end
end
context "when 2026", metadata: { year: 26 } do
let(:start_date) { collection_start_date_for_year(2026) }
it "has correct pages" do
expect(shared_ownership_initial_purchase.pages.map(&:id)).to eq(
%w[
resale
living_before_purchase_shared_ownership_joint_purchase
living_before_purchase_shared_ownership
handover_date
handover_date_check
buyer_previous_joint_purchase
buyer_previous_not_joint_purchase
previous_bedrooms
previous_property_type
shared_ownership_previous_tenure
value_shared_ownership
about_price_shared_ownership_value_check
initial_equity
shared_ownership_equity_value_check
mortgage_used_shared_ownership
mortgage_used_mortgage_value_check
mortgage_amount_shared_ownership
shared_ownership_mortgage_amount_value_check
mortgage_amount_mortgage_value_check
mortgage_length_shared_ownership_not_interviewed
mortgage_length_shared_ownership_interviewed
deposit_shared_ownership
deposit_shared_ownership_optional
deposit_joint_purchase_value_check
deposit_value_check
deposit_discount
deposit_discount_optional
shared_ownership_deposit_value_check
monthly_rent
service_charge
monthly_charges_shared_ownership_value_check
estate_management_fee
],
)
end
end
it "has the correct id" do it "has the correct id" do
expect(shared_ownership_initial_purchase.id).to eq("shared_ownership_initial_purchase") expect(shared_ownership_initial_purchase.id).to eq("shared_ownership_initial_purchase")

122
spec/services/bulk_upload/sales/year2026/row_parser_spec.rb

@ -296,7 +296,7 @@ RSpec.describe BulkUpload::Sales::Year2026::RowParser do
context "and case insensitive fields are set to lowercase" do context "and case insensitive fields are set to lowercase" do
let(:case_insensitive_fields) { %w[field_29 field_36 field_44 field_48 field_52 field_56] } let(:case_insensitive_fields) { %w[field_29 field_36 field_44 field_48 field_52 field_56] }
let(:case_insensitive_integer_fields_with_r_option) { %w[field_28 field_35 field_43 field_47 field_51 field_55 field_64 field_75 field_70 field_72] } let(:case_insensitive_integer_fields_with_r_option) { %w[field_28 field_35 field_43 field_47 field_51 field_55 field_64 field_75 field_70 field_72 field_90 field_118] }
let(:attributes) do let(:attributes) do
valid_attributes valid_attributes
.merge(case_insensitive_fields.each_with_object({}) { |field, h| h[field.to_sym] = valid_attributes[field.to_sym]&.downcase }) .merge(case_insensitive_fields.each_with_object({}) { |field, h| h[field.to_sym] = valid_attributes[field.to_sym]&.downcase })
@ -1454,6 +1454,74 @@ RSpec.describe BulkUpload::Sales::Year2026::RowParser do
expect(parser.log_already_exists?).to be(false) expect(parser.log_already_exists?).to be(false)
end end
end end
describe "field_90" do
context "when field_90 is a number" do
let(:field_90_number_attributes) { valid_attributes.merge({ field_90: 20 }) }
context "and buyer was interviewed" do
let(:attributes) { field_90_number_attributes.merge({ field_14: 2 }) }
it "does not add an error" do
parser.valid?
expect(parser.errors.where(:field_90)).not_to be_present
end
end
context "and buyer was not interviewed" do
let(:attributes) { field_90_number_attributes.merge({ field_14: 1 }) }
it "does not add an error" do
parser.valid?
expect(parser.errors.where(:field_90)).not_to be_present
end
end
end
context "when field_90 is R" do
let(:field_90_number_attributes) { valid_attributes.merge({ field_90: "R" }) }
context "and buyer was interviewed" do
let(:attributes) { field_90_number_attributes.merge({ field_14: 2 }) }
it "adds an error" do
parser.valid?
expect(parser.errors.where(:field_90)).to be_present
end
end
context "and buyer was not interviewed" do
let(:attributes) { field_90_number_attributes.merge({ field_14: 1 }) }
it "does not add an error" do
parser.valid?
expect(parser.errors.where(:field_90)).not_to be_present
end
end
end
context "when field_90 is neither a number nor R" do
let(:field_90_number_attributes) { valid_attributes.merge({ field_90: "something" }) }
context "and buyer was interviewed" do
let(:attributes) { field_90_number_attributes.merge({ field_14: 2 }) }
it "adds an error" do
parser.valid?
expect(parser.errors.where(:field_90)).to be_present
end
end
context "and buyer was not interviewed" do
let(:attributes) { field_90_number_attributes.merge({ field_14: 1 }) }
it "adds an error" do
parser.valid?
expect(parser.errors.where(:field_90)).to be_present
end
end
end
end
end end
describe "#log" do describe "#log" do
@ -1959,6 +2027,58 @@ RSpec.describe BulkUpload::Sales::Year2026::RowParser do
end end
end end
end end
describe "mortlen amd mortlen_known" do
context "when field_90 is a number" do
let(:field_90_number_attributes) { valid_attributes.merge({ field_90: 20 }) }
context "and buyer was interviewed" do
let(:attributes) { field_90_number_attributes.merge({ field_14: 2 }) }
it "sets mortlen to the length" do
log = parser.log
expect(log.mortlen).to eq(20)
end
it "sets mortlen_known to nil" do
log = parser.log
expect(log.mortlen_known).to be_nil
end
end
context "and buyer was not interviewed" do
let(:attributes) { field_90_number_attributes.merge({ field_14: 1 }) }
it "sets mortlen to the length" do
log = parser.log
expect(log.mortlen).to eq(20)
end
it "sets mortlen_known to yes" do
log = parser.log
expect(log.mortlen_known).to eq(0)
end
end
end
context "when field_90 is R" do
let(:field_90_number_attributes) { valid_attributes.merge({ field_90: "R" }) }
context "and buyer was not interviewed" do
let(:attributes) { field_90_number_attributes.merge({ field_14: 1 }) }
it "sets mortlen to nil" do
log = parser.log
expect(log.mortlen).to be_nil
end
it "sets mortlen_known to no" do
log = parser.log
expect(log.mortlen_known).to eq(1)
end
end
end
end
end end
describe "#owning_organisation_id" do describe "#owning_organisation_id" do

Loading…
Cancel
Save