Browse Source

Merge branch 'main' into CLDC-3196-update-bu-resources-link

# Conflicts:
#	app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2024.html.erb
#	app/views/bulk_upload_sales_logs/forms/prepare_your_file_2024.html.erb
#	app/views/bulk_upload_shared/guidance.html.erb
#	spec/requests/bulk_upload_lettings_logs_controller_spec.rb
#	spec/requests/bulk_upload_sales_logs_controller_spec.rb
CLDC-3196-update-bu-resources-link
natdeanlewissoftwire 1 year ago
parent
commit
a53cf0120d
  1. 6
      app/models/derived_variables/lettings_log_variables.rb
  2. 14
      app/models/derived_variables/sales_log_variables.rb
  3. 2
      app/models/form/lettings/pages/property_number_of_bedrooms.rb
  4. 5
      app/models/form/lettings/questions/beds.rb
  5. 9
      app/models/form/lettings/questions/gender_identity1.rb
  6. 9
      app/models/form/lettings/questions/person_gender_identity.rb
  7. 188
      app/models/form/lettings/questions/reason.rb
  8. 34
      app/models/form/lettings/questions/reason_renewal.rb
  9. 2
      app/models/form/lettings/questions/wheelchair.rb
  10. 8
      app/models/form/sales/pages/buyer_previous.rb
  11. 6
      app/models/form/sales/pages/la_nominations.rb
  12. 2
      app/models/form/sales/questions/buyer_live.rb
  13. 4
      app/models/form/sales/questions/buyer_previous.rb
  14. 2
      app/models/form/sales/questions/discount.rb
  15. 9
      app/models/form/sales/questions/gender_identity1.rb
  16. 6
      app/models/form/sales/questions/gender_identity2.rb
  17. 6
      app/models/form/sales/questions/person_gender_identity.rb
  18. 1
      app/models/form/sales/questions/property_wheelchair_accessible.rb
  19. 4
      app/models/lettings_log.rb
  20. 18
      app/models/sales_log.rb
  21. 2
      app/models/validations/property_validations.rb
  22. 55
      app/models/validations/sales/sale_information_validations.rb
  23. 3
      app/models/validations/sales/soft_validations.rb
  24. 2
      app/services/bulk_upload/lettings/year2024/row_parser.rb
  25. 13
      app/services/bulk_upload/sales/year2024/row_parser.rb
  26. 13
      app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2024.html.erb
  27. 12
      app/views/bulk_upload_sales_logs/forms/prepare_your_file_2024.html.erb
  28. 15
      app/views/bulk_upload_shared/guidance.html.erb
  29. 8
      config/locales/en.yml
  30. 33
      spec/models/form/lettings/pages/property_number_of_bedrooms_spec.rb
  31. 4
      spec/models/form/lettings/pages/property_wheelchair_accessible_spec.rb
  32. 65
      spec/models/form/lettings/questions/beds_spec.rb
  33. 69
      spec/models/form/lettings/questions/gender_identity1_spec.rb
  34. 23
      spec/models/form/lettings/questions/person_gender_identity_spec.rb
  35. 85
      spec/models/form/lettings/questions/reason_renewal_spec.rb
  36. 209
      spec/models/form/lettings/questions/reason_spec.rb
  37. 16
      spec/models/form/lettings/questions/wheelchair_spec.rb
  38. 4
      spec/models/form/sales/pages/about_price_rtb_spec.rb
  39. 6
      spec/models/form/sales/pages/buyer_live_spec.rb
  40. 61
      spec/models/form/sales/pages/buyer_previous_spec.rb
  41. 53
      spec/models/form/sales/pages/la_nominations_spec.rb
  42. 4
      spec/models/form/sales/pages/property_wheelchair_accessible_spec.rb
  43. 28
      spec/models/form/sales/questions/buyer_live_spec.rb
  44. 31
      spec/models/form/sales/questions/buyer_previous_spec.rb
  45. 16
      spec/models/form/sales/questions/discount_spec.rb
  46. 31
      spec/models/form/sales/questions/gender_identity1_spec.rb
  47. 27
      spec/models/form/sales/questions/gender_identity2_spec.rb
  48. 27
      spec/models/form/sales/questions/person_gender_identity_spec.rb
  49. 16
      spec/models/form/sales/questions/property_wheelchair_accessible_spec.rb
  50. 289
      spec/models/validations/sales/sale_information_validations_spec.rb
  51. 30
      spec/models/validations/sales/soft_validations_spec.rb
  52. 6
      spec/requests/bulk_upload_lettings_logs_controller_spec.rb
  53. 7
      spec/requests/bulk_upload_sales_logs_controller_spec.rb
  54. 18
      spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb
  55. 50
      spec/services/bulk_upload/sales/year2024/row_parser_spec.rb

6
app/models/derived_variables/lettings_log_variables.rb

@ -91,6 +91,9 @@ module DerivedVariables::LettingsLogVariables
self.prevten = 30 if owning_organisation&.provider_type == "LA"
end
end
if form.start_year_after_2024? && is_bedsit?
self.beds = 1
end
child_under_16_constraints!
@ -179,6 +182,9 @@ private
self.wchair = nil
self.location_id = nil
end
if form.start_year_after_2024? && (unittype_gn_changed? && unittype_gn_was == 2)
self.beds = nil
end
end
def get_totelder

14
app/models/derived_variables/sales_log_variables.rb

@ -24,6 +24,10 @@ module DerivedVariables::SalesLogVariables
self.hhmemb = number_of_household_members
self.hhtype = household_type
if saledate && form.start_year_after_2024?
self.soctenant = soctenant_from_prevten_values
end
self.uprn_known = 0 if address_answered_without_uprn?
if uprn_known&.zero?
@ -157,4 +161,14 @@ private
def address_answered_without_uprn?
[address_line1, town_or_city].all?(&:present?) && uprn.nil? && form.start_date.year >= 2023
end
def soctenant_from_prevten_values
return unless prevten && shared_ownership_scheme?
prevten_was_social_housing? ? 1 : 2
end
def prevten_was_social_housing?
[1, 2].include?(prevten) || [1, 2].include?(prevtenbuy2)
end
end

2
app/models/form/lettings/pages/property_number_of_bedrooms.rb

@ -2,7 +2,7 @@ class Form::Lettings::Pages::PropertyNumberOfBedrooms < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "property_number_of_bedrooms"
@depends_on = [{ "is_general_needs?" => true }]
@depends_on = [{ "is_general_needs?" => true, "is_beds_inferred?" => false }]
end
def questions

5
app/models/form/lettings/questions/beds.rb

@ -9,8 +9,11 @@ class Form::Lettings::Questions::Beds < ::Form::Question
@check_answers_card_number = 0
@max = 12
@min = 1
@hint_text = "If shared accommodation, enter the number of bedrooms occupied by this household. A bedsit has 1 bedroom."
@step = 1
@question_number = 22
end
def hint_text
form.start_year_after_2024? ? "If shared accommodation, enter the number of bedrooms occupied by this household." : "If shared accommodation, enter the number of bedrooms occupied by this household. A bedsit has 1 bedroom."
end
end

9
app/models/form/lettings/questions/gender_identity1.rb

@ -6,7 +6,6 @@ class Form::Lettings::Questions::GenderIdentity1 < ::Form::Question
@header = "Which of these best describes the lead tenant’s gender identity?"
@type = "radio"
@check_answers_card_number = 1
@hint_text = "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest."
@answer_options = ANSWER_OPTIONS
@question_number = 33
end
@ -18,4 +17,12 @@ class Form::Lettings::Questions::GenderIdentity1 < ::Form::Question
"divider" => { "value" => true },
"R" => { "value" => "Tenant prefers not to say" },
}.freeze
def hint_text
if form.start_year_after_2024?
"This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth."
else
"The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest."
end
end
end

9
app/models/form/lettings/questions/person_gender_identity.rb

@ -6,7 +6,6 @@ class Form::Lettings::Questions::PersonGenderIdentity < ::Form::Question
@header = "Which of these best describes person #{person_index}’s gender identity?"
@type = "radio"
@check_answers_card_number = person_index
@hint_text = ""
@answer_options = ANSWER_OPTIONS
@question_number = 32 + (4 * person_index)
end
@ -18,4 +17,12 @@ class Form::Lettings::Questions::PersonGenderIdentity < ::Form::Question
"divider" => { "value" => true },
"R" => { "value" => "Person prefers not to say" },
}.freeze
def hint_text
if form.start_year_after_2024?
"This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth."
else
""
end
end
end

188
app/models/form/lettings/questions/reason.rb

