Browse Source

Merge branch 'main' into Q29

pull/2887/head
Rachael Booth 4 months ago
parent
commit
b73c3f1d5a
  1. 2
      app/models/derived_variables/lettings_log_variables.rb
  2. 2
      app/models/derived_variables/sales_log_variables.rb
  3. 12
      app/models/form/lettings/pages/person_lead_partner.rb
  4. 19
      app/models/form/lettings/pages/rent_4_weekly.rb
  5. 19
      app/models/form/lettings/pages/rent_bi_weekly.rb
  6. 19
      app/models/form/lettings/pages/rent_monthly.rb
  7. 19
      app/models/form/lettings/pages/rent_weekly.rb
  8. 30
      app/models/form/lettings/questions/person_partner.rb
  9. 22
      app/models/form/lettings/subsections/household_characteristics.rb
  10. 22
      app/models/form/lettings/subsections/income_and_benefits.rb
  11. 2
      app/models/form/question.rb
  12. 2
      app/models/forms/bulk_upload_form/prepare_your_file.rb
  13. 6
      app/models/lettings_log.rb
  14. 29
      app/models/sales_log.rb
  15. 53
      app/models/validations/date_validations.rb
  16. 2
      app/models/validations/financial_validations.rb
  17. 2
      app/models/validations/household_validations.rb
  18. 2
      app/models/validations/sales/financial_validations.rb
  19. 2
      app/models/validations/sales/household_validations.rb
  20. 4
      app/models/validations/sales/sale_information_validations.rb
  21. 11
      app/models/validations/shared_validations.rb
  22. 4
      app/models/validations/soft_validations.rb
  23. 46
      app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2023.html.erb
  24. 40
      app/views/bulk_upload_sales_logs/forms/prepare_your_file_2023.html.erb
  25. 15
      app/views/bulk_upload_shared/guidance.html.erb
  26. 28
      config/locales/forms/2025/lettings/household_characteristics.en.yml
  27. 4
      config/locales/validations/lettings/date.en.yml
  28. 20
      config/locales/validations/sales/sale_information.en.yml
  29. 2
      config/locales/validations/shared.en.yml
  30. 14
      spec/features/bulk_upload_sales_logs_spec.rb
  31. 51
      spec/models/form/lettings/pages/person_lead_partner_spec.rb
  32. 44
      spec/models/form/lettings/pages/rent4_weekly_spec.rb
  33. 44
      spec/models/form/lettings/pages/rent_bi_weekly_spec.rb
  34. 44
      spec/models/form/lettings/pages/rent_monthly_spec.rb
  35. 44
      spec/models/form/lettings/pages/rent_weekly_spec.rb
  36. 57
      spec/models/form/lettings/questions/person_partner_spec.rb
  37. 156
      spec/models/form/lettings/subsections/household_characteristics_spec.rb
  38. 39
      spec/models/form/lettings/subsections/income_and_benefits_spec.rb
  39. 8
      spec/models/sales_log_spec.rb
  40. 83
      spec/models/validations/date_validations_spec.rb
  41. 14
      spec/models/validations/financial_validations_spec.rb
  42. 38
      spec/models/validations/sales/sale_information_validations_spec.rb
  43. 74
      spec/models/validations/shared_validations_spec.rb
  44. 1
      spec/requests/bulk_upload_lettings_logs_controller_spec.rb
  45. 1
      spec/requests/bulk_upload_sales_logs_controller_spec.rb
  46. 9
      spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb

2
app/models/derived_variables/lettings_log_variables.rb

@ -346,7 +346,7 @@ private
end end
def address_answered_without_uprn? def address_answered_without_uprn?
[address_line1, town_or_city].all?(&:present?) && uprn.nil? && form.start_date.year >= 2023 [address_line1, town_or_city].all?(&:present?) && uprn.nil?
end end
def get_lar def get_lar

2
app/models/derived_variables/sales_log_variables.rb

@ -226,7 +226,7 @@ private
end end
def address_answered_without_uprn? def address_answered_without_uprn?
[address_line1, town_or_city].all?(&:present?) && uprn.nil? && form.start_date.year >= 2023 [address_line1, town_or_city].all?(&:present?) && uprn.nil?
end end
def soctenant_from_prevten_values def soctenant_from_prevten_values

12
app/models/form/lettings/pages/person_lead_partner.rb

@ -0,0 +1,12 @@
class Form::Lettings::Pages::PersonLeadPartner < ::Form::Page
def initialize(id, hsh, subsection, person_index:)
super(id, hsh, subsection)
@id = "person_#{person_index}_lead_partner"
@depends_on = [{ "details_known_#{person_index}" => 0 }]
@person_index = person_index
end
def questions
@questions ||= [Form::Lettings::Questions::PersonPartner.new(nil, nil, self, person_index: @person_index)]
end
end

19
app/models/form/lettings/pages/rent_4_weekly.rb

@ -3,10 +3,7 @@ class Form::Lettings::Pages::Rent4Weekly < ::Form::Page
super super
@id = "rent_4_weekly" @id = "rent_4_weekly"
@copy_key = "lettings.income_and_benefits.rent_and_charges" @copy_key = "lettings.income_and_benefits.rent_and_charges"
@depends_on = [ @depends_on = depends_on
{ "household_charge" => 0, "rent_and_charges_paid_every_4_weeks?" => true, "is_carehome?" => false },
{ "household_charge" => nil, "rent_and_charges_paid_every_4_weeks?" => true, "is_carehome?" => false },
]
end end
def questions def questions
@ -18,4 +15,18 @@ class Form::Lettings::Pages::Rent4Weekly < ::Form::Page
Form::Lettings::Questions::Tcharge4Weekly.new(nil, nil, self), Form::Lettings::Questions::Tcharge4Weekly.new(nil, nil, self),
] ]
end end
def depends_on
if form.start_year_2025_or_later?
[
{ "household_charge" => 0, "rent_and_charges_paid_every_4_weeks?" => true },
{ "household_charge" => nil, "rent_and_charges_paid_every_4_weeks?" => true },
]
else
[
{ "household_charge" => 0, "rent_and_charges_paid_every_4_weeks?" => true, "is_carehome?" => false },
{ "household_charge" => nil, "rent_and_charges_paid_every_4_weeks?" => true, "is_carehome?" => false },
]
end
end
end end

19
app/models/form/lettings/pages/rent_bi_weekly.rb

@ -3,10 +3,7 @@ class Form::Lettings::Pages::RentBiWeekly < ::Form::Page
super super
@id = "rent_bi_weekly" @id = "rent_bi_weekly"
@copy_key = "lettings.income_and_benefits.rent_and_charges" @copy_key = "lettings.income_and_benefits.rent_and_charges"
@depends_on = [ @depends_on = depends_on
{ "household_charge" => nil, "rent_and_charges_paid_every_2_weeks?" => true, "is_carehome?" => false },
{ "household_charge" => 0, "rent_and_charges_paid_every_2_weeks?" => true, "is_carehome?" => false },
]
end end
def questions def questions
@ -18,4 +15,18 @@ class Form::Lettings::Pages::RentBiWeekly < ::Form::Page
Form::Lettings::Questions::TchargeBiWeekly.new(nil, nil, self), Form::Lettings::Questions::TchargeBiWeekly.new(nil, nil, self),
] ]
end end
def depends_on
if form.start_year_2025_or_later?
[
{ "household_charge" => nil, "rent_and_charges_paid_every_2_weeks?" => true },
{ "household_charge" => 0, "rent_and_charges_paid_every_2_weeks?" => true },
]
else
[
{ "household_charge" => nil, "rent_and_charges_paid_every_2_weeks?" => true, "is_carehome?" => false },
{ "household_charge" => 0, "rent_and_charges_paid_every_2_weeks?" => true, "is_carehome?" => false },
]
end
end
end end

19
app/models/form/lettings/pages/rent_monthly.rb

@ -3,10 +3,7 @@ class Form::Lettings::Pages::RentMonthly < ::Form::Page
super super
@id = "rent_monthly" @id = "rent_monthly"
@copy_key = "lettings.income_and_benefits.rent_and_charges" @copy_key = "lettings.income_and_benefits.rent_and_charges"
@depends_on = [ @depends_on = depends_on
{ "household_charge" => nil, "rent_and_charges_paid_monthly?" => true, "is_carehome?" => false },
{ "household_charge" => 0, "rent_and_charges_paid_monthly?" => true, "is_carehome?" => false },
]
end end
def questions def questions
@ -18,4 +15,18 @@ class Form::Lettings::Pages::RentMonthly < ::Form::Page
Form::Lettings::Questions::TchargeMonthly.new(nil, nil, self), Form::Lettings::Questions::TchargeMonthly.new(nil, nil, self),
] ]
end end
def depends_on
if form.start_year_2025_or_later?
[
{ "household_charge" => nil, "rent_and_charges_paid_monthly?" => true },
{ "household_charge" => 0, "rent_and_charges_paid_monthly?" => true },
]
else
[
{ "household_charge" => nil, "rent_and_charges_paid_monthly?" => true, "is_carehome?" => false },
{ "household_charge" => 0, "rent_and_charges_paid_monthly?" => true, "is_carehome?" => false },
]
end
end
end end

19
app/models/form/lettings/pages/rent_weekly.rb

@ -3,10 +3,7 @@ class Form::Lettings::Pages::RentWeekly < ::Form::Page
super super
@id = "rent_weekly" @id = "rent_weekly"
@copy_key = "lettings.income_and_benefits.rent_and_charges" @copy_key = "lettings.income_and_benefits.rent_and_charges"
@depends_on = [ @depends_on = depends_on
{ "rent_and_charges_paid_weekly?" => true, "household_charge" => 0, "is_carehome?" => false },
{ "rent_and_charges_paid_weekly?" => true, "household_charge" => nil, "is_carehome?" => false },
]
end end
def questions def questions
@ -18,4 +15,18 @@ class Form::Lettings::Pages::RentWeekly < ::Form::Page
Form::Lettings::Questions::TchargeWeekly.new(nil, nil, self), Form::Lettings::Questions::TchargeWeekly.new(nil, nil, self),
] ]
end end
def depends_on
if form.start_year_2025_or_later?
[
{ "rent_and_charges_paid_weekly?" => true, "household_charge" => 0 },
{ "rent_and_charges_paid_weekly?" => true, "household_charge" => nil },
]
else
[
{ "rent_and_charges_paid_weekly?" => true, "household_charge" => 0, "is_carehome?" => false },
{ "rent_and_charges_paid_weekly?" => true, "household_charge" => nil, "is_carehome?" => false },
]
end
end
end end

30
app/models/form/lettings/questions/person_partner.rb

@ -0,0 +1,30 @@
class Form::Lettings::Questions::PersonPartner < ::Form::Question
def initialize(id, hsh, page, person_index:)
super(id, hsh, page)
@id = "relat#{person_index}"
@type = "radio"
@check_answers_card_number = person_index
@answer_options = answer_options
@person_index = person_index
@question_number = question_number
end
def answer_options
{
"P" => { "value" => "Yes" },
"X" => { "value" => "No" },
"R" => { "value" => "Tenant prefers not to say" },
}
end
def question_number
base_question_number = case form.start_date.year
when 2023
30
else
29
end
base_question_number + (4 * @person_index)
end
end

22
app/models/form/lettings/subsections/household_characteristics.rb