@ -6,8 +6,7 @@ class Form::Lettings::Questions::Reason < ::Form::Question
@header = "What is the tenant’s main reason for the household leaving their last settled home?"
@type = "radio"
@check_answers_card_number = 0
@hint_text = "The tenant’s ‘last settled home’ is their last long-standing home. For tenants who were in temporary accommodation or sleeping rough, their last settled home is where they were living previously."
@answer_options = ANSWER_OPTIONS
@hint_text = form.start_year_after_2024? ? "The tenant’s ‘last settled home’ is their last long-standing home. For tenants who were in temporary accommodation, sleeping rough or otherwise homeless, their last settled home is where they were living previously." : "The tenant’s ‘last settled home’ is their last long-standing home. For tenants who were in temporary accommodation or sleeping rough, their last settled home is where they were living previously."
@conditional_for = {
"reasonother" => [
20,
@ -16,114 +15,81 @@ class Form::Lettings::Questions::Reason < ::Form::Question
@question_number = 77
end
ANSWER_OPTIONS = {
"40" => {
"value" => "End of assured shorthold tenancy (no fault)",
},
"41" => {
"value" => "End of assured shorthold tenancy (eviction or tenant at fault)",
},
"42" => {
"value" => "End of fixed term tenancy (no fault)",
},
"43" => {
"value" => "End of fixed term tenancy (eviction or tenant at fault)",
},
"1" => {
"value" => "Permanently decanted from another property owned by this landlord",
},
"46" => {
"value" => "Discharged from long-stay hospital or similar institution",
},
"45" => {
"value" => "Discharged from prison",
},
"2" => {
"value" => "Left home country as a refugee",
},
"4" => {
"value" => "Loss of tied accommodation",
},
"9" => {
"value" => "Asked to leave by family or friends",
},
"44" => {
"value" => "Death of household member in last settled accommodation",
},
"8" => {
"value" => "Relationship breakdown (non-violent) with partner",
},
"16" => {
"value" => "To move nearer to family, friends or school",
},
"17" => {
"value" => "To move nearer to work",
},
"48" => {
"value" => "Domestic abuse - previously joint tenancy with partner",
},
"49" => {
"value" => "Domestic abuse - other",
},
"31" => {
"value" => "Hate crime",
},
"10" => {
"value" => "Racial harassment",
},
"11" => {
"value" => "Other problems with neighbours",
},
"35" => {
"value" => "Couldn’t afford fees attached to renewing the tenancy",
},
"36" => {
"value" => "Couldn’t afford increase in rent",
},
"38" => {
"value" => "Couldn’t afford rent or mortgage (employment)",
},
"37" => {
"value" => "Couldn’t afford rent or mortgage (welfare reforms)",
},
"39" => {
"value" => "Couldn’t afford rent or mortgage (other)",
},
"34" => {
"value" => "Repossession",
},
"12" => {
"value" => "Property unsuitable because of overcrowding",
},
"13" => {
"value" => "Property unsuitable because of ill health or disability",
},
"14" => {
"value" => "Property unsuitable because of poor condition",
},
"18" => {
"value" => "To move to accommodation with support",
},
"19" => {
"value" => "To move to independent accommodation",
},
"30" => {
"value" => "Under occupation (no incentive)",
},
"29" => {
"value" => "Under occupation (offered incentive to downsize)",
},
"20" => {
"value" => "Other",
},
"47" => {
"value" => "Tenant prefers not to say",
},
"divider" => {
"value" => true,
},
"28" => {
"value" => "Don’t know",
},
def answer_options
if form.start_year_after_2024?
{
"50" => { "value" => "End of social housing tenancy - no fault" },
"51" => { "value" => "End of social housing tenancy - evicted due to anti-social behaviour (ASB)" },
"52" => { "value" => "End of social housing tenancy - evicted due to rent arrears" },
"53" => { "value" => "End of social housing tenancy - evicted for any other reason" },
"1" => { "value" => "Permanently decanted from another property owned by this landlord" },
"2" => { "value" => "Left home country as a refugee" },
"45" => { "value" => "Discharged from prison" },
"46" => { "value" => "Discharged from long-stay hospital or similar institution" },
"4" => { "value" => "Loss of tied accommodation" },
"9" => { "value" => "Asked to leave by family or friends" },
"8" => { "value" => "Relationship breakdown (non-violent) with partner" },
"44" => { "value" => "Death of household member in last settled accommodation" },
"16" => { "value" => "To move nearer to family, friends or school" },
"17" => { "value" => "To move nearer to work" },
"48" => { "value" => "Domestic abuse - previously joint tenancy with partner" },
"49" => { "value" => "Domestic abuse - other" },
"10" => { "value" => "Racial harassment" },
"31" => { "value" => "Hate crime" },
"11" => { "value" => "Other problems with neighbours" },
"34" => { "value" => "Repossession" },
"54" => { "value" => "Could no longer afford rent or mortgage" },
"12" => { "value" => "Property unsuitable because of overcrowding" },
"13" => { "value" => "Property unsuitable because of ill health or disability" },
"14" => { "value" => "Property unsuitable because of poor condition" },
"29" => { "value" => "Under occupation (offered incentive to downsize)" },
"30" => { "value" => "Under occupation (no incentive)" },
"18" => { "value" => "To move to accommodation with support" },
"19" => { "value" => "To move to independent accommodation" },
"20" => { "value" => "Other" },
"28" => { "value" => "Don’t know" },
"divider" => { "value" => true },
"47" => { "value" => "Tenant prefers not to say" },
}.freeze
else
{
"40" => { "value" => "End of assured shorthold tenancy (no fault)" },
"41" => { "value" => "End of assured shorthold tenancy (eviction or tenant at fault)" },
"42" => { "value" => "End of fixed term tenancy (no fault)" },
"43" => { "value" => "End of fixed term tenancy (eviction or tenant at fault)" },
"1" => { "value" => "Permanently decanted from another property owned by this landlord" },
"46" => { "value" => "Discharged from long-stay hospital or similar institution" },
"45" => { "value" => "Discharged from prison" },
"2" => { "value" => "Left home country as a refugee" },
"4" => { "value" => "Loss of tied accommodation" },
"9" => { "value" => "Asked to leave by family or friends" },
"44" => { "value" => "Death of household member in last settled accommodation" },
"8" => { "value" => "Relationship breakdown (non-violent) with partner" },
"16" => { "value" => "To move nearer to family, friends or school" },
"17" => { "value" => "To move nearer to work" },
"48" => { "value" => "Domestic abuse - previously joint tenancy with partner" },
"49" => { "value" => "Domestic abuse - other" },
"31" => { "value" => "Hate crime" },
"10" => { "value" => "Racial harassment" },
"11" => { "value" => "Other problems with neighbours" },
"35" => { "value" => "Couldn’t afford fees attached to renewing the tenancy" },
"36" => { "value" => "Couldn’t afford increase in rent" },
"38" => { "value" => "Couldn’t afford rent or mortgage (employment)" },
"37" => { "value" => "Couldn’t afford rent or mortgage (welfare reforms)" },
"39" => { "value" => "Couldn’t afford rent or mortgage (other)" },
"34" => { "value" => "Repossession" },
"12" => { "value" => "Property unsuitable because of overcrowding" },
"13" => { "value" => "Property unsuitable because of ill health or disability" },
"14" => { "value" => "Property unsuitable because of poor condition" },
"18" => { "value" => "To move to accommodation with support" },
"19" => { "value" => "To move to independent accommodation" },
"30" => { "value" => "Under occupation (no incentive)" },
"29" => { "value" => "Under occupation (offered incentive to downsize)" },
"20" => { "value" => "Other" },
"47" => { "value" => "Tenant prefers not to say" },
"divider" => { "value" => true },
"28" => { "value" => "Don’t know" },
}.freeze
end
end
end

34
app/models/form/lettings/questions/reason_renewal.rb

@ -7,7 +7,6 @@ class Form::Lettings::Questions::ReasonRenewal < ::Form::Question
@type = "radio"
@check_answers_card_number = 0
@hint_text = "You told us this letting is a renewal. We have removed some options because of this."
@answer_options = ANSWER_OPTIONS
@question_number = 77
@conditional_for = {
"reasonother" => [
@ -16,20 +15,27 @@ class Form::Lettings::Questions::ReasonRenewal < ::Form::Question
}
end
ANSWER_OPTIONS = {
def answer_options
if form.start_year_after_2024?
{
"50" => { "value" => "End of social housing tenancy - no fault" },
"51" => { "value" => "End of social housing tenancy - evicted due to anti-social behaviour (ASB)" },
"52" => { "value" => "End of social housing tenancy - evicted due to rent arrears" },
"53" => { "value" => "End of social housing tenancy - evicted for any other reason" },
"20" => { "value" => "Other" },
"47" => { "value" => "Tenant prefers not to say" },
"divider" => { "value" => true },
"28" => { "value" => "Don’t know" },
}.freeze
else
{
"40" => { "value" => "End of assured shorthold tenancy (no fault)" },
"42" => { "value" => "End of fixed term tenancy (no fault)" },
"20" => {
"value" => "Other",
},
"47" => {
"value" => "Tenant prefers not to say",
},
"divider" => {
"value" => true,
},
"28" => {
"value" => "Don’t know",
},
"20" => { "value" => "Other" },
"47" => { "value" => "Tenant prefers not to say" },
"divider" => { "value" => true },
"28" => { "value" => "Don’t know" },
}.freeze
end
end
end

2
app/models/form/lettings/questions/wheelchair.rb

@ -6,7 +6,7 @@ class Form::Lettings::Questions::Wheelchair < ::Form::Question
@header = "Is the property built or adapted to wheelchair-user standards?"
@type = "radio"
@check_answers_card_number = 0
@hint_text = ""
@hint_text = form.start_year_after_2024? ? "This is whether someone who uses a wheelchair is able to make full use of all of the property’s rooms and facilities, including use of both inside and outside space, and entering and exiting the property." : ""
@answer_options = ANSWER_OPTIONS
@question_number = 21
end

8
app/models/form/sales/pages/buyer_previous.rb

@ -2,7 +2,7 @@ class Form::Sales::Pages::BuyerPrevious < ::Form::Page
def initialize(id, hsh, subsection, joint_purchase:)
super(id, hsh, subsection)
@joint_purchase = joint_purchase
@depends_on = [{ "joint_purchase?" => joint_purchase }]
@depends_on = [{ "joint_purchase?" => joint_purchase, "soctenant_is_inferred?" => false }]
end
def questions
@ -10,4 +10,10 @@ class Form::Sales::Pages::BuyerPrevious < ::Form::Page
Form::Sales::Questions::BuyerPrevious.new(nil, nil, self, joint_purchase: @joint_purchase),
]
end
def routed_to?(log, _current_user)
return false if log.is_staircase? && log.form.start_year_after_2024?
super
end
end

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

@ -9,4 +9,10 @@ class Form::Sales::Pages::LaNominations < ::Form::Page
Form::Sales::Questions::LaNominations.new(nil, nil, self),
]
end
def routed_to?(log, _current_user)
return false if log.staircase == 1 && log.form.start_year_after_2024?
super
end
end

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

@ -3,7 +3,7 @@ class Form::Sales::Questions::BuyerLive < ::Form::Question
super
@id = "buylivein"
@check_answer_label = "Buyers living in property"
@header = "Will the buyers live in the property?"
@header = form.start_year_after_2024? ? "Will any buyers live in the property?" : "Will the buyers live in the property?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@question_number = 8

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

@ -21,4 +21,8 @@ class Form::Sales::Questions::BuyerPrevious < ::Form::Question
"2" => { "value" => "No" },
}
end
def derived?
form.start_year_after_2024?
end
end

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

@ -6,7 +6,7 @@ class Form::Sales::Questions::Discount < ::Form::Question
@header = "What was the percentage discount?"
@type = "numeric"
@min = 0
@max = 100
@max = form.start_year_after_2024? ? 70 : 100
@step = 1
@width = 5
@suffix = "%"

9
app/models/form/sales/questions/gender_identity1.rb

@ -5,7 +5,6 @@ class Form::Sales::Questions::GenderIdentity1 < ::Form::Question
@check_answer_label = "Buyer 1’s gender identity"
@header = "Which of these best describes buyer 1’s gender identity?"
@type = "radio"
@hint_text = "Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest."
@answer_options = ANSWER_OPTIONS
@check_answers_card_number = 1
@question_number = 21
@ -17,4 +16,12 @@ class Form::Sales::Questions::GenderIdentity1 < ::Form::Question
"X" => { "value" => "Non-binary" },
"R" => { "value" => "Prefers not to say" },
}.freeze
def hint_text
if form.start_year_after_2024?
"This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth."
else
"Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest."
end
end
end

6
app/models/form/sales/questions/gender_identity2.rb

@ -22,4 +22,10 @@ class Form::Sales::Questions::GenderIdentity2 < ::Form::Question
"X" => { "value" => "Non-binary" },
"R" => { "value" => "Buyer prefers not to say" },
}.freeze
def hint_text
return unless form.start_year_after_2024?
"This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth."
end
end