@ -32,7 +32,7 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::LeadTenantUnderRetirementValueCheck.new("working_situation_lead_tenant_under_retirement_value_check", nil, self), Form::Lettings::Pages::LeadTenantUnderRetirementValueCheck.new("working_situation_lead_tenant_under_retirement_value_check", nil, self),
Form::Lettings::Pages::LeadTenantOverRetirementValueCheck.new("working_situation_lead_tenant_over_retirement_value_check", nil, self), Form::Lettings::Pages::LeadTenantOverRetirementValueCheck.new("working_situation_lead_tenant_over_retirement_value_check", nil, self),
Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 2), Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 2),
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 2), relationship_question(person_index: 2),
(Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_2_partner_under_16_value_check", nil, self, person_index: 2) if form.start_year_2024_or_later?), (Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_2_partner_under_16_value_check", nil, self, person_index: 2) if form.start_year_2024_or_later?),
(Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_2_multiple_partners_value_check", nil, self, person_index: 2) if form.start_year_2024_or_later?), (Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_2_multiple_partners_value_check", nil, self, person_index: 2) if form.start_year_2024_or_later?),
Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 2), Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 2),
@ -52,7 +52,7 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_2_under_retirement_value_check", nil, self, person_index: 2), Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_2_under_retirement_value_check", nil, self, person_index: 2),
Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_2_over_retirement_value_check", nil, self, person_index: 2), Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_2_over_retirement_value_check", nil, self, person_index: 2),
Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 3), Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 3),
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 3), relationship_question(person_index: 3),
(Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_3_partner_under_16_value_check", nil, self, person_index: 3) if form.start_year_2024_or_later?), (Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_3_partner_under_16_value_check", nil, self, person_index: 3) if form.start_year_2024_or_later?),
(Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_3_multiple_partners_value_check", nil, self, person_index: 3) if form.start_year_2024_or_later?), (Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_3_multiple_partners_value_check", nil, self, person_index: 3) if form.start_year_2024_or_later?),
Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 3), Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 3),
@ -72,7 +72,7 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_3_under_retirement_value_check", nil, self, person_index: 3), Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_3_under_retirement_value_check", nil, self, person_index: 3),
Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_3_over_retirement_value_check", nil, self, person_index: 3), Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_3_over_retirement_value_check", nil, self, person_index: 3),
Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 4), Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 4),
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 4), relationship_question(person_index: 4),
(Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_4_partner_under_16_value_check", nil, self, person_index: 4) if form.start_year_2024_or_later?), (Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_4_partner_under_16_value_check", nil, self, person_index: 4) if form.start_year_2024_or_later?),
(Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_4_multiple_partners_value_check", nil, self, person_index: 4) if form.start_year_2024_or_later?), (Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_4_multiple_partners_value_check", nil, self, person_index: 4) if form.start_year_2024_or_later?),
Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 4), Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 4),
@ -92,7 +92,7 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_4_under_retirement_value_check", nil, self, person_index: 4), Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_4_under_retirement_value_check", nil, self, person_index: 4),
Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_4_over_retirement_value_check", nil, self, person_index: 4), Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_4_over_retirement_value_check", nil, self, person_index: 4),
Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 5), Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 5),
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 5), relationship_question(person_index: 5),
(Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_5_partner_under_16_value_check", nil, self, person_index: 5) if form.start_year_2024_or_later?), (Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_5_partner_under_16_value_check", nil, self, person_index: 5) if form.start_year_2024_or_later?),
(Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_5_multiple_partners_value_check", nil, self, person_index: 5) if form.start_year_2024_or_later?), (Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_5_multiple_partners_value_check", nil, self, person_index: 5) if form.start_year_2024_or_later?),
Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 5), Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 5),
@ -112,7 +112,7 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_5_under_retirement_value_check", nil, self, person_index: 5), Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_5_under_retirement_value_check", nil, self, person_index: 5),
Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_5_over_retirement_value_check", nil, self, person_index: 5), Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_5_over_retirement_value_check", nil, self, person_index: 5),
Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 6), Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 6),
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 6), relationship_question(person_index: 6),
(Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_6_partner_under_16_value_check", nil, self, person_index: 6) if form.start_year_2024_or_later?), (Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_6_partner_under_16_value_check", nil, self, person_index: 6) if form.start_year_2024_or_later?),
(Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_6_multiple_partners_value_check", nil, self, person_index: 6) if form.start_year_2024_or_later?), (Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_6_multiple_partners_value_check", nil, self, person_index: 6) if form.start_year_2024_or_later?),
Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 6), Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 6),
@ -132,7 +132,7 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_6_under_retirement_value_check", nil, self, person_index: 6), Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_6_under_retirement_value_check", nil, self, person_index: 6),
Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_6_over_retirement_value_check", nil, self, person_index: 6), Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_6_over_retirement_value_check", nil, self, person_index: 6),
Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 7), Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 7),
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 7), relationship_question(person_index: 7),
(Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_7_partner_under_16_value_check", nil, self, person_index: 7) if form.start_year_2024_or_later?), (Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_7_partner_under_16_value_check", nil, self, person_index: 7) if form.start_year_2024_or_later?),
(Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_7_multiple_partners_value_check", nil, self, person_index: 7) if form.start_year_2024_or_later?), (Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_7_multiple_partners_value_check", nil, self, person_index: 7) if form.start_year_2024_or_later?),
Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 7), Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 7),
@ -152,7 +152,7 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_7_under_retirement_value_check", nil, self, person_index: 7), Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_7_under_retirement_value_check", nil, self, person_index: 7),
Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_7_over_retirement_value_check", nil, self, person_index: 7), Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_7_over_retirement_value_check", nil, self, person_index: 7),
Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 8), Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 8),
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 8), relationship_question(person_index: 8),
(Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_8_partner_under_16_value_check", nil, self, person_index: 8) if form.start_year_2024_or_later?), (Form::Lettings::Pages::PartnerUnder16ValueCheck.new("relationship_8_partner_under_16_value_check", nil, self, person_index: 8) if form.start_year_2024_or_later?),
(Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_8_multiple_partners_value_check", nil, self, person_index: 8) if form.start_year_2024_or_later?), (Form::Lettings::Pages::MultiplePartnersValueCheck.new("relationship_8_multiple_partners_value_check", nil, self, person_index: 8) if form.start_year_2024_or_later?),
Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 8), Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 8),
@ -173,4 +173,12 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_8_over_retirement_value_check", nil, self, person_index: 8), Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_8_over_retirement_value_check", nil, self, person_index: 8),
].compact ].compact
end end
def relationship_question(person_index:)
if form.start_year_2025_or_later?
Form::Lettings::Pages::PersonLeadPartner.new(nil, nil, self, person_index:)
else
Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index:)
end
end
end end

22
app/models/form/lettings/subsections/income_and_benefits.rb

@ -15,11 +15,7 @@ class Form::Lettings::Subsections::IncomeAndBenefits < ::Form::Subsection
Form::Lettings::Pages::BenefitsProportion.new("benefits_proportion", nil, self), Form::Lettings::Pages::BenefitsProportion.new("benefits_proportion", nil, self),
Form::Lettings::Pages::RentOrOtherCharges.new(nil, nil, self), Form::Lettings::Pages::RentOrOtherCharges.new(nil, nil, self),
Form::Lettings::Pages::RentPeriod.new(nil, nil, self), Form::Lettings::Pages::RentPeriod.new(nil, nil, self),
Form::Lettings::Pages::CareHomeWeekly.new(nil, nil, self), carehome_questions,
Form::Lettings::Pages::CareHomeBiWeekly.new(nil, nil, self),
Form::Lettings::Pages::CareHome4Weekly.new(nil, nil, self),
Form::Lettings::Pages::CareHomeMonthly.new(nil, nil, self),
Form::Lettings::Pages::CareHomeChargesValueCheck.new(nil, nil, self),
Form::Lettings::Pages::RentWeekly.new(nil, nil, self), Form::Lettings::Pages::RentWeekly.new(nil, nil, self),
Form::Lettings::Pages::RentBiWeekly.new(nil, nil, self), Form::Lettings::Pages::RentBiWeekly.new(nil, nil, self),
Form::Lettings::Pages::Rent4Weekly.new(nil, nil, self), Form::Lettings::Pages::Rent4Weekly.new(nil, nil, self),
@ -30,6 +26,20 @@ class Form::Lettings::Subsections::IncomeAndBenefits < ::Form::Subsection
Form::Lettings::Pages::SupchargValueCheck.new(nil, nil, self), Form::Lettings::Pages::SupchargValueCheck.new(nil, nil, self),
Form::Lettings::Pages::Outstanding.new(nil, nil, self), Form::Lettings::Pages::Outstanding.new(nil, nil, self),
Form::Lettings::Pages::OutstandingAmount.new(nil, nil, self), Form::Lettings::Pages::OutstandingAmount.new(nil, nil, self),
].compact ].flatten.compact
end
private
def carehome_questions
return [] if form.start_year_2025_or_later?
[
Form::Lettings::Pages::CareHomeWeekly.new(nil, nil, self),
Form::Lettings::Pages::CareHomeBiWeekly.new(nil, nil, self),
Form::Lettings::Pages::CareHome4Weekly.new(nil, nil, self),
Form::Lettings::Pages::CareHomeMonthly.new(nil, nil, self),
Form::Lettings::Pages::CareHomeChargesValueCheck.new(nil, nil, self),
]
end end
end end

2
app/models/form/question.rb

@ -276,7 +276,7 @@ class Form::Question
end end
def question_number_string(hidden: false) def question_number_string(hidden: false)
if @question_number && !hidden && form.start_date.year >= 2023 if @question_number && !hidden
"Q#{@question_number}" "Q#{@question_number}"
end end
end end

2
app/models/forms/bulk_upload_form/prepare_your_file.rb

@ -11,8 +11,6 @@ module Forms
def view_path def view_path
case year case year
when 2023
"bulk_upload_#{log_type}_logs/forms/prepare_your_file_2023"
when 2024 when 2024
"bulk_upload_#{log_type}_logs/forms/prepare_your_file_2024" "bulk_upload_#{log_type}_logs/forms/prepare_your_file_2024"
end end

6
app/models/lettings_log.rb

@ -240,7 +240,7 @@ class LettingsLog < Log
end end
def applicable_income_range def applicable_income_range
return unless ecstat1 && hhmemb return unless ecstat1 && hhmemb && ALLOWED_INCOME_RANGES[ecstat1]
range = ALLOWED_INCOME_RANGES[ecstat1].clone range = ALLOWED_INCOME_RANGES[ecstat1].clone
@ -670,8 +670,7 @@ class LettingsLog < Log
["owning_organisation_id", ["owning_organisation_id",
"startdate", "startdate",
"tenancycode", "tenancycode",
form.start_date.year < 2023 || uprn.blank? ? "postcode_full" : nil, uprn.blank? ? "postcode_full" : "uprn",
form.start_date.year >= 2023 && uprn.present? ? "uprn" : nil,
"scheme_id", "scheme_id",
"location_id", "location_id",
"age1", "age1",
@ -884,7 +883,6 @@ private
def should_process_uprn_change? def should_process_uprn_change?
return unless uprn return unless uprn
return unless startdate return unless startdate
return unless collection_start_year_for_date(startdate) >= 2023
uprn_changed? || startdate_changed? uprn_changed? || startdate_changed?
end end

29
app/models/sales_log.rb

@ -129,33 +129,12 @@ class SalesLog < Log
def dynamically_not_required def dynamically_not_required
not_required = [] not_required = []
not_required << "proplen" if proplen_optional?
not_required << "mortlen" if mortlen_optional?
not_required << "frombeds" if frombeds_optional?
not_required << "deposit" if form.start_year_2024_or_later? && stairowned_100? not_required << "deposit" if form.start_year_2024_or_later? && stairowned_100?
not_required += %w[address_line2 county] not_required += %w[address_line2 county]
not_required not_required
end end
def proplen_optional?
return false unless collection_start_year
collection_start_year < 2023
end
def mortlen_optional?
return false unless collection_start_year
collection_start_year < 2023
end
def frombeds_optional?
return false unless collection_start_year
collection_start_year < 2023
end
def unresolved def unresolved
false false
end end
@ -440,16 +419,15 @@ class SalesLog < Log
def income_soft_min_for_ecstat(ecstat_field) def income_soft_min_for_ecstat(ecstat_field)
economic_status_code = public_send(ecstat_field) economic_status_code = public_send(ecstat_field)
return unless ALLOWED_INCOME_RANGES_SALES return unless ALLOWED_INCOME_RANGES_SALES && ALLOWED_INCOME_RANGES_SALES[economic_status_code]
soft_min = ALLOWED_INCOME_RANGES_SALES[economic_status_code]&.soft_min soft_min = ALLOWED_INCOME_RANGES_SALES[economic_status_code].soft_min
format_as_currency(soft_min) format_as_currency(soft_min)
end end
def should_process_uprn_change? def should_process_uprn_change?
return unless uprn return unless uprn
return unless saledate return unless saledate
return unless collection_start_year_for_date(saledate) >= 2023
uprn_changed? || saledate_changed? uprn_changed? || saledate_changed?
end end
@ -514,8 +492,7 @@ class SalesLog < Log
"age1", "age1",
"sex1", "sex1",
"ecstat1", "ecstat1",
form.start_date.year < 2023 || uprn.blank? ? "postcode_full" : nil, uprn.blank? ? "postcode_full" : "uprn"].compact
form.start_date.year >= 2023 && uprn.present? ? "uprn" : nil].compact
end end
def soctenant_is_inferred? def soctenant_is_inferred?