6
app/models/form/sales/questions/person_gender_identity.rb

@ -21,4 +21,10 @@ class Form::Sales::Questions::PersonGenderIdentity < ::Form::Question
"X" => { "value" => "Non-binary" },
"R" => { "value" => "Person prefers not to say" },
}.freeze
def hint_text
return unless form.start_year_after_2024?
"This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth."
end
end

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

@ -7,6 +7,7 @@ class Form::Sales::Questions::PropertyWheelchairAccessible < ::Form::Question
@type = "radio"
@answer_options = ANSWER_OPTIONS
@question_number = 17
@hint_text = form.start_year_after_2024? ? "This is whether someone who uses a wheelchair is able to make full use of all of the property’s rooms and facilities, including use of both inside and outside space, and entering and exiting the property." : nil
end
ANSWER_OPTIONS = {

4
app/models/lettings_log.rb

@ -356,6 +356,10 @@ class LettingsLog < Log
unittype_gn == 2
end
def is_beds_inferred?
form.start_year_after_2024? && is_bedsit?
end
def is_shared_housing?
# 4: Shared flat or maisonette
# 9: Shared house

18
app/models/sales_log.rb

@ -470,6 +470,10 @@ class SalesLog < Log
form.start_date.year >= 2023 && uprn.present? ? "uprn" : nil].compact
end
def soctenant_is_inferred?
form.start_year_after_2024?
end
def duplicates
SalesLog.where.not(duplicate_set_id: nil).where(duplicate_set_id:).where.not(id:)
end
@ -477,4 +481,18 @@ class SalesLog < Log
def nationality2_uk_or_prefers_not_to_say?
nationality_all_buyer2_group&.zero? || nationality_all_buyer2_group == 826
end
def is_staircase?
staircase == 1
end
def discount_value
return unless discount && value
value * discount / 100
end
def is_not_staircasing?
staircase == 2 || staircase == 3
end
end

2
app/models/validations/property_validations.rb

@ -34,7 +34,7 @@ module Validations::PropertyValidations
def validate_shared_housing_rooms(record)
unless record.unittype_gn.nil?
if record.is_bedsit? && record.beds != 1 && record.beds.present?
if record.is_bedsit? && record.beds != 1 && record.beds.present? && !record.form.start_year_after_2024?
record.errors.add :unittype_gn, I18n.t("validations.property.unittype_gn.one_bedroom_bedsit")
record.errors.add :beds, I18n.t("validations.property.unittype_gn.one_bedroom_bedsit")
end

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

@ -55,4 +55,59 @@ module Validations::Sales::SaleInformationValidations
record.errors.add :type, I18n.t("validations.sale_information.monthly_rent.higher_than_expected")
end
end
def validate_grant_amount(record)
return unless record.saledate && record.form.start_year_after_2024?
return unless record.grant && (record.type == 8 || record.type == 21)
unless record.grant.between?(9_000, 16_000)
record.errors.add :grant, I18n.t("validations.sale_information.grant.out_of_range")
end
end
def validate_stairbought(record)
return unless record.stairbought && record.type
return unless record.saledate && record.form.start_year_after_2024?
max_stairbought = case record.type
when 30, 16, 28, 31, 32
90
when 2, 18
75
when 24
50
end
if max_stairbought && record.stairbought > max_stairbought
record.errors.add :stairbought, I18n.t("validations.sale_information.stairbought.over_max", max_stairbought:, type: record.form.get_question("type", record).answer_label(record))
record.errors.add :type, I18n.t("validations.sale_information.stairbought.over_max", max_stairbought:, type: record.form.get_question("type", record).answer_label(record))
end
end
def validate_discount_and_value(record)
return unless record.saledate && record.form.start_year_after_2024?
return unless record.discount && record.value && record.la
if record.london_property? && record.discount_value > 136_400
%i[discount value la postcode_full uprn].each do |field|
record.errors.add field, I18n.t("validations.sale_information.value.over_discounted_london_max", discount_value: record.field_formatted_as_currency("discount_value"))
end
elsif record.property_not_in_london? && record.discount_value > 102_400
%i[discount value la postcode_full uprn].each do |field|
record.errors.add field, I18n.t("validations.sale_information.value.over_discounted_max", discount_value: record.field_formatted_as_currency("discount_value"))
end
end
end
def validate_non_staircasing_mortgage(record)
return unless record.mortgage && record.value && record.deposit && record.equity
return unless record.is_not_staircasing?
return unless record.saledate && record.form.start_year_after_2024?
if record.mortgage_and_deposit_total != record.expected_shared_ownership_deposit_value
%i[mortgage value deposit equity].each do |field|
record.errors.add field, I18n.t("validations.sale_information.non_staircasing_mortgage", mortgage_and_deposit_total: record.field_formatted_as_currency("mortgage_and_deposit_total"), expected_shared_ownership_deposit_value: record.field_formatted_as_currency("expected_shared_ownership_deposit_value"))
end
end
end
end

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

@ -116,7 +116,8 @@ module Validations::Sales::SoftValidations
end
def grant_outside_common_range?
return unless grant
return unless grant && type && saledate
return if form.start_year_after_2024? && (type == 21 || type == 8)
!grant.between?(9_000, 16_000)
end

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

@ -1036,7 +1036,7 @@ private
attributes["unittype_gn"] = field_26
attributes["builtype"] = field_27
attributes["wchair"] = field_28
attributes["beds"] = field_29
attributes["beds"] = field_26 == 2 ? 1 : field_29
attributes["joint"] = field_36
attributes["startertenancy"] = field_37
attributes["tenancy"] = field_38

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

@ -717,7 +717,6 @@ private
lanomagr: %i[field_97],
frombeds: %i[field_98],
fromprop: %i[field_99],
soctenant: %i[field_98 field_99 field_100],
value: %i[field_101 field_114 field_125],
equity: %i[field_102],
mortgage: %i[field_104 field_118 field_127],
@ -927,7 +926,7 @@ private
attributes["stairbought"] = field_87
attributes["stairowned"] = field_88
attributes["socprevten"] = field_100
attributes["soctenant"] = [attributes["socprevten"], attributes["frombeds"], attributes["fromprop"]].any?(&:present?) ? 1 : 0
attributes["soctenant"] = infer_soctenant_from_prevten_and_prevtenbuy2
attributes["mortgageused"] = mortgageused
attributes["uprn"] = field_22
@ -1123,6 +1122,16 @@ private
0 if field_62 == 1
end
def infer_soctenant_from_prevten_and_prevtenbuy2
return unless shared_ownership?
if [1, 2].include?(field_61) || [1, 2].include?(field_71.to_i)
1
else
2
end
end
def block_log_creation!
self.block_log_creation = true
end

13
app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2024.html.erb

@ -13,22 +13,17 @@
<h2 class="govuk-heading-s">Download template</h2>
<p class="govuk-body govuk-!-margin-bottom-2">Use this template to upload logs for 2024/25:</p>
<ul class="govuk-list govuk-list--bullet">
<li>
<%= govuk_link_to "Download the template", @form.template_path %>: In this template, the questions are in the same order as the 2024/25 paper form and web form.
</li>
</ul>
<p class="govuk-body govuk-!-margin-bottom-2">There are 7 or 8 rows of content in the templates. These rows are called the ‘headers’. They contain the CORE form questions and guidance about which questions are required and how to format your answers.</p>
<p class="govuk-body govuk-!-margin-bottom-2"><%= govuk_link_to "Download the lettings bulk upload template (2024 to 2025)", @form.template_path %></p>
<p class="govuk-body govuk-!-margin-bottom-2">There are 8 rows of content in the templates. These rows are called the ‘headers’. They contain the CORE form questions and guidance about which questions are required and how to format your answers.</p>
<h2 class="govuk-heading-s">Create your file</h2>
<ul class="govuk-list govuk-list--bullet">
<li>Fill in the template with data from your housing management system. Your data should go below the headers, with one row per log. Leave column A blank - the bulk upload fields start in column B.</li>
<li>Make sure each column of your data aligns with the matching headers above. You may need to reorder your data.</li>
<li>Use the <%= govuk_link_to "Lettings #{@form.year_combo} Bulk Upload Specification", @form.specification_path %> to check your data is in the correct format.</li>
<li>Use the <%= govuk_link_to "Lettings bulk upload Specification (2024 to 2025)", @form.specification_path %> to check your data is in the correct format.</li>
<li><strong>Username field:</strong> To assign a log to someone else, enter the email address they use to log into CORE.</li>
<li>If you are using the new template, keep the headers. If you are using the legacy template, you can either keep or remove the headers. If you remove the headers, you should also remove the blank column A.</li>
<li>If you have reordered the headers, keep the headers in the file.</li>
</ul>
<%= govuk_inset_text(text: "You can upload both general needs and supported housing logs in the same file for 2023/24 data.") %>

12
app/views/bulk_upload_sales_logs/forms/prepare_your_file_2024.html.erb