53
app/models/validations/date_validations.rb

@ -2,53 +2,56 @@ module Validations::DateValidations
include Validations::SharedValidations include Validations::SharedValidations
def validate_property_major_repairs(record) def validate_property_major_repairs(record)
date_valid?("mrcdate", record) return unless record["mrcdate"].present? && date_valid?("mrcdate", record)
if record["startdate"].present? && record["mrcdate"].present? && record["startdate"] < record["mrcdate"]
record.errors.add :mrcdate, I18n.t("validations.lettings.date.mrcdate.before_tenancy_start")
end
if is_rsnvac_first_let?(record) && record["mrcdate"].present? if is_rsnvac_first_let?(record) && record["mrcdate"].present?
record.errors.add :mrcdate, I18n.t("validations.lettings.date.mrcdate.not_first_let") record.errors.add :mrcdate, I18n.t("validations.lettings.date.mrcdate.not_first_let")
end end
return unless record["startdate"].present? && date_valid?("startdate", record)
if record["mrcdate"].present? && record["startdate"].present? && record["startdate"].to_date - record["mrcdate"].to_date > 3650 if record["startdate"].present? && record["mrcdate"].present? && record["startdate"] < record["mrcdate"]
record.errors.add :mrcdate, I18n.t("validations.lettings.date.mrcdate.before_tenancy_start")
record.errors.add :startdate, I18n.t("validations.lettings.date.startdate.after_major_repair_date")
end
if record.form.start_year_2025_or_later?
if record["startdate"].to_date - 20.years > record["mrcdate"].to_date
record.errors.add :mrcdate, I18n.t("validations.lettings.date.mrcdate.twenty_years_before_tenancy_start")
record.errors.add :startdate, I18n.t("validations.lettings.date.startdate.twenty_years_after_mrc_date")
end
elsif record["startdate"].to_date - 10.years > record["mrcdate"].to_date
record.errors.add :mrcdate, I18n.t("validations.lettings.date.mrcdate.ten_years_before_tenancy_start") record.errors.add :mrcdate, I18n.t("validations.lettings.date.mrcdate.ten_years_before_tenancy_start")
record.errors.add :startdate, I18n.t("validations.lettings.date.startdate.ten_years_after_mrc_date")
end end
end end
def validate_property_void_date(record) def validate_property_void_date(record)
if record["voiddate"].present? && record["startdate"].present? && record["startdate"].to_date - record["voiddate"].to_date > 3650 return unless record["voiddate"].present? && date_valid?("voiddate", record)
record.errors.add :voiddate, I18n.t("validations.lettings.date.void_date.ten_years_before_tenancy_start")
end
if record["voiddate"].present? && record["startdate"].present? && record["startdate"].to_date < record["voiddate"].to_date
record.errors.add :voiddate, I18n.t("validations.lettings.date.void_date.before_tenancy_start")
end
if record["voiddate"].present? && record["mrcdate"].present? && record["mrcdate"].to_date < record["voiddate"].to_date if record["mrcdate"].present? && record["mrcdate"].to_date < record["voiddate"].to_date
record.errors.add :voiddate, :after_mrcdate, message: I18n.t("validations.lettings.date.void_date.after_mrcdate") record.errors.add :voiddate, :after_mrcdate, message: I18n.t("validations.lettings.date.void_date.after_mrcdate")
record.errors.add :mrcdate, I18n.t("validations.lettings.date.mrcdate.before_void_date") record.errors.add :mrcdate, I18n.t("validations.lettings.date.mrcdate.before_void_date")
end end
end return unless record["startdate"].present? && date_valid?("startdate", record)
def validate_startdate(record) if record["startdate"].to_date < record["voiddate"].to_date
return unless record.startdate && date_valid?("startdate", record) record.errors.add :voiddate, I18n.t("validations.lettings.date.void_date.before_tenancy_start")
if record["voiddate"].present? && record.startdate < record["voiddate"]
record.errors.add :startdate, I18n.t("validations.lettings.date.startdate.after_void_date") record.errors.add :startdate, I18n.t("validations.lettings.date.startdate.after_void_date")
end end
if record["mrcdate"].present? && record.startdate < record["mrcdate"] if record.form.start_year_2025_or_later?
record.errors.add :startdate, I18n.t("validations.lettings.date.startdate.after_major_repair_date") if record["startdate"].to_date - 20.years > record["voiddate"].to_date
record.errors.add :voiddate, I18n.t("validations.lettings.date.void_date.twenty_years_before_tenancy_start")
record.errors.add :startdate, I18n.t("validations.lettings.date.startdate.twenty_years_after_void_date")
end end
elsif record["startdate"].to_date - 10.years > record["voiddate"].to_date
if record["voiddate"].present? && record["startdate"].to_date - record["voiddate"].to_date > 3650 record.errors.add :voiddate, I18n.t("validations.lettings.date.void_date.ten_years_before_tenancy_start")
record.errors.add :startdate, I18n.t("validations.lettings.date.startdate.ten_years_after_void_date") record.errors.add :startdate, I18n.t("validations.lettings.date.startdate.ten_years_after_void_date")
end end
if record["mrcdate"].present? && record["startdate"].to_date - record["mrcdate"].to_date > 3650
record.errors.add :startdate, I18n.t("validations.lettings.date.startdate.ten_years_after_mrc_date")
end end
def validate_startdate(record)
date_valid?("startdate", record)
end end
private private

2
app/models/validations/financial_validations.rb

@ -25,7 +25,7 @@ module Validations::FinancialValidations
end end
def validate_net_income(record) def validate_net_income(record)
if record.ecstat1 && record.hhmemb && record.weekly_net_income && record.startdate && record.form.start_date.year >= 2023 if record.ecstat1 && record.hhmemb && record.weekly_net_income && record.startdate
if record.weekly_net_income > record.applicable_income_range.hard_max if record.weekly_net_income > record.applicable_income_range.hard_max
frequency = record.form.get_question("incfreq", record).label_from_value(record.incfreq).downcase frequency = record.form.get_question("incfreq", record).label_from_value(record.incfreq).downcase
hard_max = format_as_currency(record.applicable_income_range.hard_max) hard_max = format_as_currency(record.applicable_income_range.hard_max)

2
app/models/validations/household_validations.rb

@ -201,7 +201,7 @@ module Validations::HouseholdValidations
def validate_layear_and_prevloc(record) def validate_layear_and_prevloc(record)
return unless record.layear && record.la && record.prevloc && record.collection_start_year return unless record.layear && record.la && record.prevloc && record.collection_start_year
if record.la == record.prevloc && record.layear == 1 && record.collection_start_year >= 2023 if record.la == record.prevloc && record.layear == 1
record.errors.add :layear, :renewal_just_moved, message: I18n.t("validations.lettings.household.layear.same_la_just_moved_to_area") record.errors.add :layear, :renewal_just_moved, message: I18n.t("validations.lettings.household.layear.same_la_just_moved_to_area")
record.errors.add :la, :renewal_just_moved, message: I18n.t("validations.lettings.household.la.same_la_just_moved_to_area") record.errors.add :la, :renewal_just_moved, message: I18n.t("validations.lettings.household.la.same_la_just_moved_to_area")
record.errors.add :postcode_full, :renewal_just_moved, message: I18n.t("validations.lettings.household.postcode_full.same_la_just_moved_to_area") record.errors.add :postcode_full, :renewal_just_moved, message: I18n.t("validations.lettings.household.postcode_full.same_la_just_moved_to_area")

2
app/models/validations/sales/financial_validations.rb

@ -82,7 +82,7 @@ module Validations::Sales::FinancialValidations
def validate_child_income(record) def validate_child_income(record)
return unless record.income2 && record.ecstat2 return unless record.income2 && record.ecstat2
if record.income2.positive? && is_economic_status_child?(record.ecstat2) && record.form.start_date.year >= 2023 if record.income2.positive? && is_economic_status_child?(record.ecstat2)
record.errors.add :ecstat2, I18n.t("validations.sales.financial.ecstat2.child_has_income") record.errors.add :ecstat2, I18n.t("validations.sales.financial.ecstat2.child_has_income")
record.errors.add :income2, I18n.t("validations.sales.financial.income2.child_has_income") record.errors.add :income2, I18n.t("validations.sales.financial.income2.child_has_income")
end end

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

@ -13,8 +13,6 @@ module Validations::Sales::HouseholdValidations
end end
def validate_buyers_living_in_property(record) def validate_buyers_living_in_property(record)
return unless record.form.start_date.year >= 2023
if record.buyers_will_live_in? && record.buyer_one_will_not_live_in_property? && record.buyer_two_will_not_live_in_property? if record.buyers_will_live_in? && record.buyer_one_will_not_live_in_property? && record.buyer_two_will_not_live_in_property?
record.errors.add :buylivein, I18n.t("validations.sales.household.buylivein.buyers_will_live_in_property_values_inconsistent") record.errors.add :buylivein, I18n.t("validations.sales.household.buylivein.buyers_will_live_in_property_values_inconsistent")
record.errors.add :buy1livein, I18n.t("validations.sales.household.buy1livein.buyers_will_live_in_property_values_inconsistent") record.errors.add :buy1livein, I18n.t("validations.sales.household.buy1livein.buyers_will_live_in_property_values_inconsistent")

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

@ -144,11 +144,11 @@ module Validations::Sales::SaleInformationValidations
return unless record.saledate && record.form.start_year_2024_or_later? return unless record.saledate && record.form.start_year_2024_or_later?
return unless record.discount && record.value && record.la return unless record.discount && record.value && record.la
if record.london_property? && record.discount_value > 136_400 if record.london_property? && record.discount_value > 137_400
%i[discount value la postcode_full uprn].each do |field| %i[discount value la postcode_full uprn].each do |field|
record.errors.add field, I18n.t("validations.sales.sale_information.#{field}.value_over_discounted_london_max", discount_value: record.field_formatted_as_currency("discount_value")) record.errors.add field, I18n.t("validations.sales.sale_information.#{field}.value_over_discounted_london_max", discount_value: record.field_formatted_as_currency("discount_value"))
end end
elsif record.property_not_in_london? && record.discount_value > 102_400 elsif record.property_not_in_london? && record.discount_value > 103_400
%i[discount value la postcode_full uprn].each do |field| %i[discount value la postcode_full uprn].each do |field|
record.errors.add field, I18n.t("validations.sales.sale_information.#{field}.value_over_discounted_max", discount_value: record.field_formatted_as_currency("discount_value")) record.errors.add field, I18n.t("validations.sales.sale_information.#{field}.value_over_discounted_max", discount_value: record.field_formatted_as_currency("discount_value"))
end end

11
app/models/validations/shared_validations.rb