@ -13,19 +13,17 @@
<h2 class="govuk-heading-s">Download template</h2>
<p class="govuk-body govuk-!-margin-bottom-2">Use this template to upload logs for 2024/25:</p>
<ul class="govuk-list govuk-list--bullet">
<li><%= govuk_link_to "Download the template", @form.template_path %>: In this template, the questions are in the same order as the 2024/25 paper form and web form.</li>
</ul>
<p class="govuk-body govuk-!-margin-bottom-2">There are 7 or 8 rows of content in the templates. These rows are called the ‘headers’. They contain the CORE form questions and guidance about which questions are required and how to format your answers.</p>
<p class="govuk-body govuk-!-margin-bottom-2">Use one of these templates to upload logs for 2024/25:</p>
<p class="govuk-body govuk-!-margin-bottom-2"><%= govuk_link_to "Download the sales bulk upload template (2024 to 2025)", @form.template_path %>: In this template, the questions are in the same order as the 2024/25 paper form and web form.</p>
<p class="govuk-body govuk-!-margin-bottom-2">There are 8 rows of content in the templates. These rows are called the ‘headers’. They contain the CORE form questions and guidance about which questions are required and how to format your answers.</p>
<h2 class="govuk-heading-s">Create your file</h2>
<ul class="govuk-list govuk-list--bullet">
<li>Fill in the template with data from your housing management system. Your data should go below the headers, with one row per log. The bulk upload fields start at column B. Leave column A blank.</li>
<li>Make sure each column of your data aligns with the matching headers above. You may need to reorder your data.</li>
<li>Use the <%= govuk_link_to "Sales #{@form.year_combo} Bulk Upload Specification", @form.specification_path %> to check your data is in the correct format.</li>
<li>Use the <%= govuk_link_to "Sales bulk upload Specification (2024 to 2025)", @form.specification_path %> to check your data is in the correct format.</li>
<li><strong>Username field:</strong> To assign a log to someone else, enter the email address they use to log into CORE.</li>
<li>If you are using the new template, keep the headers. If you are using the legacy template, you can either keep or remove the headers. If you remove the headers, you should also remove the blank column A.</li>
<li>If you have reordered the headers, keep the headers in the file.</li>
</ul>
<h2 class="govuk-heading-s">Save your file</h2>

15
app/views/bulk_upload_shared/guidance.html.erb

@ -28,7 +28,11 @@
<%= accordion.with_section(heading_text: "Using the bulk upload template") do %>
<p class="govuk-body">For each collection year, we publish a bulk upload template and specification.</p>
<% if @form.year == 2023 %>
<p class="govuk-body">The bulk upload templates contain 7 or 8 rows of ‘headers’ with information about how to fill in the template, including:</p>
<% else %>
<p class="govuk-body">The bulk upload templates contain 8 rows of ‘headers’ with information about how to fill in the template, including:</p>
<% end %>
<ul class="govuk-list govuk-list--bullet">
<li>the CORE form questions and their field numbers</li>
@ -39,19 +43,20 @@
<p class="govuk-body">You can paste your data below the headers or copy the headers and insert them above the data in your file. The bulk upload fields start at column B. Leave column A blank.</p>
<p class="govuk-body">Make sure that each column of data aligns with the corresponding question in the headers. We recommend ordering your data to match the headers, but you can also reorder the headers to match your data. When processing the file, we check what each column of data represents based on the headers above.</p>
<% unless @form.year == 2023 %>
<% if @form.year == 2023 %>
<p class="govuk-body">For 2023/24 uploads, there are 2 templates to choose from, a new template and a legacy template. They outline suggested ways of ordering your data.</p>
<p class="govuk-body">You must include the headers in your file when you upload, unless your data matches the exact order of the legacy template. If you choose to remove the headers, you must also remove the blank column A.</p>
<p class="govuk-body"><strong>New template</strong>: In this template, the questions are in the same order as the <%= @form.year_combo %> paper form and web form. Use this template if your organisation is new to bulk upload or if your housing management system matches the new column ordering.</p>
<% end %>
<p class="govuk-body"><strong>New template</strong>: In this template, the questions are in the same order as the 2023/24 paper form and web form. Use this template if your organisation is new to bulk upload or if your housing management system matches the new column ordering.</p>
<p class="govuk-body"><%= govuk_link_to @form.download_text("lettings", "template", "new"), @form.lettings_template_path %></p>
<p class="govuk-body"><%= govuk_link_to @form.download_text("sales", "template", "new"), @form.sales_template_path %></p>
<% if @form.year == 2023 %>
<p class="govuk-body"><strong>Legacy template</strong>: In this template, the questions are in the same order as the 2022/23 template, with new questions added on to the end. Use this template if you have not updated your system to match the new template yet.</p>
<p class="govuk-body"><%= govuk_link_to @form.download_text("lettings", "template", "legacy"), @form.lettings_legacy_template_path %></p>
<p class="govuk-body"><%= govuk_link_to @form.download_text("sales", "template", "legacy"), @form.sales_legacy_template_path %></p>
<% else %>
<p class="govuk-body"><%= govuk_link_to @form.download_text("lettings", "template"), @form.lettings_template_path %></p>
<p class="govuk-body"><%= govuk_link_to @form.download_text("sales", "template"), @form.sales_template_path %></p>
<% end %>
<% end %>

8
config/locales/en.yml

@ -615,6 +615,14 @@ en:
discounted_ownership_value: "The mortgage, deposit, and grant when added together is %{mortgage_deposit_and_grant_total}, and the purchase purchase price times by the discount is %{value_with_discount}. These figures should be the same"
monthly_rent:
higher_than_expected: "Basic monthly rent must be between £0.00 and £9,999.00"
grant:
out_of_range: "Loan, grants or subsidies must be between £9,000 and £16,000"
stairbought:
over_max: "The percentage bought in this staircasing transaction cannot be higher than %{max_stairbought}% for %{type} sales."
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."
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."
non_staircasing_mortgage: "The mortgage and deposit added together is %{mortgage_and_deposit_total} and the purchase price times by the equity is %{expected_shared_ownership_deposit_value}. These figures should be the same."
merge_request:
organisation_part_of_another_merge: "This organisation is part of another merge - select a different one"
organisation_not_selected: "Select an organisation from the search list"

33
spec/models/form/lettings/pages/property_number_of_bedrooms_spec.rb

@ -0,0 +1,33 @@
require "rails_helper"
RSpec.describe Form::Lettings::Pages::PropertyNumberOfBedrooms, 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) }
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[beds])
end
it "has the correct id" do
expect(page.id).to eq("property_number_of_bedrooms")
end
it "has the correct header" do
expect(page.header).to be_nil
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has the correct depends_on" do
expect(page.depends_on).to eq([{ "is_general_needs?" => true, "is_beds_inferred?" => false }])
end
end

4
spec/models/form/lettings/pages/property_wheelchair_accessible_spec.rb

@ -5,6 +5,10 @@ RSpec.describe Form::Lettings::Pages::PropertyWheelchairAccessible, type: :model
let(:subsection) { instance_double(Form::Subsection) }
before do
allow(subsection).to receive(:form).and_return(instance_double(Form, start_year_after_2024?: false))
end
it "has correct subsection" do
expect(page.subsection).to be(subsection)
end

65
spec/models/form/lettings/questions/beds_spec.rb

@ -0,0 +1,65 @@
require "rails_helper"
RSpec.describe Form::Lettings::Questions::Beds, 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) }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form) }
before do
allow(form).to receive(:start_year_after_2024?).and_return(false)
allow(page).to receive(:subsection).and_return(subsection)
allow(subsection).to receive(:form).and_return(form)
end
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("beds")
end
it "has the correct header" do
expect(question.header).to eq("How many bedrooms does the property have?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Number of bedrooms")
end
it "has the correct type" do
expect(question.type).to eq("numeric")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct min" do
expect(question.min).to eq(1)
end
it "has the correct max" do
expect(question.max).to eq(12)
end
context "with 2023/24 form" do
it "has the correct hint_text" do
expect(question.hint_text).to eq("If shared accommodation, enter the number of bedrooms occupied by this household. A bedsit has 1 bedroom.")
end
end
context "with 2024/25 form" do
before do
allow(form).to receive(:start_year_after_2024?).and_return(true)
end
it "has the correct hint_text" do
expect(question.hint_text).to eq("If shared accommodation, enter the number of bedrooms occupied by this household.")
end
end
end

69
spec/models/form/lettings/questions/gender_identity1_spec.rb

@ -0,0 +1,69 @@
require "rails_helper"
RSpec.describe Form::Lettings::Questions::GenderIdentity1, type: :model do
subject(:question) { described_class.new(nil, question_definition, page) }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form) }
before do
allow(page).to receive(:subsection).and_return(subsection)
allow(subsection).to receive(:form).and_return(form)
end
it "has the correct id" do
expect(question.id).to eq("sex1")
end
it "has the correct header" do
expect(question.header).to eq("Which of these best describes the lead tenant’s gender identity?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Lead tenant’s gender identity")
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(1)
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"F" => { "value" => "Female" },
"M" => { "value" => "Male" },
"X" => { "value" => "Non-binary" },
"divider" => { "value" => true },
"R" => { "value" => "Tenant prefers not to say" },
})
end
context "with form year before 2024" do
before do
allow(form).to receive(:start_year_after_2024?).and_return(false)
end
it "has the correct hint" do
expect(question.hint_text).to eq("The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.")
end
end
context "with form year >= 2024" do
before do
allow(form).to receive(:start_year_after_2024?).and_return(true)
end
it "has the correct hint" do
expect(question.hint_text).to eq("This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.")
end
end
end

23
spec/models/form/lettings/questions/person_gender_identity_spec.rb

@ -6,6 +6,13 @@ RSpec.describe Form::Lettings::Questions::PersonGenderIdentity, type: :model do
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
let(:person_index) { 2 }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form) }
before do
allow(page).to receive(:subsection).and_return(subsection)
allow(subsection).to receive(:form).and_return(form)
end
it "has correct page" do
expect(question.page).to eq(page)
@ -19,9 +26,25 @@ RSpec.describe Form::Lettings::Questions::PersonGenderIdentity, type: :model do
expect(question.derived?).to be false
end
context "with form year before 2024" do
before do
allow(form).to receive(:start_year_after_2024?).and_return(false)
end
it "has the correct hint" do
expect(question.hint_text).to eq("")
end
end
context "with form year >= 2024" do
before do
allow(form).to receive(:start_year_after_2024?).and_return(true)
end
it "has the correct hint" do
expect(question.hint_text).to eq("This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.")
end
end
context "with person 2" do
it "has the correct id" do