@ -54,14 +54,15 @@ module Validations::SharedValidations
field = question.check_answer_label || question.id field = question.check_answer_label || question.id
incorrect_accuracy = (value.to_d * 100) % (question.step * 100) != 0 incorrect_accuracy = (value.to_d * 100) % (question.step * 100) != 0
if question.step < 1 && incorrect_accuracy next unless incorrect_accuracy
record.errors.add question.id.to_sym, I18n.t("validations.shared.numeric.nearest_hundredth", field:)
elsif incorrect_accuracy || value.to_d != value.to_i # if the user enters a value in exponent notation (eg '4e1') the to_i method does not convert this to the correct value
field = question.check_answer_label || question.id
case question.step case question.step
when 0.01 then record.errors.add question.id.to_sym, I18n.t("validations.shared.numeric.nearest_hundredth", field:)
when 0.1 then record.errors.add question.id.to_sym, I18n.t("validations.shared.numeric.nearest_tenth", field:)
when 1 then record.errors.add question.id.to_sym, :not_integer, message: I18n.t("validations.shared.numeric.whole_number", field:) when 1 then record.errors.add question.id.to_sym, :not_integer, message: I18n.t("validations.shared.numeric.whole_number", field:)
when 10 then record.errors.add question.id.to_sym, I18n.t("validations.shared.numeric.nearest_ten", field:) when 10 then record.errors.add question.id.to_sym, I18n.t("validations.shared.numeric.nearest_ten", field:)
end else
record.errors.add question.id.to_sym, I18n.t("validations.shared.numeric.nearest_step", field:, step: question.step)
end end
end end
end end

4
app/models/validations/soft_validations.rb

@ -16,13 +16,13 @@ module Validations::SoftValidations
}.freeze }.freeze
def net_income_in_soft_max_range? def net_income_in_soft_max_range?
return unless weekly_net_income && ecstat1 && hhmemb return unless weekly_net_income && ecstat1 && hhmemb && applicable_income_range
weekly_net_income.between?(applicable_income_range.soft_max, applicable_income_range.hard_max) weekly_net_income.between?(applicable_income_range.soft_max, applicable_income_range.hard_max)
end end
def net_income_in_soft_min_range? def net_income_in_soft_min_range?
return unless weekly_net_income && ecstat1 && hhmemb return unless weekly_net_income && ecstat1 && hhmemb && applicable_income_range
weekly_net_income.between?(applicable_income_range.hard_min, applicable_income_range.soft_min) weekly_net_income.between?(applicable_income_range.hard_min, applicable_income_range.soft_min)
end end

46
app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2023.html.erb

@ -1,46 +0,0 @@
<% content_for :before_content do %>
<%= govuk_back_link href: @form.back_path %>
<% end %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<%= form_with model: @form, scope: :form, url: bulk_upload_lettings_log_path(id: "prepare-your-file"), method: :patch do |f| %>
<%= f.hidden_field :year %>
<span class="govuk-caption-l">Upload lettings logs in bulk (<%= @form.year_combo %>)</span>
<h1 class="govuk-heading-l">Prepare your file</h1>
<p class="govuk-body govuk-!-margin-bottom-2"><%= govuk_link_to "Read the full guidance", bulk_upload_lettings_log_path(id: "guidance", form: { year: @form.year }, referrer: "prepare-your-file") %> before you start if you have not used bulk upload before.</p>
<h2 class="govuk-heading-s">Download template</h2>
<p class="govuk-body govuk-!-margin-bottom-2">Use one of these templates to upload logs for 2023/24:</p>
<ul class="govuk-list govuk-list--bullet">
<li>
<%= govuk_link_to "Download the new template", @form.template_path %>: In this template, the questions are in the same order as the 2023/24 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>
<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><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>
</ul>
<%= govuk_inset_text(text: "You can upload both general needs and supported housing logs in the same file for 2023 to 2024 data.") %>
<h2 class="govuk-heading-s">Save your file</h2>
<ul class="govuk-list govuk-list--bullet">
<li>Save your file as a CSV.</li>
<li>Your file should now be ready to upload.</li>
</ul>
<%= f.govuk_submit class: "govuk-!-margin-top-7" %>
<% end %>
</div>
</div>

40
app/views/bulk_upload_sales_logs/forms/prepare_your_file_2023.html.erb

@ -1,40 +0,0 @@
<% content_for :before_content do %>
<%= govuk_back_link href: @form.back_path %>
<% end %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<%= form_with model: @form, scope: :form, url: bulk_upload_sales_log_path(id: "prepare-your-file"), method: :patch do |f| %>
<%= f.hidden_field :year %>
<span class="govuk-caption-l">Upload sales logs in bulk (<%= @form.year_combo %>)</span>
<h1 class="govuk-heading-l">Prepare your file</h1>
<p class="govuk-body govuk-!-margin-bottom-2"><%= govuk_link_to "Read the full guidance", bulk_upload_sales_log_path(id: "guidance", form: { year: @form.year }, referrer: "prepare-your-file") %> before you start if you have not used bulk upload before.</p>
<h2 class="govuk-heading-s">Download template</h2>
<p class="govuk-body govuk-!-margin-bottom-2">Use one of these templates to upload logs for 2023/24:</p>
<ul class="govuk-list govuk-list--bullet">
<li><%= govuk_link_to "Download the new template", @form.template_path %>: In this template, the questions are in the same order as the 2023/24 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>
<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><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>
</ul>
<h2 class="govuk-heading-s">Save your file</h2>
<ul class="govuk-list govuk-list--bullet">
<li>Save your file as a CSV.</li>
<li>Your file should now be ready to upload.</li>
</ul>
<%= f.govuk_submit %>
<% end %>
</div>
</div>

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

@ -28,30 +28,15 @@
<%= accordion.with_section(heading_text: "Using the bulk upload template") do %> <%= 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> <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> <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 %>
<%= govuk_list ["the CORE form questions and their field numbers", "each field’s valid responses", "if/when certain fields can be left blank", "which fields are used to check for duplicate logs"], type: :bullet %> <%= govuk_list ["the CORE form questions and their field numbers", "each field’s valid responses", "if/when certain fields can be left blank", "which fields are used to check for duplicate logs"], type: :bullet %>
<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">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> <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>
<% 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 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 "Download the lettings bulk upload template (2023 to 2024) – New question ordering", @form.lettings_template_path %></p>
<p class="govuk-body"><%= govuk_link_to "Download the sales bulk upload template (2023 to 2024) – New question ordering", @form.sales_template_path %></p>
<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>
<% else %>
<p class="govuk-body"><%= govuk_link_to "Download the lettings bulk upload template (2024 to 2025)", @form.lettings_template_path %></p> <p class="govuk-body"><%= govuk_link_to "Download the lettings bulk upload template (2024 to 2025)", @form.lettings_template_path %></p>
<p class="govuk-body"><%= govuk_link_to "Download the sales bulk upload template (2024 to 2025)", @form.sales_template_path %></p> <p class="govuk-body"><%= govuk_link_to "Download the sales bulk upload template (2024 to 2025)", @form.sales_template_path %></p>
<% end %> <% end %>
<% end %>
<%= accordion.with_section(heading_text: "Using the bulk upload specification") do %> <%= accordion.with_section(heading_text: "Using the bulk upload specification") do %>
<p class="govuk-body">The bulk upload specification contains the same information as the template headers, as well as more details about the accepted responses for each question. For multiple-choice questions, we use number or letter codes to represent each option, and the specification shows what answer each code represents.</p> <p class="govuk-body">The bulk upload specification contains the same information as the template headers, as well as more details about the accepted responses for each question. For multiple-choice questions, we use number or letter codes to represent each option, and the specification shows what answer each code represents.</p>

28
config/locales/forms/2025/lettings/household_characteristics.en.yml

@ -84,9 +84,9 @@ en:
relat2: relat2:
page_header: "" page_header: ""
check_answer_label: "Person 2’s relationship to the lead tenant" check_answer_label: "Person 2 lead tenant’s partner"
hint_text: "" hint_text: ""
question_text: "What is person 2’s relationship to the lead tenant?" question_text: "Is tenant 2 the partner of tenant 1?"
age2: age2:
page_header: "" page_header: ""
@ -119,9 +119,9 @@ en:
relat3: relat3:
page_header: "" page_header: ""
check_answer_label: "Person 3’s relationship to the lead tenant" check_answer_label: "Person 3 lead tenant’s partner"
hint_text: "" hint_text: ""
question_text: "What is person 3’s relationship to the lead tenant?" question_text: "Is tenant 3 the partner of tenant 1?"
age3: age3:
page_header: "" page_header: ""
@ -154,9 +154,9 @@ en:
relat4: relat4:
page_header: "" page_header: ""
check_answer_label: "Person 4’s relationship to the lead tenant" check_answer_label: "Person 4 lead tenant’s partner"
hint_text: "" hint_text: ""
question_text: "What is person 4’s relationship to the lead tenant?" question_text: "Is tenant 4 the partner of tenant 1?"
age4: age4:
page_header: "" page_header: ""
@ -189,9 +189,9 @@ en:
relat5: relat5:
page_header: "" page_header: ""
check_answer_label: "Person 5’s relationship to the lead tenant" check_answer_label: "Person 5 lead tenant’s partner"
hint_text: "" hint_text: ""
question_text: "What is person 5’s relationship to the lead tenant?" question_text: "Is tenant 5 the partner of tenant 1?"
age5: age5:
page_header: "" page_header: ""
@ -224,9 +224,9 @@ en:
relat6: relat6:
page_header: "" page_header: ""
check_answer_label: "Person 6’s relationship to the lead tenant" check_answer_label: "Person 6 lead tenant’s partner"
hint_text: "" hint_text: ""
question_text: "What is person 6’s relationship to the lead tenant?" question_text: "Is tenant 6 the partner of tenant 1?"
age6: age6:
page_header: "" page_header: ""
@ -259,9 +259,9 @@ en:
relat7: relat7:
page_header: "" page_header: ""
check_answer_label: "Person 7’s relationship to the lead tenant" check_answer_label: "Person 7 lead tenant’s partner"
hint_text: "" hint_text: ""
question_text: "What is person 7’s relationship to the lead tenant?" question_text: "Is tenant 7 the partner of tenant 1?"
age7: age7:
page_header: "" page_header: ""
@ -294,9 +294,9 @@ en:
relat8: relat8:
page_header: "" page_header: ""
check_answer_label: "Person 8’s relationship to the lead tenant" check_answer_label: "Person 8 lead tenant’s partner"
hint_text: "" hint_text: ""
question_text: "What is person 8’s relationship to the lead tenant?" question_text: "Is tenant 8 the partner of tenant 1?"
age8: age8:
page_header: "" page_header: ""

4
config/locales/validations/lettings/date.en.yml

@ -7,14 +7,18 @@ en:
after_major_repair_date: "Enter a tenancy start date that is after the major repair date." after_major_repair_date: "Enter a tenancy start date that is after the major repair date."
ten_years_after_void_date: "Enter a tenancy start date that is no more than 10 years after the void date." ten_years_after_void_date: "Enter a tenancy start date that is no more than 10 years after the void date."
ten_years_after_mrc_date: "Enter a tenancy start date that is no more than 10 years after the major repairs completion date." ten_years_after_mrc_date: "Enter a tenancy start date that is no more than 10 years after the major repairs completion date."
twenty_years_after_void_date: "Enter a tenancy start date that is no more than 20 years after the void date."
twenty_years_after_mrc_date: "Enter a tenancy start date that is no more than 20 years after the major repairs completion date."
mrcdate: mrcdate:
before_tenancy_start: "Enter a major repairs date that is before the tenancy start date." before_tenancy_start: "Enter a major repairs date that is before the tenancy start date."
not_first_let: "Major repairs date must not be completed if the tenancy is a first let." not_first_let: "Major repairs date must not be completed if the tenancy is a first let."
ten_years_before_tenancy_start: "Enter a major repairs completion date that is no more than 10 years before the tenancy start date." ten_years_before_tenancy_start: "Enter a major repairs completion date that is no more than 10 years before the tenancy start date."
twenty_years_before_tenancy_start: "Enter a major repairs completion date that is no more than 20 years before the tenancy start date."
before_void_date: "Major repairs date must be after the void date if provided." before_void_date: "Major repairs date must be after the void date if provided."
void_date: void_date:
ten_years_before_tenancy_start: "Enter a void date no more than 10 years before the tenancy start date." ten_years_before_tenancy_start: "Enter a void date no more than 10 years before the tenancy start date."
twenty_years_before_tenancy_start: "Enter a void date no more than 20 years before the tenancy start date."
before_tenancy_start: "Enter a void date that is before the tenancy start date." before_tenancy_start: "Enter a void date that is before the tenancy start date."
after_mrcdate: "Void date must be before the major repairs date if provided." after_mrcdate: "Void date must be before the major repairs date if provided."

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

@ -47,8 +47,8 @@ en:
value: value:
discounted_ownership_value: "The mortgage%{mortgage}%{deposit_and_grant_sentence} added together is %{mortgage_deposit_and_grant_total}.</br></br>The full purchase price%{discount_sentence} is %{value_with_discount}.</br></br>These two amounts should be the same." discounted_ownership_value: "The mortgage%{mortgage}%{deposit_and_grant_sentence} added together is %{mortgage_deposit_and_grant_total}.</br></br>The full purchase price%{discount_sentence} is %{value_with_discount}.</br></br>These two amounts should be the same."
outright_sale_value: "The mortgage%{mortgage} and cash deposit (%{deposit}) when added together is %{mortgage_and_deposit_total}.</br></br>The full purchase price is %{value}.</br></br>These two amounts should be the same." outright_sale_value: "The mortgage%{mortgage} and cash deposit (%{deposit}) when added together is %{mortgage_and_deposit_total}.</br></br>The full purchase price is %{value}.</br></br>These two amounts should be the same."
value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £136,400 for properties in London." value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £137,400 for properties in London."
value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £102,400 for properties outside of London." value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £103,400 for properties outside of London."
non_staircasing_mortgage: non_staircasing_mortgage:
mortgage_used: "The mortgage (%{mortgage}) and cash deposit (%{deposit}) added together is %{mortgage_and_deposit_total}.</br></br>The full purchase price (%{value}) multiplied by the percentage equity stake purchased (%{equity}) is %{expected_shared_ownership_deposit_value}.</br></br>These two amounts should be the same." mortgage_used: "The mortgage (%{mortgage}) and cash deposit (%{deposit}) added together is %{mortgage_and_deposit_total}.</br></br>The full purchase price (%{value}) multiplied by the percentage equity stake purchased (%{equity}) is %{expected_shared_ownership_deposit_value}.</br></br>These two amounts should be the same."
mortgage_not_used: "The cash deposit is %{deposit}.</br></br>The full purchase price (%{value}) multiplied by the percentage bought is %{expected_shared_ownership_deposit_value}.</br></br>These two amounts should be the same." mortgage_not_used: "The cash deposit is %{deposit}.</br></br>The full purchase price (%{value}) multiplied by the percentage bought is %{expected_shared_ownership_deposit_value}.</br></br>These two amounts should be the same."
@ -87,8 +87,8 @@ en:
mortgage_not_used_socialhomebuy: "The cash deposit (%{deposit}) and cash discount (%{cashdis}) added together is %{deposit_and_discount_total}.</br></br>The full purchase price (%{value}) multiplied by the percentage bought (%{equity}) is %{expected_shared_ownership_deposit_value}.</br></br>These two amounts should be the same." mortgage_not_used_socialhomebuy: "The cash deposit (%{deposit}) and cash discount (%{cashdis}) added together is %{deposit_and_discount_total}.</br></br>The full purchase price (%{value}) multiplied by the percentage bought (%{equity}) is %{expected_shared_ownership_deposit_value}.</br></br>These two amounts should be the same."
discount: discount:
discounted_ownership_value: "The mortgage%{mortgage}%{deposit_and_grant_sentence} added together is %{mortgage_deposit_and_grant_total}.</br></br>The full purchase price%{discount_sentence} is %{value_with_discount}.</br></br>These two amounts should be the same." discounted_ownership_value: "The mortgage%{mortgage}%{deposit_and_grant_sentence} added together is %{mortgage_deposit_and_grant_total}.</br></br>The full purchase price%{discount_sentence} is %{value_with_discount}.</br></br>These two amounts should be the same."
value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £136,400 for properties in London." value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £137,400 for properties in London."
value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £102,400 for properties outside of London." value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £103,400 for properties outside of London."
grant: grant:
discounted_ownership_value: "The mortgage%{mortgage}%{deposit_and_grant_sentence} added together is %{mortgage_deposit_and_grant_total}.</br></br>The full purchase price%{discount_sentence} is %{value_with_discount}.</br></br>These two amounts should be the same." discounted_ownership_value: "The mortgage%{mortgage}%{deposit_and_grant_sentence} added together is %{mortgage_deposit_and_grant_total}.</br></br>The full purchase price%{discount_sentence} is %{value_with_discount}.</br></br>These two amounts should be the same."
out_of_range: "Loan, grants or subsidies must be between £9,000 and £16,000." out_of_range: "Loan, grants or subsidies must be between £9,000 and £16,000."
@ -119,14 +119,14 @@ en:
staircase: staircase:
mortgage_used_value: "You must answer either ‘yes’ or ‘no’ to the question ‘was a mortgage used’ for staircasing transactions." mortgage_used_value: "You must answer either ‘yes’ or ‘no’ to the question ‘was a mortgage used’ for staircasing transactions."
la: la:
value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £136,400 for properties in London." value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £137,400 for properties in London."
value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £102,400 for properties outside of London." value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £103,400 for properties outside of London."
uprn: uprn:
value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £136,400 for properties in London." value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £137,400 for properties in London."
value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £102,400 for properties outside of London." value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £103,400 for properties outside of London."
postcode_full: postcode_full:
value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £136,400 for properties in London." value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £137,400 for properties in London."
value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £102,400 for properties outside of London." value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £103,400 for properties outside of London."
numstair: numstair:
must_be_greater_than_one: "The number of staircasing transactions must be greater than 1 when this is not the first staircasing transaction." must_be_greater_than_one: "The number of staircasing transactions must be greater than 1 when this is not the first staircasing transaction."
firststair: firststair:

2
config/locales/validations/shared.en.yml

@ -9,7 +9,9 @@ en:
above_min: "%{field} must be at least %{min}." above_min: "%{field} must be at least %{min}."
whole_number: "%{field} must be a whole number." whole_number: "%{field} must be a whole number."
nearest_ten: "%{field} must be given to the nearest ten." nearest_ten: "%{field} must be given to the nearest ten."
nearest_tenth: "%{field} must be given to the nearest tenth."
nearest_hundredth: "%{field} must be given to the nearest hundredth." nearest_hundredth: "%{field} must be given to the nearest hundredth."
nearest_step: "${field} must be given to the nearest %{step}."
normal_format: "Enter a number." normal_format: "Enter a number."
format: "%{field} must be a number." format: "%{field} must be a number."

14
spec/features/bulk_upload_sales_logs_spec.rb

@ -22,7 +22,7 @@ RSpec.describe "Bulk upload sales log" do
# rubocop:disable RSpec/AnyInstance # rubocop:disable RSpec/AnyInstance
context "when during crossover period" do context "when during crossover period" do
before do before do
Timecop.freeze(2023, 5, 1) Timecop.freeze(2024, 5, 1)
end end
after do after do
@ -38,15 +38,15 @@ RSpec.describe "Bulk upload sales log" do
click_button("Continue") click_button("Continue")
expect(page).to have_content("You must select a collection period to upload for") expect(page).to have_content("You must select a collection period to upload for")
choose("2023 to 2024") choose("2024 to 2025")
click_button("Continue") click_button("Continue")
click_link("Back") click_link("Back")
expect(page.find_field("form-year-2023-field")).to be_checked expect(page.find_field("form-year-2024-field")).to be_checked
click_button("Continue") click_button("Continue")
expect(page).to have_content("Upload sales logs in bulk (2023 to 2024)") expect(page).to have_content("Upload sales logs in bulk (2024 to 2025)")
click_button("Continue") click_button("Continue")
expect(page).to have_content("Upload your file") expect(page).to have_content("Upload your file")
@ -80,7 +80,7 @@ RSpec.describe "Bulk upload sales log" do
expect(page).to have_content("Which year") expect(page).to have_content("Which year")
click_button("Continue") click_button("Continue")
click_button("Continue") click_button("Continue")
choose("2023 to 2024") choose("2024 to 2025")
click_button("Continue") click_button("Continue")
click_button("Continue") click_button("Continue")
@ -96,7 +96,7 @@ RSpec.describe "Bulk upload sales log" do
context "when not in crossover period" do context "when not in crossover period" do
before do before do
Timecop.freeze(2024, 2, 1) Timecop.freeze(2025, 2, 1)
end end
after do after do
@ -108,7 +108,7 @@ RSpec.describe "Bulk upload sales log" do
expect(page).to have_link("Upload sales logs in bulk") expect(page).to have_link("Upload sales logs in bulk")
click_link("Upload sales logs in bulk") click_link("Upload sales logs in bulk")
expect(page).to have_content("Upload sales logs in bulk (2023 to 2024)") expect(page).to have_content("Upload sales logs in bulk (2024 to 2025)")
click_button("Continue") click_button("Continue")
expect(page).to have_content("Upload your file") expect(page).to have_content("Upload your file")

51
spec/models/form/lettings/pages/person_lead_partner_spec.rb

@ -0,0 +1,51 @@
require "rails_helper"
RSpec.describe Form::Lettings::Pages::PersonLeadPartner, type: :model do
subject(:page) { described_class.new(nil, page_definition, subsection, person_index:) }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2024, 4, 1), start_year_2025_or_later?: true)) }
let(:person_index) { 2 }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
it "has the correct description" do
expect(page.description).to be nil
end
context "with person 2" do
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[relat2])
end
it "has the correct id" do
expect(page.id).to eq("person_2_lead_partner")
end
it "has correct depends_on" do
expect(page.depends_on).to eq(
[{ "details_known_2" => 0 }],
)
end
end
context "with person 3" do
let(:person_index) { 3 }
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[relat3])
end
it "has the correct id" do
expect(page.id).to eq("person_3_lead_partner")
end
it "has correct depends_on" do
expect(page.depends_on).to eq(
[{ "details_known_3" => 0 }],
)
end
end
end

44
spec/models/form/lettings/pages/rent4_weekly_spec.rb

@ -0,0 +1,44 @@
require "rails_helper"
RSpec.describe Form::Lettings::Pages::Rent4Weekly, type: :model do
subject(:page) { described_class.new(nil, page_definition, subsection) }
let(:page_definition) { nil }
let(:start_date) { Time.zone.local(2024, 4, 1) }
let(:start_year_2025_or_later) { false }
let(:form) { instance_double(Form, start_date:) }
let(:subsection) { instance_double(Form::Subsection, form:) }
let(:person_index) { 2 }
before do
allow(form).to receive(:start_year_2025_or_later?).and_return(start_year_2025_or_later)
end
context "with form before 2025" do
let(:start_date) { Time.zone.local(2024, 4, 1) }
let(:start_year_2025_or_later) { false }
it "has correct depends_on" do
expect(page.depends_on).to eq(
[
{ "rent_and_charges_paid_every_4_weeks?" => true, "household_charge" => 0, "is_carehome?" => false },
{ "rent_and_charges_paid_every_4_weeks?" => true, "household_charge" => nil, "is_carehome?" => false },
],
)
end
end
context "with form on or after 2025" do
let(:start_date) { Time.zone.local(2025, 4, 1) }
let(:start_year_2025_or_later) { true }
it "has correct depends_on" do
expect(page.depends_on).to eq(
[
{ "rent_and_charges_paid_every_4_weeks?" => true, "household_charge" => 0 },
{ "rent_and_charges_paid_every_4_weeks?" => true, "household_charge" => nil },
],
)
end
end
end

44
spec/models/form/lettings/pages/rent_bi_weekly_spec.rb