85
spec/models/form/lettings/questions/reason_renewal_spec.rb

@ -0,0 +1,85 @@
require "rails_helper"
RSpec.describe Form::Lettings::Questions::ReasonRenewal, 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) }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form) }
before do
allow(form).to receive(:start_year_after_2024?).and_return(false)
allow(page).to receive(:subsection).and_return(subsection)
allow(subsection).to receive(:form).and_return(form)
end
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("reason")
end
it "has the correct header" do
expect(question.header).to eq("What is the tenant’s main reason for the household leaving their last settled home?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Reason for leaving last settled home")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(0)
end
it "has the correct hint" do
expect(question.hint_text).to eq("You told us this letting is a renewal. We have removed some options because of this.")
end
it "has the correct conditional_for" do
expect(question.conditional_for).to eq({ "reasonother" => [20] })
end
it "is not marked as derived" do
expect(question).not_to be_derived
end
context "with 2023/24 form" do
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"40" => { "value" => "End of assured shorthold tenancy (no fault)" },
"42" => { "value" => "End of fixed term tenancy (no fault)" },
"20" => { "value" => "Other" },
"47" => { "value" => "Tenant prefers not to say" },
"divider" => { "value" => true },
"28" => { "value" => "Don’t know" },
})
end
end
context "with 2024/25 form" do
before do
allow(form).to receive(:start_year_after_2024?).and_return(true)
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"50" => { "value" => "End of social housing tenancy - no fault" },
"51" => { "value" => "End of social housing tenancy - evicted due to anti-social behaviour (ASB)" },
"52" => { "value" => "End of social housing tenancy - evicted due to rent arrears" },
"53" => { "value" => "End of social housing tenancy - evicted for any other reason" },
"20" => { "value" => "Other" },
"47" => { "value" => "Tenant prefers not to say" },
"divider" => { "value" => true },
"28" => { "value" => "Don’t know" },
})
end
end
end

209
spec/models/form/lettings/questions/reason_spec.rb

@ -6,6 +6,14 @@ RSpec.describe Form::Lettings::Questions::Reason, type: :model do
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form) }
before do
allow(form).to receive(:start_year_after_2024?).and_return(false)
allow(page).to receive(:subsection).and_return(subsection)
allow(subsection).to receive(:form).and_return(form)
end
it "has correct page" do
expect(question.page).to eq(page)
@ -31,10 +39,6 @@ RSpec.describe Form::Lettings::Questions::Reason, type: :model do
expect(question.check_answers_card_number).to eq(0)
end
it "has the correct hint" do
expect(question.hint_text).to eq("The tenant’s ‘last settled home’ is their last long-standing home. For tenants who were in temporary accommodation or sleeping rough, their last settled home is where they were living previously.")
end
it "has the correct conditional_for" do
expect(question.conditional_for).to eq({ "reasonother" => [20] })
end
@ -43,116 +47,97 @@ RSpec.describe Form::Lettings::Questions::Reason, type: :model do
expect(question).not_to be_derived
end
context "with 2023/24 form" do
it "has the correct hint" do
expect(question.hint_text).to eq("The tenant’s ‘last settled home’ is their last long-standing home. For tenants who were in temporary accommodation or sleeping rough, their last settled home is where they were living previously.")
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"40" => { "value" => "End of assured shorthold tenancy (no fault)" },
"41" => { "value" => "End of assured shorthold tenancy (eviction or tenant at fault)" },
"42" => { "value" => "End of fixed term tenancy (no fault)" },
"43" => { "value" => "End of fixed term tenancy (eviction or tenant at fault)" },
"1" => { "value" => "Permanently decanted from another property owned by this landlord" },
"46" => { "value" => "Discharged from long-stay hospital or similar institution" },
"45" => { "value" => "Discharged from prison" },
"2" => { "value" => "Left home country as a refugee" },
"4" => { "value" => "Loss of tied accommodation" },
"9" => { "value" => "Asked to leave by family or friends" },
"44" => { "value" => "Death of household member in last settled accommodation" },
"8" => { "value" => "Relationship breakdown (non-violent) with partner" },
"16" => { "value" => "To move nearer to family, friends or school" },
"17" => { "value" => "To move nearer to work" },
"48" => { "value" => "Domestic abuse - previously joint tenancy with partner" },
"49" => { "value" => "Domestic abuse - other" },
"31" => { "value" => "Hate crime" },
"10" => { "value" => "Racial harassment" },
"11" => { "value" => "Other problems with neighbours" },
"35" => { "value" => "Couldn’t afford fees attached to renewing the tenancy" },
"36" => { "value" => "Couldn’t afford increase in rent" },
"38" => { "value" => "Couldn’t afford rent or mortgage (employment)" },
"37" => { "value" => "Couldn’t afford rent or mortgage (welfare reforms)" },
"39" => { "value" => "Couldn’t afford rent or mortgage (other)" },
"34" => { "value" => "Repossession" },
"12" => { "value" => "Property unsuitable because of overcrowding" },
"13" => { "value" => "Property unsuitable because of ill health or disability" },
"14" => { "value" => "Property unsuitable because of poor condition" },
"18" => { "value" => "To move to accommodation with support" },
"19" => { "value" => "To move to independent accommodation" },
"30" => { "value" => "Under occupation (no incentive)" },
"29" => { "value" => "Under occupation (offered incentive to downsize)" },
"20" => { "value" => "Other" },
"47" => { "value" => "Tenant prefers not to say" },
"divider" => { "value" => true },
"28" => { "value" => "Don’t know" },
})
end
end
context "with 2024/25 form" do
before do
allow(form).to receive(:start_year_after_2024?).and_return(true)
end
it "has the correct hint" do
expect(question.hint_text).to eq("The tenant’s ‘last settled home’ is their last long-standing home. For tenants who were in temporary accommodation, sleeping rough or otherwise homeless, their last settled home is where they were living previously.")
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"40" => {
"value" => "End of assured shorthold tenancy (no fault)",
},
"41" => {
"value" => "End of assured shorthold tenancy (eviction or tenant at fault)",
},
"42" => {
"value" => "End of fixed term tenancy (no fault)",
},
"43" => {
"value" => "End of fixed term tenancy (eviction or tenant at fault)",
},
"1" => {
"value" => "Permanently decanted from another property owned by this landlord",
},
"46" => {
"value" => "Discharged from long-stay hospital or similar institution",
},
"45" => {
"value" => "Discharged from prison",
},
"2" => {
"value" => "Left home country as a refugee",
},
"4" => {
"value" => "Loss of tied accommodation",
},
"9" => {
"value" => "Asked to leave by family or friends",
},
"44" => {
"value" => "Death of household member in last settled accommodation",
},
"8" => {
"value" => "Relationship breakdown (non-violent) with partner",
},
"16" => {
"value" => "To move nearer to family, friends or school",
},
"17" => {
"value" => "To move nearer to work",
},
"48" => {
"value" => "Domestic abuse - previously joint tenancy with partner",
},
"49" => {
"value" => "Domestic abuse - other",
},
"31" => {
"value" => "Hate crime",
},
"10" => {
"value" => "Racial harassment",
},
"11" => {
"value" => "Other problems with neighbours",
},
"35" => {
"value" => "Couldn’t afford fees attached to renewing the tenancy",
},
"36" => {
"value" => "Couldn’t afford increase in rent",
},
"38" => {
"value" => "Couldn’t afford rent or mortgage (employment)",
},
"37" => {
"value" => "Couldn’t afford rent or mortgage (welfare reforms)",
},
"39" => {
"value" => "Couldn’t afford rent or mortgage (other)",
},
"34" => {
"value" => "Repossession",
},
"12" => {
"value" => "Property unsuitable because of overcrowding",
},
"13" => {
"value" => "Property unsuitable because of ill health or disability",
},
"14" => {
"value" => "Property unsuitable because of poor condition",
},
"18" => {
"value" => "To move to accommodation with support",
},
"19" => {
"value" => "To move to independent accommodation",
},
"30" => {
"value" => "Under occupation (no incentive)",
},
"29" => {
"value" => "Under occupation (offered incentive to downsize)",
},
"20" => {
"value" => "Other",
},
"47" => {
"value" => "Tenant prefers not to say",
},
"divider" => {
"value" => true,
},
"28" => {
"value" => "Don’t know",
},
"50" => { "value" => "End of social housing tenancy - no fault" },
"51" => { "value" => "End of social housing tenancy - evicted due to anti-social behaviour (ASB)" },
"52" => { "value" => "End of social housing tenancy - evicted due to rent arrears" },
"53" => { "value" => "End of social housing tenancy - evicted for any other reason" },
"1" => { "value" => "Permanently decanted from another property owned by this landlord" },
"2" => { "value" => "Left home country as a refugee" },
"45" => { "value" => "Discharged from prison" },
"46" => { "value" => "Discharged from long-stay hospital or similar institution" },
"4" => { "value" => "Loss of tied accommodation" },
"9" => { "value" => "Asked to leave by family or friends" },
"8" => { "value" => "Relationship breakdown (non-violent) with partner" },
"44" => { "value" => "Death of household member in last settled accommodation" },
"16" => { "value" => "To move nearer to family, friends or school" },
"17" => { "value" => "To move nearer to work" },
"48" => { "value" => "Domestic abuse - previously joint tenancy with partner" },
"49" => { "value" => "Domestic abuse - other" },
"10" => { "value" => "Racial harassment" },
"31" => { "value" => "Hate crime" },
"11" => { "value" => "Other problems with neighbours" },
"34" => { "value" => "Repossession" },
"54" => { "value" => "Could no longer afford rent or mortgage" },
"12" => { "value" => "Property unsuitable because of overcrowding" },
"13" => { "value" => "Property unsuitable because of ill health or disability" },
"14" => { "value" => "Property unsuitable because of poor condition" },
"29" => { "value" => "Under occupation (offered incentive to downsize)" },
"30" => { "value" => "Under occupation (no incentive)" },
"18" => { "value" => "To move to accommodation with support" },
"19" => { "value" => "To move to independent accommodation" },
"20" => { "value" => "Other" },
"28" => { "value" => "Don’t know" },
"divider" => { "value" => true },
"47" => { "value" => "Tenant prefers not to say" },
})
end
end
end

16
spec/models/form/lettings/questions/wheelchair_spec.rb

@ -4,6 +4,12 @@ RSpec.describe Form::Lettings::Questions::Wheelchair, type: :model do
subject(:question) { described_class.new(nil, nil, page) }
let(:page) { instance_double(Form::Page) }
let(:subsection) { instance_double(Form::Subsection) }
before do
allow(page).to receive(:subsection).and_return(subsection)
allow(subsection).to receive(:form).and_return(instance_double(Form, start_year_after_2024?: false))
end
it "has correct page" do
expect(question.page).to eq(page)
@ -39,4 +45,14 @@ RSpec.describe Form::Lettings::Questions::Wheelchair, type: :model do
it "is not marked as derived" do
expect(question.derived?).to be false
end
context "with 2024 form" do
before do
allow(subsection).to receive(:form).and_return(instance_double(Form, start_year_after_2024?: true))
end
it "has the correct hint_text" do
expect(question.hint_text).to eq "This is whether someone who uses a wheelchair is able to make full use of all of the property’s rooms and facilities, including use of both inside and outside space, and entering and exiting the property."
end
end
end

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

@ -7,6 +7,10 @@ RSpec.describe Form::Sales::Pages::AboutPriceRtb, type: :model do
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
before do
allow(subsection).to receive(:form).and_return(instance_double(Form, start_year_after_2024?: false))
end
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end

6
spec/models/form/sales/pages/buyer_live_spec.rb

@ -6,6 +6,12 @@ RSpec.describe Form::Sales::Pages::BuyerLive, type: :model do
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form) }
before do
allow(form).to receive(:start_year_after_2024?).and_return(false)
allow(subsection).to receive(:form).and_return(form)
end
it "has correct subsection" do
expect(page.subsection).to eq(subsection)

61
spec/models/form/sales/pages/buyer_previous_spec.rb

@ -3,11 +3,21 @@ require "rails_helper"
RSpec.describe Form::Sales::Pages::BuyerPrevious, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection, joint_purchase:) }
let(:log) { create(:sales_log, :completed) }
let(:page_id) { "example" }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form) }
let(:joint_purchase) { false }
before do
allow(subsection).to receive(:depends_on).and_return(nil)
allow(subsection).to receive(:enabled?).and_return(true)
allow(subsection).to receive(:form).and_return(form)
allow(form).to receive(:depends_on_met).and_return(true)
end
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
@ -32,13 +42,60 @@ RSpec.describe Form::Sales::Pages::BuyerPrevious, type: :model do
let(:joint_purchase) { true }
it "has the correct depends on" do
expect(page.depends_on).to eq([{ "joint_purchase?" => true }])
expect(page.depends_on).to eq([{ "joint_purchase?" => true, "soctenant_is_inferred?" => false }])
end
end
context "when sales is not a joint purchase" do
it "has the correct depends on" do
expect(page.depends_on).to eq([{ "joint_purchase?" => false }])
expect(page.depends_on).to eq([{ "joint_purchase?" => false, "soctenant_is_inferred?" => false }])
end
end
context "with 23/24 log" do
before do
Timecop.freeze(Time.zone.local(2023, 4, 2))
Singleton.__init__(FormHandler)
end
after do
Timecop.return
end
it "has correct routed to" do
log.staircase = 1
expect(page.routed_to?(log, nil)).to eq(true)
end
end
context "with 24/25 log" do
before do
Timecop.freeze(Time.zone.local(2024, 4, 2))
Singleton.__init__(FormHandler)
end
after do
Timecop.return
end
it "has correct routed to when staircase is yes" do
log.staircase = 1
expect(page.routed_to?(log, nil)).to eq(false)
end
it "has correct routed to when staircase is nil" do
log.staircase = nil
expect(page.routed_to?(log, nil)).to eq(true)
end
it "has correct routed to when staircase is no" do
log.staircase = 2
expect(page.routed_to?(log, nil)).to eq(true)
end
it "has correct routed to when staircase is don't know" do
log.staircase = 3
expect(page.routed_to?(log, nil)).to eq(true)
end
end
end

53
spec/models/form/sales/pages/la_nominations_spec.rb

@ -3,10 +3,16 @@ require "rails_helper"
RSpec.describe Form::Sales::Pages::LaNominations, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:log) { create(:sales_log, :completed) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
before do
allow(subsection).to receive(:depends_on).and_return(nil)
end
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
@ -26,4 +32,51 @@ RSpec.describe Form::Sales::Pages::LaNominations, type: :model do
it "has the correct description" do
expect(page.description).to be_nil
end
context "with 23/24 log" do
before do
Timecop.freeze(Time.zone.local(2023, 4, 2))
Singleton.__init__(FormHandler)
end
after do
Timecop.return
end
it "has correct routed to" do
log.staircase = 1
expect(page.routed_to?(log, nil)).to eq(true)
end
end
context "with 24/25 log" do
before do
Timecop.freeze(Time.zone.local(2024, 4, 2))
Singleton.__init__(FormHandler)
end
after do
Timecop.return
end
it "has correct routed to when staircase is yes" do
log.staircase = 1
expect(page.routed_to?(log, nil)).to eq(false)
end
it "has correct routed to when staircase is nil" do
log.staircase = nil
expect(page.routed_to?(log, nil)).to eq(true)
end
it "has correct routed to when staircase is no" do
log.staircase = 2
expect(page.routed_to?(log, nil)).to eq(true)
end
it "has correct routed to when staircase is don't know" do
log.staircase = 3
expect(page.routed_to?(log, nil)).to eq(true)
end
end
end

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

@ -7,6 +7,10 @@ RSpec.describe Form::Sales::Pages::PropertyWheelchairAccessible, type: :model do
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
before do
allow(subsection).to receive(:form).and_return(instance_double(Form, start_year_after_2024?: false))
end
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end

28
spec/models/form/sales/questions/buyer_live_spec.rb

@ -6,6 +6,14 @@ RSpec.describe Form::Sales::Questions::BuyerLive, type: :model do
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form) }
before do
allow(form).to receive(:start_year_after_2024?).and_return(false)
allow(page).to receive(:subsection).and_return(subsection)
allow(subsection).to receive(:form).and_return(form)
end
it "has correct page" do
expect(question.page).to eq(page)
@ -15,10 +23,6 @@ RSpec.describe Form::Sales::Questions::BuyerLive, type: :model do
expect(question.id).to eq("buylivein")
end
it "has the correct header" do
expect(question.header).to eq("Will the buyers live in the property?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Buyers living in property")
end
@ -37,4 +41,20 @@ RSpec.describe Form::Sales::Questions::BuyerLive, type: :model do
"2" => { "value" => "No" },
})
end
context "with 2023/24 form" do
it "has the correct header" do
expect(question.header).to eq("Will the buyers live in the property?")
end
end
context "with 2024/25 form" do
before do
allow(form).to receive(:start_year_after_2024?).and_return(true)
end
it "has the correct header" do
expect(question.header).to eq("Will any buyers live in the property?")
end
end
end

31
spec/models/form/sales/questions/buyer_previous_spec.rb

@ -6,8 +6,15 @@ RSpec.describe Form::Sales::Questions::BuyerPrevious, type: :model do
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form) }
let(:joint_purchase) { true }
before do
allow(page).to receive(:subsection).and_return(subsection)
allow(subsection).to receive(:form).and_return(form)
end
it "has correct page" do
expect(question.page).to eq(page)
end
@ -42,10 +49,6 @@ RSpec.describe Form::Sales::Questions::BuyerPrevious, type: :model do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct displayed_answer_options" do
expect(question.displayed_answer_options(nil)).to eq({
"1" => { "value" => "Yes" },
@ -68,4 +71,24 @@ RSpec.describe Form::Sales::Questions::BuyerPrevious, type: :model do
it "has the correct hint" do
expect(question.hint_text).to eq(nil)
end
context "when form year is before 2024" do
before do
allow(form).to receive(:start_year_after_2024?).and_return(false)
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
end
context "when form year is >= 2024" do
before do
allow(form).to receive(:start_year_after_2024?).and_return(true)
end
it "is marked as derived" do
expect(question.derived?).to be true
end
end
end

16
spec/models/form/sales/questions/discount_spec.rb

@ -6,6 +6,12 @@ RSpec.describe Form::Sales::Questions::Discount, type: :model do
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
let(:subsection) { instance_double(Form::Subsection) }
before do
allow(page).to receive(:subsection).and_return(subsection)
allow(subsection).to receive(:form).and_return(instance_double(Form, start_year_after_2024?: false))
end
it "has correct page" do
expect(question.page).to eq(page)
@ -52,4 +58,14 @@ RSpec.describe Form::Sales::Questions::Discount, type: :model do
it "has correct max" do
expect(question.max).to eq(100)
end
context "with form start year after 2024" do
before do
allow(subsection).to receive(:form).and_return(instance_double(Form, start_year_after_2024?: true))
end
it "has correct max" do
expect(question.max).to eq(70)
end
end
end

31
spec/models/form/sales/questions/gender_identity1_spec.rb

@ -6,6 +6,13 @@ RSpec.describe Form::Sales::Questions::GenderIdentity1, type: :model do
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form) }
before do
allow(page).to receive(:subsection).and_return(subsection)
allow(subsection).to receive(:form).and_return(form)
end
it "has correct page" do
expect(question.page).to eq(page)
@ -31,10 +38,6 @@ RSpec.describe Form::Sales::Questions::GenderIdentity1, type: :model do
expect(question.derived?).to be false
end
it "has the correct hint" do
expect(question.hint_text).to eq("Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest.")
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"F" => { "value" => "Female" },
@ -47,4 +50,24 @@ RSpec.describe Form::Sales::Questions::GenderIdentity1, type: :model do
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(1)
end
context "with start year before 2024" do
before do
allow(form).to receive(:start_year_after_2024?).and_return(false)
end
it "has the correct hint" do
expect(question.hint_text).to eq("Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest.")
end
end
context "with start year >= 2024" do
before do
allow(form).to receive(:start_year_after_2024?).and_return(true)
end
it "has the correct hint" do
expect(question.hint_text).to eq("This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.")
end
end
end