@ -0,0 +1,44 @@
require "rails_helper"
RSpec.describe Form::Lettings::Pages::RentBiWeekly, type: :model do
subject(:page) { described_class.new(nil, page_definition, subsection) }
let(:page_definition) { nil }
let(:start_date) { Time.zone.local(2024, 4, 1) }
let(:start_year_2025_or_later) { false }
let(:form) { instance_double(Form, start_date:) }
let(:subsection) { instance_double(Form::Subsection, form:) }
let(:person_index) { 2 }
before do
allow(form).to receive(:start_year_2025_or_later?).and_return(start_year_2025_or_later)
end
context "with form before 2025" do
let(:start_date) { Time.zone.local(2024, 4, 1) }
let(:start_year_2025_or_later) { false }
it "has correct depends_on" do
expect(page.depends_on).to eq(
[
{ "rent_and_charges_paid_every_2_weeks?" => true, "household_charge" => nil, "is_carehome?" => false },
{ "rent_and_charges_paid_every_2_weeks?" => true, "household_charge" => 0, "is_carehome?" => false },
],
)
end
end
context "with form on or after 2025" do
let(:start_date) { Time.zone.local(2025, 4, 1) }
let(:start_year_2025_or_later) { true }
it "has correct depends_on" do
expect(page.depends_on).to eq(
[
{ "rent_and_charges_paid_every_2_weeks?" => true, "household_charge" => nil },
{ "rent_and_charges_paid_every_2_weeks?" => true, "household_charge" => 0 },
],
)
end
end
end

44
spec/models/form/lettings/pages/rent_monthly_spec.rb

@ -0,0 +1,44 @@
require "rails_helper"
RSpec.describe Form::Lettings::Pages::RentMonthly, type: :model do
subject(:page) { described_class.new(nil, page_definition, subsection) }
let(:page_definition) { nil }
let(:start_date) { Time.zone.local(2024, 4, 1) }
let(:start_year_2025_or_later) { false }
let(:form) { instance_double(Form, start_date:) }
let(:subsection) { instance_double(Form::Subsection, form:) }
let(:person_index) { 2 }
before do
allow(form).to receive(:start_year_2025_or_later?).and_return(start_year_2025_or_later)
end
context "with form before 2025" do
let(:start_date) { Time.zone.local(2024, 4, 1) }
let(:start_year_2025_or_later) { false }
it "has correct depends_on" do
expect(page.depends_on).to eq(
[
{ "rent_and_charges_paid_monthly?" => true, "household_charge" => nil, "is_carehome?" => false },
{ "rent_and_charges_paid_monthly?" => true, "household_charge" => 0, "is_carehome?" => false },
],
)
end
end
context "with form on or after 2025" do
let(:start_date) { Time.zone.local(2025, 4, 1) }
let(:start_year_2025_or_later) { true }
it "has correct depends_on" do
expect(page.depends_on).to eq(
[
{ "rent_and_charges_paid_monthly?" => true, "household_charge" => nil },
{ "rent_and_charges_paid_monthly?" => true, "household_charge" => 0 },
],
)
end
end
end

44
spec/models/form/lettings/pages/rent_weekly_spec.rb

@ -0,0 +1,44 @@
require "rails_helper"
RSpec.describe Form::Lettings::Pages::RentWeekly, type: :model do
subject(:page) { described_class.new(nil, page_definition, subsection) }
let(:page_definition) { nil }
let(:start_date) { Time.zone.local(2024, 4, 1) }
let(:start_year_2025_or_later) { false }
let(:form) { instance_double(Form, start_date:) }
let(:subsection) { instance_double(Form::Subsection, form:) }
let(:person_index) { 2 }
before do
allow(form).to receive(:start_year_2025_or_later?).and_return(start_year_2025_or_later)
end
context "with form before 2025" do
let(:start_date) { Time.zone.local(2024, 4, 1) }
let(:start_year_2025_or_later) { false }
it "has correct depends_on" do
expect(page.depends_on).to eq(
[
{ "rent_and_charges_paid_weekly?" => true, "household_charge" => 0, "is_carehome?" => false },
{ "rent_and_charges_paid_weekly?" => true, "household_charge" => nil, "is_carehome?" => false },
],
)
end
end
context "with form on or after 2025" do
let(:start_date) { Time.zone.local(2025, 4, 1) }
let(:start_year_2025_or_later) { true }
it "has correct depends_on" do
expect(page.depends_on).to eq(
[
{ "rent_and_charges_paid_weekly?" => true, "household_charge" => 0 },
{ "rent_and_charges_paid_weekly?" => true, "household_charge" => nil },
],
)
end
end
end

57
spec/models/form/lettings/questions/person_partner_spec.rb

@ -0,0 +1,57 @@
require "rails_helper"
RSpec.describe Form::Lettings::Questions::PersonPartner, type: :model do
subject(:question) { described_class.new(nil, question_definition, page, person_index:) }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 4), start_year_2025_or_later?: true))) }
let(:person_index) { 2 }
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?(nil)).to be false
end
it "has the correct answer_options" do
expect(question.answer_options).to eq("P" => { "value" => "Yes" },
"X" => { "value" => "No" },
"R" => { "value" => "Tenant prefers not to say" })
end
it "has correct conditional for" do
expect(question.conditional_for).to be nil
end
it "has the correct hidden_in_check_answers" do
expect(question.hidden_in_check_answers).to be nil
end
context "with person 2" do
it "has the correct id" do
expect(question.id).to eq("relat2")
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(2)
end
end
context "with person 3" do
let(:person_index) { 3 }
it "has the correct id" do
expect(question.id).to eq("relat3")
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(3)
end
end
end

156
spec/models/form/lettings/subsections/household_characteristics_spec.rb

@ -10,6 +10,7 @@ RSpec.describe Form::Lettings::Subsections::HouseholdCharacteristics, type: :mod
before do before do
allow(section).to receive(:form).and_return(form) allow(section).to receive(:form).and_return(form)
allow(form).to receive(:start_year_2025_or_later?).and_return(false)
end end
it "has correct section" do it "has correct section" do
@ -304,6 +305,161 @@ RSpec.describe Form::Lettings::Subsections::HouseholdCharacteristics, type: :mod
end end
end end
context "with start year >= 2025" do
before do
allow(form).to receive(:start_year_2024_or_later?).and_return(true)
allow(form).to receive(:start_year_2025_or_later?).and_return(true)
end
it "has correct pages" do
expect(household_characteristics.pages.map(&:id)).to eq(
%w[
household_members
no_females_pregnant_household_lead_hhmemb_value_check
females_in_soft_age_range_in_pregnant_household_lead_hhmemb_value_check
lead_tenant_age
no_females_pregnant_household_lead_age_value_check
females_in_soft_age_range_in_pregnant_household_lead_age_value_check
age_lead_tenant_under_retirement_value_check
age_lead_tenant_over_retirement_value_check
lead_tenant_gender_identity
no_females_pregnant_household_lead_value_check
females_in_soft_age_range_in_pregnant_household_lead_value_check
gender_lead_tenant_over_retirement_value_check
lead_tenant_ethnic_group
lead_tenant_ethnic_background_arab
lead_tenant_ethnic_background_asian
lead_tenant_ethnic_background_black
lead_tenant_ethnic_background_mixed
lead_tenant_ethnic_background_white
lead_tenant_nationality
lead_tenant_working_situation
working_situation_lead_tenant_under_retirement_value_check
working_situation_lead_tenant_over_retirement_value_check
person_2_known
person_2_lead_partner
relationship_2_partner_under_16_value_check
relationship_2_multiple_partners_value_check
person_2_age
no_females_pregnant_household_person_2_age_value_check
females_in_soft_age_range_in_pregnant_household_person_2_age_value_check
age_2_under_retirement_value_check
age_2_over_retirement_value_check
age_2_partner_under_16_value_check
person_2_gender_identity
no_females_pregnant_household_person_2_value_check
females_in_soft_age_range_in_pregnant_household_person_2_value_check
gender_2_over_retirement_value_check
person_2_working_situation
working_situation_2_under_retirement_value_check
working_situation_2_over_retirement_value_check
person_3_known
person_3_lead_partner
relationship_3_partner_under_16_value_check
relationship_3_multiple_partners_value_check
person_3_age
no_females_pregnant_household_person_3_age_value_check
females_in_soft_age_range_in_pregnant_household_person_3_age_value_check
age_3_under_retirement_value_check
age_3_over_retirement_value_check
age_3_partner_under_16_value_check
person_3_gender_identity
no_females_pregnant_household_person_3_value_check
females_in_soft_age_range_in_pregnant_household_person_3_value_check
gender_3_over_retirement_value_check
person_3_working_situation
working_situation_3_under_retirement_value_check
working_situation_3_over_retirement_value_check
person_4_known
person_4_lead_partner
relationship_4_partner_under_16_value_check
relationship_4_multiple_partners_value_check
person_4_age
no_females_pregnant_household_person_4_age_value_check
females_in_soft_age_range_in_pregnant_household_person_4_age_value_check
age_4_under_retirement_value_check
age_4_over_retirement_value_check
age_4_partner_under_16_value_check
person_4_gender_identity
no_females_pregnant_household_person_4_value_check
females_in_soft_age_range_in_pregnant_household_person_4_value_check
gender_4_over_retirement_value_check
person_4_working_situation
working_situation_4_under_retirement_value_check
working_situation_4_over_retirement_value_check
person_5_known
person_5_lead_partner
relationship_5_partner_under_16_value_check
relationship_5_multiple_partners_value_check
person_5_age
no_females_pregnant_household_person_5_age_value_check
females_in_soft_age_range_in_pregnant_household_person_5_age_value_check
age_5_under_retirement_value_check
age_5_over_retirement_value_check
age_5_partner_under_16_value_check
person_5_gender_identity
no_females_pregnant_household_person_5_value_check
females_in_soft_age_range_in_pregnant_household_person_5_value_check
gender_5_over_retirement_value_check
person_5_working_situation
working_situation_5_under_retirement_value_check
working_situation_5_over_retirement_value_check
person_6_known
person_6_lead_partner
relationship_6_partner_under_16_value_check
relationship_6_multiple_partners_value_check
person_6_age
no_females_pregnant_household_person_6_age_value_check
females_in_soft_age_range_in_pregnant_household_person_6_age_value_check
age_6_under_retirement_value_check
age_6_over_retirement_value_check
age_6_partner_under_16_value_check
person_6_gender_identity
no_females_pregnant_household_person_6_value_check
females_in_soft_age_range_in_pregnant_household_person_6_value_check
gender_6_over_retirement_value_check
person_6_working_situation
working_situation_6_under_retirement_value_check
working_situation_6_over_retirement_value_check
person_7_known
person_7_lead_partner
relationship_7_partner_under_16_value_check
relationship_7_multiple_partners_value_check
person_7_age
no_females_pregnant_household_person_7_age_value_check
females_in_soft_age_range_in_pregnant_household_person_7_age_value_check
age_7_under_retirement_value_check
age_7_over_retirement_value_check
age_7_partner_under_16_value_check
person_7_gender_identity
no_females_pregnant_household_person_7_value_check
females_in_soft_age_range_in_pregnant_household_person_7_value_check
gender_7_over_retirement_value_check
person_7_working_situation
working_situation_7_under_retirement_value_check
working_situation_7_over_retirement_value_check
person_8_known
person_8_lead_partner
relationship_8_partner_under_16_value_check
relationship_8_multiple_partners_value_check
person_8_age
no_females_pregnant_household_person_8_age_value_check
females_in_soft_age_range_in_pregnant_household_person_8_age_value_check
age_8_under_retirement_value_check
age_8_over_retirement_value_check
age_8_partner_under_16_value_check
person_8_gender_identity
no_females_pregnant_household_person_8_value_check
females_in_soft_age_range_in_pregnant_household_person_8_value_check
gender_8_over_retirement_value_check
person_8_working_situation
working_situation_8_under_retirement_value_check
working_situation_8_over_retirement_value_check
],
)
end
end
it "has the correct id" do it "has the correct id" do
expect(household_characteristics.id).to eq("household_characteristics") expect(household_characteristics.id).to eq("household_characteristics")
end end