27
spec/models/form/sales/questions/gender_identity2_spec.rb

@ -6,6 +6,13 @@ RSpec.describe Form::Sales::Questions::GenderIdentity2, type: :model do
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form) }
before do
allow(page).to receive(:subsection).and_return(subsection)
allow(subsection).to receive(:form).and_return(form)
end
it "has correct page" do
expect(question.page).to eq(page)
@ -49,4 +56,24 @@ RSpec.describe Form::Sales::Questions::GenderIdentity2, type: :model do
{ "condition" => { "sex2" => "R" }, "value" => "Prefers not to say" },
])
end
context "with start year before 2024" do
before do
allow(form).to receive(:start_year_after_2024?).and_return(false)
end
it "has the correct hint" do
expect(question.hint_text).to be_nil
end
end
context "with start year >= 2024" do
before do
allow(form).to receive(:start_year_after_2024?).and_return(true)
end
it "has the correct hint" do
expect(question.hint_text).to eq("This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.")
end
end
end

27
spec/models/form/sales/questions/person_gender_identity_spec.rb

@ -7,6 +7,13 @@ RSpec.describe Form::Sales::Questions::PersonGenderIdentity, type: :model do
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
let(:person_index) { 2 }
let(:subsection) { instance_double(Form::Subsection) }
let(:form) { instance_double(Form) }
before do
allow(page).to receive(:subsection).and_return(subsection)
allow(subsection).to receive(:form).and_return(form)
end
it "has correct page" do
expect(question.page).to eq(page)
@ -29,6 +36,26 @@ RSpec.describe Form::Sales::Questions::PersonGenderIdentity, type: :model do
})
end
context "when form year is before 2024" do
before do
allow(form).to receive(:start_year_after_2024?).and_return(false)
end
it "has the correct hint" do
expect(question.hint_text).to be_nil
end
end
context "when form year is >= 2024" do
before do
allow(form).to receive(:start_year_after_2024?).and_return(true)
end
it "has the correct hint" do
expect(question.hint_text).to eq("This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth.")
end
end
context "when person 2" do
let(:question_id) { "sex2" }
let(:person_index) { 2 }

16
spec/models/form/sales/questions/property_wheelchair_accessible_spec.rb

@ -6,6 +6,12 @@ RSpec.describe Form::Sales::Questions::PropertyWheelchairAccessible, type: :mode
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
let(:subsection) { instance_double(Form::Subsection) }
before do
allow(page).to receive(:subsection).and_return(subsection)
allow(subsection).to receive(:form).and_return(instance_double(Form, start_year_after_2024?: false))
end
it "has correct page" do
expect(question.page).to eq(page)
@ -38,4 +44,14 @@ RSpec.describe Form::Sales::Questions::PropertyWheelchairAccessible, type: :mode
"3" => { "value" => "Don't know" },
})
end
context "with 2024 form" do
before do
allow(subsection).to receive(:form).and_return(instance_double(Form, start_year_after_2024?: true))
end
it "has the correct hint_text" do
expect(question.hint_text).to eq("This is whether someone who uses a wheelchair is able to make full use of all of the property’s rooms and facilities, including use of both inside and outside space, and entering and exiting the property.")
end
end
end

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

@ -467,4 +467,293 @@ RSpec.describe Validations::Sales::SaleInformationValidations do
end
end
end
describe "#validate_grant_amount" do
context "when within permitted bounds" do
let(:record) { build(:sales_log, grant: 10_000, saledate: Time.zone.local(2024, 4, 5)) }
it "does not add an error" do
sale_information_validator.validate_grant_amount(record)
expect(record.errors).not_to be_present
end
end
context "when over the max" do
let(:record) { build(:sales_log, type: 8, grant: 17_000, saledate: Time.zone.local(2024, 4, 5)) }
it "adds an error" do
sale_information_validator.validate_grant_amount(record)
expect(record.errors[:grant]).to include("Loan, grants or subsidies must be between £9,000 and £16,000")
end
end
context "when under the min" do
let(:record) { build(:sales_log, type: 21, grant: 3, saledate: Time.zone.local(2024, 4, 5)) }
it "adds an error" do
sale_information_validator.validate_grant_amount(record)
expect(record.errors[:grant]).to include("Loan, grants or subsidies must be between £9,000 and £16,000")
end
end
context "when grant is blank" do
let(:record) { build(:sales_log, type: 21, grant: nil, saledate: Time.zone.local(2024, 4, 5)) }
it "does not add an error" do
sale_information_validator.validate_grant_amount(record)
expect(record.errors).not_to be_present
end
end
context "when over the max and type is not RTA of social homebuy" do
let(:record) { build(:sales_log, type: 9, grant: 17_000, saledate: Time.zone.local(2024, 4, 5)) }
it "does not add an error" do
sale_information_validator.validate_grant_amount(record)
expect(record.errors).not_to be_present
end
end
context "when under the min and type is not RTA of social homebuy" do
let(:record) { build(:sales_log, type: 9, grant: 17_000, saledate: Time.zone.local(2024, 4, 5)) }
it "does not add error" do
sale_information_validator.validate_grant_amount(record)
expect(record.errors).not_to be_present
end
end
context "with log before 2024/25 collection" do
let(:record) { build(:sales_log, type: 8, grant: 3, saledate: Time.zone.local(2023, 4, 5)) }
it "does not add an error" do
sale_information_validator.validate_grant_amount(record)
expect(record.errors).not_to be_present
end
end
end
describe "#validate_stairbought" do
let(:now) { Time.zone.local(2024, 4, 4) }
before do
Timecop.freeze(now)
Singleton.__init__(FormHandler)
end
after do
Timecop.return
Singleton.__init__(FormHandler)
end
[
["Shared Ownership (new model lease)", 30, 90],
["Home Ownership for people with Long-Term Disabilities (HOLD)", 16, 90],
["Rent to Buy — Shared Ownership", 28, 90],
["Right to Shared Ownership (RtSO)", 31, 90],
["London Living Rent — Shared Ownership", 32, 90],
["Shared Ownership (old model lease)", 2, 75],
["Social HomeBuy — shared ownership purchase", 18, 75],
["Older Persons Shared Ownership", 24, 50],
].each do |label, type, max|
context "when ownership type is #{label}" do
let(:record) { build(:sales_log, ownershipsch: 1, type:, saledate: now) }
it "does not add an error if stairbought is under #{max}%" do
record.stairbought = max - 1
sale_information_validator.validate_stairbought(record)
expect(record.errors).to be_empty
end
it "does not add an error if stairbought is #{max}%" do
record.stairbought = max
sale_information_validator.validate_stairbought(record)
expect(record.errors).to be_empty
end
it "does not add an error if stairbought is not given" do
record.stairbought = nil
sale_information_validator.validate_stairbought(record)
expect(record.errors).to be_empty
end
it "adds an error if stairbought is over #{max}%" do
record.stairbought = max + 2
sale_information_validator.validate_stairbought(record)
expect(record.errors[:stairbought]).to include("The percentage bought in this staircasing transaction cannot be higher than #{max}% for #{label} sales.")
expect(record.errors[:type]).to include("The percentage bought in this staircasing transaction cannot be higher than #{max}% for #{label} sales.")
end
end
end
context "when the collection year is before 2024" do
let(:record) { build(:sales_log, ownershipsch: 1, type: 24, saledate: now, stairbought: 90) }
let(:now) { Time.zone.local(2023, 4, 4) }
it "does not add an error" do
sale_information_validator.validate_stairbought(record)
expect(record.errors).to be_empty
end
end
end
describe "#validate_discount_and_value" do
let(:record) { FactoryBot.build(:sales_log, value: 200_000, discount: 50, ownershipsch: 2, type: 9, saledate: now) }
let(:now) { Time.zone.local(2024, 4, 1) }
around do |example|
Timecop.freeze(now) do
example.run
end
Timecop.return
end
context "with a log in the 24/25 collection year" do
context "when in London" do
before do
record.la = "E09000001"
end
it "adds an error if value * discount is more than 136,400" do
record.discount = 80
sale_information_validator.validate_discount_and_value(record)
expect(record.errors["value"]).to include("The percentage discount multiplied by the purchase price is £160,000.00. This figure should not be more than £136,400 for properties in London.")
expect(record.errors["discount"]).to include("The percentage discount multiplied by the purchase price is £160,000.00. This figure should not be more than £136,400 for properties in London.")
expect(record.errors["la"]).to include("The percentage discount multiplied by the purchase price is £160,000.00. This figure should not be more than £136,400 for properties in London.")
expect(record.errors["postcode_full"]).to include("The percentage discount multiplied by the purchase price is £160,000.00. This figure should not be more than £136,400 for properties in London.")
expect(record.errors["uprn"]).to include("The percentage discount multiplied by the purchase price is £160,000.00. This figure should not be more than £136,400 for properties in London.")
end
it "does not add an error value * discount is less than 136,400" do
sale_information_validator.validate_discount_and_value(record)
expect(record.errors["value"]).to be_empty
expect(record.errors["discount"]).to be_empty
expect(record.errors["la"]).to be_empty
expect(record.errors["postcode_full"]).to be_empty
expect(record.errors["uprn"]).to be_empty
end
end
context "when in outside of London" do
before do
record.la = "E06000015"
end
it "adds an error if value * discount is more than 136,400" do
record.discount = 52
sale_information_validator.validate_discount_and_value(record)
expect(record.errors["value"]).to include("The percentage discount multiplied by the purchase price is £104,000.00. This figure should not be more than £102,400 for properties outside of London.")
expect(record.errors["discount"]).to include("The percentage discount multiplied by the purchase price is £104,000.00. This figure should not be more than £102,400 for properties outside of London.")
expect(record.errors["la"]).to include("The percentage discount multiplied by the purchase price is £104,000.00. This figure should not be more than £102,400 for properties outside of London.")
expect(record.errors["postcode_full"]).to include("The percentage discount multiplied by the purchase price is £104,000.00. This figure should not be more than £102,400 for properties outside of London.")
expect(record.errors["uprn"]).to include("The percentage discount multiplied by the purchase price is £104,000.00. This figure should not be more than £102,400 for properties outside of London.")
end
it "does not add an error value * discount is less than 136,400" do
sale_information_validator.validate_discount_and_value(record)
expect(record.errors["value"]).to be_empty
expect(record.errors["discount"]).to be_empty
expect(record.errors["la"]).to be_empty
expect(record.errors["postcode_full"]).to be_empty
expect(record.errors["uprn"]).to be_empty
end
end
end
context "when it is a 2023 log" do
let(:record) { FactoryBot.build(:sales_log, value: 200_000, discount: 80, ownershipsch: 2, type: 9, saledate: Time.zone.local(2023, 4, 1), la: "E06000015") }
it "does not add an error" do
sale_information_validator.validate_discount_and_value(record)
expect(record.errors["value"]).to be_empty
expect(record.errors["discount"]).to be_empty
expect(record.errors["la"]).to be_empty
expect(record.errors["postcode_full"]).to be_empty
expect(record.errors["uprn"]).to be_empty
end
end
end
describe "#validate_non_staircasing_mortgage" do
let(:record) { FactoryBot.build(:sales_log, mortgage: 10_000, deposit: 5_000, value: 30_000, equity: 28, ownershipsch: 1, type: 30, saledate: now) }
around do |example|
Timecop.freeze(now) do
Singleton.__init__(FormHandler)
example.run
end
Timecop.return
Singleton.__init__(FormHandler)
end
context "with a log in the 24/25 collection year" do
let(:now) { Time.zone.local(2024, 4, 4) }
context "when MORTGAGE + DEPOSIT does not equal VALUE * EQUITY/100 " do
context "and it is not a staircase transaction" do
before do
record.staircase = 2
end
it "adds an error" do
sale_information_validator.validate_non_staircasing_mortgage(record)
expect(record.errors["mortgage"]).to include("The mortgage and deposit added together is £15,000.00 and the purchase price times by the equity is £8,400.00. These figures should be the same.")
expect(record.errors["value"]).to include("The mortgage and deposit added together is £15,000.00 and the purchase price times by the equity is £8,400.00. These figures should be the same.")
expect(record.errors["deposit"]).to include("The mortgage and deposit added together is £15,000.00 and the purchase price times by the equity is £8,400.00. These figures should be the same.")
expect(record.errors["equity"]).to include("The mortgage and deposit added together is £15,000.00 and the purchase price times by the equity is £8,400.00. These figures should be the same.")
end
end
context "and it is a staircase transaction" do
before do
record.staircase = 1
end
it "does not add an error" do
sale_information_validator.validate_non_staircasing_mortgage(record)
expect(record.errors["mortgage"]).to be_empty
expect(record.errors["value"]).to be_empty
expect(record.errors["deposit"]).to be_empty
expect(record.errors["equity"]).to be_empty
end
end
end
context "when MORTGAGE + DEPOSIT equals VALUE * EQUITY/100" do
let(:record) { FactoryBot.build(:sales_log, mortgage: 10_000, staircase: 2, deposit: 5_000, value: 30_000, equity: 50, ownershipsch: 1, type: 30, saledate: now) }
it "does not add an error" do
sale_information_validator.validate_non_staircasing_mortgage(record)
expect(record.errors["mortgage"]).to be_empty
expect(record.errors["value"]).to be_empty
expect(record.errors["deposit"]).to be_empty
expect(record.errors["equity"]).to be_empty
end
end
end
context "when it is a 2023 log" do
let(:now) { Time.zone.local(2023, 4, 1) }
let(:record) { FactoryBot.build(:sales_log, mortgage: 10_000, staircase: 2, deposit: 5_000, value: 30_000, equity: 28, ownershipsch: 1, type: 30, saledate: now) }
it "does not add an error" do
sale_information_validator.validate_non_staircasing_mortgage(record)
expect(record.errors["mortgage"]).to be_empty
expect(record.errors["value"]).to be_empty
expect(record.errors["deposit"]).to be_empty
expect(record.errors["equity"]).to be_empty
end
end
end
end