39
spec/models/form/lettings/subsections/income_and_benefits_spec.rb

@ -5,13 +5,20 @@ RSpec.describe Form::Lettings::Subsections::IncomeAndBenefits, type: :model do
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)) } let(:start_date) { Time.zone.local(2024, 4, 1) }
let(:start_year_2025_or_later) { false }
let(:form) { instance_double(Form, start_date:) }
let(:section) { instance_double(Form::Lettings::Sections::RentAndCharges, form:) } let(:section) { instance_double(Form::Lettings::Sections::RentAndCharges, form:) }
before do
allow(form).to receive(:start_year_2025_or_later?).and_return(start_year_2025_or_later)
end
it "has correct section" do it "has correct section" do
expect(income_and_benefits.section).to eq(section) expect(income_and_benefits.section).to eq(section)
end end
context "with 2024 form" do
it "has correct pages" do it "has correct pages" do
expect(income_and_benefits.pages.map(&:id)).to eq( expect(income_and_benefits.pages.map(&:id)).to eq(
%w[ %w[
@ -40,6 +47,36 @@ RSpec.describe Form::Lettings::Subsections::IncomeAndBenefits, type: :model do
], ],
) )
end end
end
context "with 2025 form" do
let(:start_date) { Time.zone.local(2025, 4, 1) }
let(:start_year_2025_or_later) { true }
it "has correct pages" do
expect(income_and_benefits.pages.map(&:id)).to eq(
%w[
income_known
income_amount
net_income_value_check
housing_benefit
benefits_proportion
rent_or_other_charges
rent_period
rent_weekly
rent_bi_weekly
rent_4_weekly
rent_monthly
brent_rent_value_check
scharge_value_check
pscharge_value_check
supcharg_value_check
outstanding
outstanding_amount
],
)
end
end
it "has the correct id" do it "has the correct id" do
expect(income_and_benefits.id).to eq("income_and_benefits") expect(income_and_benefits.id).to eq("income_and_benefits")

8
spec/models/sales_log_spec.rb

@ -120,14 +120,6 @@ RSpec.describe SalesLog, type: :model do
allow(Time).to receive(:now).and_return(Time.zone.local(2023, 5, 1)) allow(Time).to receive(:now).and_return(Time.zone.local(2023, 5, 1))
end end
it "is set to completed for a log with a saledate before 23/24" do
completed_sales_log.update!(proplen: nil, proplen_asked: 0, saledate: Time.zone.local(2022, 5, 1))
expect(completed_sales_log.in_progress?).to be(false)
expect(completed_sales_log.not_started?).to be(false)
expect(completed_sales_log.completed?).to be(true)
expect(completed_sales_log.deleted?).to be(false)
end
it "is set to in_progress for a log with a saledate after 23/24" do it "is set to in_progress for a log with a saledate after 23/24" do
completed_sales_log.update!(proplen: nil, proplen_asked: 0, saledate: Time.zone.local(2023, 5, 1)) completed_sales_log.update!(proplen: nil, proplen_asked: 0, saledate: Time.zone.local(2023, 5, 1))
expect(completed_sales_log.in_progress?).to be(true) expect(completed_sales_log.in_progress?).to be(true)

83
spec/models/validations/date_validations_spec.rb

@ -21,22 +21,6 @@ RSpec.describe Validations::DateValidations do
expect(record.errors["startdate"]).to be_empty expect(record.errors["startdate"]).to be_empty
end end
it "validates that the tenancy start date is after the void date if it has a void date" do
record.startdate = Time.zone.local(2022, 1, 1)
record.voiddate = Time.zone.local(2022, 2, 1)
date_validator.validate_startdate(record)
expect(record.errors["startdate"])
.to include(match I18n.t("validations.lettings.date.startdate.after_void_date"))
end
it "validates that the tenancy start date is after the major repair date if it has a major repair date" do
record.startdate = Time.zone.local(2022, 1, 1)
record.mrcdate = Time.zone.local(2022, 2, 1)
date_validator.validate_startdate(record)
expect(record.errors["startdate"])
.to include(match I18n.t("validations.lettings.date.startdate.after_major_repair_date"))
end
it "produces no error when the tenancy start date is before the end date of the chosen scheme if it has an end date" do it "produces no error when the tenancy start date is before the end date of the chosen scheme if it has an end date" do
record.startdate = Time.zone.today - 30.days record.startdate = Time.zone.today - 30.days
record.scheme = scheme record.scheme = scheme
@ -59,6 +43,8 @@ RSpec.describe Validations::DateValidations do
date_validator.validate_property_major_repairs(record) date_validator.validate_property_major_repairs(record)
expect(record.errors["mrcdate"]) expect(record.errors["mrcdate"])
.to include(match I18n.t("validations.lettings.date.mrcdate.before_tenancy_start")) .to include(match I18n.t("validations.lettings.date.mrcdate.before_tenancy_start"))
expect(record.errors["startdate"])
.to include(match I18n.t("validations.lettings.date.startdate.after_major_repair_date"))
end end
it "must be before the tenancy start date" do it "must be before the tenancy start date" do
@ -68,11 +54,11 @@ RSpec.describe Validations::DateValidations do
expect(record.errors["mrcdate"]).to be_empty expect(record.errors["mrcdate"]).to be_empty
end end
context "with 2024 logs or earlier" do
it "cannot be more than 10 years before the tenancy start date" do it "cannot be more than 10 years before the tenancy start date" do
record.startdate = Time.zone.local(2022, 2, 1) record.startdate = Time.zone.local(2024, 2, 1)
record.mrcdate = Time.zone.local(2012, 1, 1) record.mrcdate = Time.zone.local(2014, 1, 31)
date_validator.validate_property_major_repairs(record) date_validator.validate_property_major_repairs(record)
date_validator.validate_startdate(record)
expect(record.errors["mrcdate"]) expect(record.errors["mrcdate"])
.to include(match I18n.t("validations.lettings.date.mrcdate.ten_years_before_tenancy_start")) .to include(match I18n.t("validations.lettings.date.mrcdate.ten_years_before_tenancy_start"))
expect(record.errors["startdate"]) expect(record.errors["startdate"])
@ -80,12 +66,33 @@ RSpec.describe Validations::DateValidations do
end end
it "must be within 10 years of the tenancy start date" do it "must be within 10 years of the tenancy start date" do
record.startdate = Time.zone.local(2022, 2, 1) record.startdate = Time.zone.local(2024, 2, 1)
record.mrcdate = Time.zone.local(2012, 3, 1) record.mrcdate = Time.zone.local(2014, 2, 1)
date_validator.validate_property_major_repairs(record) date_validator.validate_property_major_repairs(record)
expect(record.errors["mrcdate"]).to be_empty expect(record.errors["mrcdate"]).to be_empty
expect(record.errors["startdate"]).to be_empty expect(record.errors["startdate"]).to be_empty
end end
end
context "with 2025 logs or later" do
it "cannot be more than 20 years before the tenancy start date" do
record.startdate = Time.zone.local(2026, 2, 1)
record.mrcdate = Time.zone.local(2006, 1, 31)
date_validator.validate_property_major_repairs(record)
expect(record.errors["mrcdate"])
.to include(match I18n.t("validations.lettings.date.mrcdate.twenty_years_before_tenancy_start"))
expect(record.errors["startdate"])
.to include(match I18n.t("validations.lettings.date.startdate.twenty_years_after_mrc_date"))
end
it "must be within 20 years of the tenancy start date" do
record.startdate = Time.zone.local(2026, 2, 1)
record.mrcdate = Time.zone.local(2006, 2, 1)
date_validator.validate_property_major_repairs(record)
expect(record.errors["mrcdate"]).to be_empty
expect(record.errors["startdate"]).to be_empty
end
end
context "when reason for vacancy is first let of property" do context "when reason for vacancy is first let of property" do
it "validates that no major repair date is provided for a new build" do it "validates that no major repair date is provided for a new build" do
@ -130,6 +137,8 @@ RSpec.describe Validations::DateValidations do
date_validator.validate_property_void_date(record) date_validator.validate_property_void_date(record)
expect(record.errors["voiddate"]) expect(record.errors["voiddate"])
.to include(match I18n.t("validations.lettings.date.void_date.before_tenancy_start")) .to include(match I18n.t("validations.lettings.date.void_date.before_tenancy_start"))
expect(record.errors["startdate"])
.to include(match I18n.t("validations.lettings.date.startdate.after_void_date"))
end end
it "must be before the tenancy start date" do it "must be before the tenancy start date" do
@ -139,11 +148,11 @@ RSpec.describe Validations::DateValidations do
expect(record.errors["voiddate"]).to be_empty expect(record.errors["voiddate"]).to be_empty
end end
context "with 2024 logs or earlier" do
it "cannot be more than 10 years before the tenancy start date" do it "cannot be more than 10 years before the tenancy start date" do
record.startdate = Time.zone.local(2022, 2, 1) record.startdate = Time.zone.local(2024, 2, 1)
record.voiddate = Time.zone.local(2012, 1, 1) record.voiddate = Time.zone.local(2014, 1, 31)
date_validator.validate_property_void_date(record) date_validator.validate_property_void_date(record)
date_validator.validate_startdate(record)
expect(record.errors["voiddate"]) expect(record.errors["voiddate"])
.to include(match I18n.t("validations.lettings.date.void_date.ten_years_before_tenancy_start")) .to include(match I18n.t("validations.lettings.date.void_date.ten_years_before_tenancy_start"))
expect(record.errors["startdate"]) expect(record.errors["startdate"])
@ -151,12 +160,34 @@ RSpec.describe Validations::DateValidations do
end end
it "must be within 10 years of the tenancy start date" do it "must be within 10 years of the tenancy start date" do
record.startdate = Time.zone.local(2022, 2, 1) record.startdate = Time.zone.local(2024, 2, 1)
record.voiddate = Time.zone.local(2012, 3, 1) record.voiddate = Time.zone.local(2014, 2, 1)
date_validator.validate_property_void_date(record)
expect(record.errors["voiddate"]).to be_empty
expect(record.errors["startdate"]).to be_empty
end
end
context "with 2025 logs or later" do
it "cannot be more than 20 years before the tenancy start date" do
record.startdate = Time.zone.local(2026, 2, 1)
record.voiddate = Time.zone.local(2006, 1, 31)
date_validator.validate_property_void_date(record)
date_validator.validate_startdate(record)
expect(record.errors["voiddate"])
.to include(match I18n.t("validations.lettings.date.void_date.twenty_years_before_tenancy_start"))
expect(record.errors["startdate"])
.to include(match I18n.t("validations.lettings.date.startdate.twenty_years_after_void_date"))
end
it "must be within 20 years of the tenancy start date" do
record.startdate = Time.zone.local(2026, 2, 1)
record.voiddate = Time.zone.local(2006, 2, 1)
date_validator.validate_property_void_date(record) date_validator.validate_property_void_date(record)
expect(record.errors["voiddate"]).to be_empty expect(record.errors["voiddate"]).to be_empty
expect(record.errors["startdate"]).to be_empty expect(record.errors["startdate"]).to be_empty
end end
end
context "when major repairs have been carried out" do context "when major repairs have been carried out" do
it "void_date cannot be after major repairs date" do it "void_date cannot be after major repairs date" do

14
spec/models/validations/financial_validations_spec.rb

@ -338,20 +338,6 @@ RSpec.describe Validations::FinancialValidations do
expect(record.errors["ecstat#{n}"]).to be_empty expect(record.errors["ecstat#{n}"]).to be_empty
end end
end end
context "when the net income is lower than the hard min for their employment status for 22/23 collection" do
it "does not add an error" do
record.startdate = Time.zone.local(2022, 5, 1)
record.earnings = 50
record.incfreq = 1
record.hhmemb = 1
record.ecstat1 = 1
financial_validator.validate_net_income(record)
expect(record.errors["earnings"]).to be_empty
expect(record.errors["ecstat1"]).to be_empty
expect(record.errors["hhmemb"]).to be_empty
end
end
end end
end end

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