30
spec/models/validations/sales/soft_validations_spec.rb

@ -660,21 +660,51 @@ RSpec.describe Validations::Sales::SoftValidations do
describe "#grant_outside_common_range?" do
it "returns true if grant is below 9000" do
record.grant = 1_000
record.type = 9
record.saledate = Time.zone.local(2024, 1, 1)
expect(record).to be_grant_outside_common_range
end
it "returns true if grant is above 16000" do
record.grant = 100_000
record.type = 9
record.saledate = Time.zone.local(2024, 1, 1)
expect(record).to be_grant_outside_common_range
end
it "returns false if grant is within expected range" do
record.grant = 10_000
record.type = 9
record.saledate = Time.zone.local(2024, 1, 1)
expect(record).not_to be_grant_outside_common_range
end
it "returns false for logs after 2024 with RTA" do
record.grant = 100_000
record.type = 8
record.saledate = Time.zone.local(2025, 1, 1)
expect(record).not_to be_grant_outside_common_range
end
it "returns false for logs after 2024 with socialBuy" do
record.grant = 100_000
record.type = 21
record.saledate = Time.zone.local(2025, 1, 1)
expect(record).not_to be_grant_outside_common_range
end
it "returns true for logs after 2024 with other type" do
record.grant = 100_000
record.type = 9
record.saledate = Time.zone.local(2025, 1, 1)
expect(record).to be_grant_outside_common_range
end
end
describe "#staircase_bought_above_fifty" do

6
spec/requests/bulk_upload_lettings_logs_controller_spec.rb

@ -49,7 +49,7 @@ RSpec.describe BulkUploadLettingsLogsController, type: :request do
it "shows guidance page with correct title" do
Timecop.freeze(2022, 1, 1) do
get "/lettings-logs/bulk-upload-logs/guidance?form%5Byear%5D=#{expected_year}", params: {}
get "/lettings-logs/bulk-upload-logs/guidance?form%5Byear%5D=2022", params: {}
expect(response.body).to include("How to upload logs in bulk")
end
@ -57,11 +57,9 @@ RSpec.describe BulkUploadLettingsLogsController, type: :request do
end
context "when in crossover period" do
let(:expected_year) { FormHandler.instance.forms["current_sales"].start_date.year }
it "shows guidance page with correct title" do
Timecop.freeze(2023, 6, 1) do
get "/lettings-logs/bulk-upload-logs/guidance?form%5Byear%5D=#{expected_year}", params: {}
get "/lettings-logs/bulk-upload-logs/guidance?form%5Byear%5D=2023", params: {}
expect(response.body).to include("How to upload logs in bulk")
end

7
spec/requests/bulk_upload_sales_logs_controller_spec.rb

@ -45,11 +45,10 @@ RSpec.describe BulkUploadSalesLogsController, type: :request do
describe "GET /sales-logs/bulk-upload-logs/guidance" do
context "when not in crossover period" do
let(:expected_year) { FormHandler.instance.forms["current_sales"].start_date.year }
it "shows guidance page with correct title" do
Timecop.freeze(2022, 1, 1) do
get "/sales-logs/bulk-upload-logs/guidance?form%5Byear%5D=#{expected_year}", params: {}
get "/sales-logs/bulk-upload-logs/guidance?form%5Byear%5D=2022", params: {}
expect(response.body).to include("How to upload logs in bulk")
end
@ -57,11 +56,9 @@ RSpec.describe BulkUploadSalesLogsController, type: :request do
end
context "when in crossover period" do
let(:expected_year) { FormHandler.instance.forms["current_sales"].start_date.year }
it "shows guidance page with correct title" do
Timecop.freeze(2023, 6, 1) do
get "/sales-logs/bulk-upload-logs/guidance?form%5Byear%5D=#{expected_year}", params: {}
get "/sales-logs/bulk-upload-logs/guidance?form%5Byear%5D=2023", params: {}
expect(response.body).to include("How to upload logs in bulk")
end

18
spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb

@ -1816,6 +1816,24 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
end
end
describe "#beds" do
context "when property is a bedsit" do
let(:attributes) { setup_section_params.merge({ field_26: 2, field_29: 2 }) }
it "sets value to 1 even if field_29 contradicts this" do
expect(parser.log.beds).to be(1)
end
end
context "when property is not a bedsit" do
let(:attributes) { setup_section_params.merge({ field_26: 1, field_29: 2 }) }
it "sets value to field_29" do
expect(parser.log.beds).to be(2)
end
end
end
describe "#cbl" do
context "when field_112 is yes ie 1" do
let(:attributes) { { bulk_upload:, field_112: 1 } }

50
spec/services/bulk_upload/sales/year2024/row_parser_spec.rb

@ -1264,13 +1264,59 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do
end
describe "#soctenant" do
let(:attributes) { setup_section_params.merge({ field_99: "1" }) }
context "when discounted ownership" do
let(:attributes) { valid_attributes.merge({ field_8: "2" }) }
it "is correctly set" do
it "is set to nil" do
expect(parser.log.soctenant).to be_nil
end
end
context "when outright sale" do
let(:attributes) { valid_attributes.merge({ field_8: "3" }) }
it "is set to nil" do
expect(parser.log.soctenant).to be_nil
end
end
context "when shared ownership" do
context "when prevten is a social housing type" do
let(:attributes) { valid_attributes.merge({ field_8: "1", field_61: "1" }) }
it "is set to yes" do
expect(parser.log.soctenant).to be(1)
end
end
context "when prevten is not a social housing type" do
context "and prevtenbuy2 is a social housing type" do
let(:attributes) { valid_attributes.merge({ field_8: "1", field_61: "3", field_71: "2" }) }
it "is set to yes" do
expect(parser.log.soctenant).to be(1)
end
end
context "and prevtenbuy2 is not a social housing type" do
let(:attributes) { valid_attributes.merge({ field_8: "1", field_61: "3", field_71: "4" }) }
it "is set to no" do
expect(parser.log.soctenant).to be(2)
end
end
context "and prevtenbuy2 is blank" do
let(:attributes) { valid_attributes.merge({ field_8: "1", field_61: "3", field_71: nil }) }
it "is set to no" do
expect(parser.log.soctenant).to be(2)
end
end
end
end
end
describe "with living before purchase years for shared ownership more than 0" do
let(:attributes) { setup_section_params.merge({ field_8: "1", field_85: "1" }) }

Loading…
Cancel
Save