@ -795,17 +795,22 @@ RSpec.describe Validations::Sales::SaleInformationValidations do
record.la = "E09000001" record.la = "E09000001"
end end
it "adds an error if value * discount is more than 136,400" do it "adds an error if value * discount is more than 137,400" do
record.value = 200_000
record.discount = 80 record.discount = 80
discount_value = "£160,000.00"
sale_information_validator.validate_discount_and_value(record) 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["value"]).to include(I18n.t("validations.sales.sale_information.value.value_over_discounted_london_max", discount_value:))
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["discount"]).to include(I18n.t("validations.sales.sale_information.discount.value_over_discounted_london_max", discount_value:))
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["la"]).to include(I18n.t("validations.sales.sale_information.la.value_over_discounted_london_max", discount_value:))
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["postcode_full"]).to include(I18n.t("validations.sales.sale_information.postcode_full.value_over_discounted_london_max", discount_value:))
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.") expect(record.errors["uprn"]).to include(I18n.t("validations.sales.sale_information.uprn.value_over_discounted_london_max", discount_value:))
end end
it "does not add an error value * discount is less than 136,400" do it "does not add an error value * discount is less than 137,400" do
record.value = 200_000
record.discount = 50
# Discount value: 100,000
sale_information_validator.validate_discount_and_value(record) sale_information_validator.validate_discount_and_value(record)
expect(record.errors["value"]).to be_empty expect(record.errors["value"]).to be_empty
expect(record.errors["discount"]).to be_empty expect(record.errors["discount"]).to be_empty
@ -820,17 +825,22 @@ RSpec.describe Validations::Sales::SaleInformationValidations do
record.la = "E06000015" record.la = "E06000015"
end end
it "adds an error if value * discount is more than 136,400" do it "adds an error if value * discount is more than 103,400" do
record.value = 200_000
record.discount = 52 record.discount = 52
discount_value = "£104,000.00"
sale_information_validator.validate_discount_and_value(record) 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["value"]).to include(I18n.t("validations.sales.sale_information.value.value_over_discounted_max", discount_value:))
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["discount"]).to include(I18n.t("validations.sales.sale_information.discount.value_over_discounted_max", discount_value:))
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["la"]).to include(I18n.t("validations.sales.sale_information.la.value_over_discounted_max", discount_value:))
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["postcode_full"]).to include(I18n.t("validations.sales.sale_information.postcode_full.value_over_discounted_max", discount_value:))
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.") expect(record.errors["uprn"]).to include(I18n.t("validations.sales.sale_information.uprn.value_over_discounted_max", discount_value:))
end end
it "does not add an error value * discount is less than 136,400" do it "does not add an error value * discount is less than 103,400" do
record.value = 200_000
record.discount = 50
# Discount value: 100,000
sale_information_validator.validate_discount_and_value(record) sale_information_validator.validate_discount_and_value(record)
expect(record.errors["value"]).to be_empty expect(record.errors["value"]).to be_empty
expect(record.errors["discount"]).to be_empty expect(record.errors["discount"]).to be_empty

74
spec/models/validations/shared_validations_spec.rb

@ -116,19 +116,23 @@ RSpec.describe Validations::SharedValidations do
end end
describe "validating level of accuracy or rounding for numeric questions" do describe "validating level of accuracy or rounding for numeric questions" do
let(:sales_log) { build(:sales_log, :completed) }
before do
income_question = instance_double(Form::Question, step:, type: "numeric", id: "income1", check_answer_label: "Buyer 1’s gross annual income", page: instance_double(Form::Page, routed_to?: true))
form = instance_double(Form, numeric_questions: [income_question])
allow(FormHandler.instance).to receive(:get_form).and_return(form)
end
context "when validating a question with a step of 1" do context "when validating a question with a step of 1" do
let(:step) { 1 }
it "adds an error if input is a decimal" do it "adds an error if input is a decimal" do
sales_log.income1 = 30_000.5 sales_log.income1 = 30_000.5
shared_validator.validate_numeric_step(sales_log) shared_validator.validate_numeric_step(sales_log)
expect(sales_log.errors[:income1]).to include I18n.t("validations.shared.numeric.whole_number", field: "Buyer 1’s gross annual income") expect(sales_log.errors[:income1]).to include I18n.t("validations.shared.numeric.whole_number", field: "Buyer 1’s gross annual income")
end end
it "adds an error if the user attempts to input a number in exponent format" do
sales_log.income1 = "3e5"
shared_validator.validate_numeric_step(sales_log)
expect(sales_log.errors[:income1]).to include I18n.t("validations.shared.numeric.whole_number", field: "Buyer 1’s gross annual income")
end
it "does not add an error if input is an integer" do it "does not add an error if input is an integer" do
sales_log.income1 = 30_000 sales_log.income1 = 30_000
shared_validator.validate_numeric_step(sales_log) shared_validator.validate_numeric_step(sales_log)
@ -137,47 +141,64 @@ RSpec.describe Validations::SharedValidations do
end end
context "when validating a question with a step of 10" do context "when validating a question with a step of 10" do
it "adds an error if input is not a multiple of ten" do let(:step) { 10 }
sales_log.savings = 30_005
sales_log.jointpur = 1
shared_validator.validate_numeric_step(sales_log)
expect(sales_log.errors[:savings]).to include I18n.t("validations.shared.numeric.nearest_ten", field: "Buyers’ total savings before any deposit paid")
end
it "adds an error if the user attempts to input a number in exponent format" do it "adds an error if input is not a multiple of ten" do
sales_log.savings = "3e5" sales_log.income1 = 30_005
sales_log.jointpur = 1
shared_validator.validate_numeric_step(sales_log) shared_validator.validate_numeric_step(sales_log)
expect(sales_log.errors[:savings]).to include I18n.t("validations.shared.numeric.nearest_ten", field: "Buyers’ total savings before any deposit paid") expect(sales_log.errors[:income1]).to include I18n.t("validations.shared.numeric.nearest_ten", field: "Buyer 1’s gross annual income")
end end
it "does not add an error if input is a multiple of ten" do it "does not add an error if input is a multiple of ten" do
sales_log.savings = 30_000 sales_log.income1 = 30_000
shared_validator.validate_numeric_step(sales_log) shared_validator.validate_numeric_step(sales_log)
expect(sales_log.errors).to be_empty expect(sales_log.errors).to be_empty
end end
end end
context "when validating a question with a step of 0.01" do context "when validating a question with a step of 0.01" do
let(:step) { 0.01 }
it "adds an error if input has more than 2 decimal places" do it "adds an error if input has more than 2 decimal places" do
sales_log.mscharge = 30.7418 sales_log.income1 = 30_123.7418
shared_validator.validate_numeric_step(sales_log) shared_validator.validate_numeric_step(sales_log)
expect(sales_log.errors[:mscharge]).to include I18n.t("validations.shared.numeric.nearest_hundredth", field: "Monthly leasehold charges") expect(sales_log.errors[:income1]).to include I18n.t("validations.shared.numeric.nearest_hundredth", field: "Buyer 1’s gross annual income")
end end
it "does not add an error if the user attempts to input a number in exponent format" do it "does not add an error if input has 2 or fewer decimal places" do
sales_log.mscharge = "3e1" sales_log.income1 = 30_123.74
shared_validator.validate_numeric_step(sales_log) shared_validator.validate_numeric_step(sales_log)
expect(sales_log.errors).to be_empty expect(sales_log.errors).to be_empty
end end
end
it "does not add an error if input has 2 or fewer decimal places" do context "when validating a question with a step of 0.1" do
sales_log.mscharge = 30.74 let(:step) { 0.1 }
it "adds an error if input has more than 1 decimal place" do
sales_log.income1 = 30_123.74
shared_validator.validate_numeric_step(sales_log)
expect(sales_log.errors[:income1]).to include I18n.t("validations.shared.numeric.nearest_tenth", field: "Buyer 1’s gross annual income")
end
it "does not add an error if input has 1 or fewer decimal places" do
sales_log.income1 = 30_123.8
shared_validator.validate_numeric_step(sales_log) shared_validator.validate_numeric_step(sales_log)
expect(sales_log.errors).to be_empty expect(sales_log.errors).to be_empty
end end
end end
context "when validating a question with an unusual step" do
let(:step) { 0.001 }
it "adds an appropriate error if input does not match" do
sales_log.income1 = 30_123.7419
shared_validator.validate_numeric_step(sales_log)
expect(sales_log.errors[:income1]).to include I18n.t("validations.shared.numeric.nearest_step", field: "Buyer 1’s gross annual income", step: 0.001)
end
end
end
%i[sales_log lettings_log].each do |log_type| %i[sales_log lettings_log].each do |log_type|
describe "validate_owning_organisation_data_sharing_agremeent_signed" do describe "validate_owning_organisation_data_sharing_agremeent_signed" do
it "is valid if the Data Protection Confirmation is signed" do it "is valid if the Data Protection Confirmation is signed" do
@ -214,7 +235,6 @@ RSpec.describe Validations::SharedValidations do
end end
end end
end end
end
describe "validate numeric question input" do describe "validate numeric question input" do
it "does not allow letters" do it "does not allow letters" do
@ -229,6 +249,12 @@ RSpec.describe Validations::SharedValidations do
expect(sales_log.errors[:income1]).to include I18n.t("validations.shared.numeric.format", field: "Buyer 1’s gross annual income") expect(sales_log.errors[:income1]).to include I18n.t("validations.shared.numeric.format", field: "Buyer 1’s gross annual income")
end end
it "does not allow exponent format" do
sales_log.income1 = "1e4"
shared_validator.validate_numeric_input(sales_log)
expect(sales_log.errors[:income1]).to include I18n.t("validations.shared.numeric.format", field: "Buyer 1’s gross annual income")
end
it "allows a digit" do it "allows a digit" do
sales_log.income1 = "300" sales_log.income1 = "300"
shared_validator.validate_numeric_input(sales_log) shared_validator.validate_numeric_input(sales_log)

1
spec/requests/bulk_upload_lettings_logs_controller_spec.rb

@ -112,6 +112,7 @@ RSpec.describe BulkUploadLettingsLogsController, type: :request do
context "when requesting the previous year in a crossover period" do context "when requesting the previous year in a crossover period" do
before do before do
allow(Time.zone).to receive(:now).and_return(Time.zone.now + 1.year)
allow(FormHandler.instance).to receive(:lettings_in_crossover_period?).and_return(true) allow(FormHandler.instance).to receive(:lettings_in_crossover_period?).and_return(true)
end end

1
spec/requests/bulk_upload_sales_logs_controller_spec.rb

@ -112,6 +112,7 @@ RSpec.describe BulkUploadSalesLogsController, type: :request do
context "when requesting the previous year in a crossover period" do context "when requesting the previous year in a crossover period" do
before do before do
allow(Time.zone).to receive(:now).and_return(Time.zone.now + 1.year)
allow(FormHandler.instance).to receive(:sales_in_crossover_period?).and_return(true) allow(FormHandler.instance).to receive(:sales_in_crossover_period?).and_return(true)
end end

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

@ -1915,6 +1915,15 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do
expect(parser.errors.where(:field_125, category: :soft_validation).first.message).to eql("You told us the rent is £120.00 every week. This is higher than we would expect.") expect(parser.errors.where(:field_125, category: :soft_validation).first.message).to eql("You told us the rent is £120.00 every week. This is higher than we would expect.")
end end
end end
context "when an invalid ecstat1 is given" do
let(:attributes) { setup_section_params.merge({ field_46: 11, field_119: 123, field_118: 1 }) }
it "does not run net income soft validations validation" do
parser.valid?
expect(parser.errors.where(:field_46).count).to be(1)
end
end
end end
describe "log_already_exists?" do describe "log_already_exists?" do

Loading…
Cancel
Save