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. 55
      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. 17
      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. 21
      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. 93
      spec/models/form/lettings/subsections/income_and_benefits_spec.rb
  39. 8
      spec/models/sales_log_spec.rb
  40. 123
      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. 122
      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
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
def get_lar

2
app/models/derived_variables/sales_log_variables.rb

@ -226,7 +226,7 @@ private
end
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
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
@id = "rent_4_weekly"
@copy_key = "lettings.income_and_benefits.rent_and_charges"
@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 },
]
@depends_on = depends_on
end
def questions
@ -18,4 +15,18 @@ class Form::Lettings::Pages::Rent4Weekly < ::Form::Page
Form::Lettings::Questions::Tcharge4Weekly.new(nil, nil, self),
]
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

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

@ -3,10 +3,7 @@ class Form::Lettings::Pages::RentBiWeekly < ::Form::Page
super
@id = "rent_bi_weekly"
@copy_key = "lettings.income_and_benefits.rent_and_charges"
@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 },
]
@depends_on = depends_on
end
def questions
@ -18,4 +15,18 @@ class Form::Lettings::Pages::RentBiWeekly < ::Form::Page
Form::Lettings::Questions::TchargeBiWeekly.new(nil, nil, self),
]
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

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

@ -3,10 +3,7 @@ class Form::Lettings::Pages::RentMonthly < ::Form::Page
super
@id = "rent_monthly"
@copy_key = "lettings.income_and_benefits.rent_and_charges"
@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 },
]
@depends_on = depends_on
end
def questions
@ -18,4 +15,18 @@ class Form::Lettings::Pages::RentMonthly < ::Form::Page
Form::Lettings::Questions::TchargeMonthly.new(nil, nil, self),
]
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

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

@ -3,10 +3,7 @@ class Form::Lettings::Pages::RentWeekly < ::Form::Page
super
@id = "rent_weekly"
@copy_key = "lettings.income_and_benefits.rent_and_charges"
@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 },
]
@depends_on = depends_on
end
def questions
@ -18,4 +15,18 @@ class Form::Lettings::Pages::RentWeekly < ::Form::Page
Form::Lettings::Questions::TchargeWeekly.new(nil, nil, self),
]
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

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::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::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::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),
@ -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::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::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::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),
@ -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::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::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::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),
@ -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::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::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::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),
@ -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::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::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::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),
@ -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::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::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::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),
@ -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::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::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::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),
@ -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),
].compact
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

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::RentOrOtherCharges.new(nil, nil, self),
Form::Lettings::Pages::RentPeriod.new(nil, nil, self),
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),
carehome_questions,
Form::Lettings::Pages::RentWeekly.new(nil, nil, self),
Form::Lettings::Pages::RentBiWeekly.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::Outstanding.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

2
app/models/form/question.rb

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

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

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

6
app/models/lettings_log.rb

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

29
app/models/sales_log.rb

@ -129,33 +129,12 @@ class SalesLog < Log
def dynamically_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 += %w[address_line2 county]
not_required
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
false
end
@ -440,16 +419,15 @@ class SalesLog < Log
def income_soft_min_for_ecstat(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)
end
def should_process_uprn_change?
return unless uprn
return unless saledate
return unless collection_start_year_for_date(saledate) >= 2023
uprn_changed? || saledate_changed?
end
@ -514,8 +492,7 @@ class SalesLog < Log
"age1",
"sex1",
"ecstat1",
form.start_date.year < 2023 || uprn.blank? ? "postcode_full" : nil,
form.start_date.year >= 2023 && uprn.present? ? "uprn" : nil].compact
uprn.blank? ? "postcode_full" : "uprn"].compact
end
def soctenant_is_inferred?

55
app/models/validations/date_validations.rb

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

2
app/models/validations/financial_validations.rb

@ -25,7 +25,7 @@ module Validations::FinancialValidations
end
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
frequency = record.form.get_question("incfreq", record).label_from_value(record.incfreq).downcase
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)
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 :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")

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

@ -82,7 +82,7 @@ module Validations::Sales::FinancialValidations
def validate_child_income(record)
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 :income2, I18n.t("validations.sales.financial.income2.child_has_income")
end

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

@ -13,8 +13,6 @@ module Validations::Sales::HouseholdValidations
end
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?
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")

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.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|
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
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|
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

17
app/models/validations/shared_validations.rb

@ -54,14 +54,15 @@ module Validations::SharedValidations
field = question.check_answer_label || question.id
incorrect_accuracy = (value.to_d * 100) % (question.step * 100) != 0
if question.step < 1 && 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
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:)
end
next unless incorrect_accuracy
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 10 then record.errors.add question.id.to_sym, I18n.t("validations.shared.numeric.nearest_ten", field:)
else
record.errors.add question.id.to_sym, I18n.t("validations.shared.numeric.nearest_step", field:, step: question.step)
end
end
end

4
app/models/validations/soft_validations.rb

@ -16,13 +16,13 @@ module Validations::SoftValidations
}.freeze
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)
end
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)
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>

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

@ -28,29 +28,14 @@
<%= accordion.with_section(heading_text: "Using the bulk upload template") do %>
<p class="govuk-body">For each collection year, we publish a bulk upload template and specification.</p>
<% if @form.year == 2023 %>
<p class="govuk-body">The bulk upload templates contain 7 or 8 rows of ‘headers’ with information about how to fill in the template, including:</p>
<% else %>
<p class="govuk-body">The bulk upload templates contain 8 rows of ‘headers’ with information about how to fill in the template, including:</p>
<% end %>
<p class="govuk-body">The bulk upload templates contain 8 rows of ‘headers’ with information about how to fill in the template, including:</p>
<%= 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">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 sales bulk upload template (2024 to 2025)", @form.sales_template_path %></p>
<% end %>
<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>
<% end %>
<%= accordion.with_section(heading_text: "Using the bulk upload specification") do %>

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

@ -84,9 +84,9 @@ en:
relat2:
page_header: ""
check_answer_label: "Person 2’s relationship to the lead tenant"
check_answer_label: "Person 2 lead tenant’s partner"
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:
page_header: ""
@ -119,9 +119,9 @@ en:
relat3:
page_header: ""
check_answer_label: "Person 3’s relationship to the lead tenant"
check_answer_label: "Person 3 lead tenant’s partner"
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:
page_header: ""
@ -154,9 +154,9 @@ en:
relat4:
page_header: ""
check_answer_label: "Person 4’s relationship to the lead tenant"
check_answer_label: "Person 4 lead tenant’s partner"
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:
page_header: ""
@ -189,9 +189,9 @@ en:
relat5:
page_header: ""
check_answer_label: "Person 5’s relationship to the lead tenant"
check_answer_label: "Person 5 lead tenant’s partner"
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:
page_header: ""
@ -224,9 +224,9 @@ en:
relat6:
page_header: ""
check_answer_label: "Person 6’s relationship to the lead tenant"
check_answer_label: "Person 6 lead tenant’s partner"
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:
page_header: ""
@ -259,9 +259,9 @@ en:
relat7:
page_header: ""
check_answer_label: "Person 7’s relationship to the lead tenant"
check_answer_label: "Person 7 lead tenant’s partner"
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:
page_header: ""
@ -294,9 +294,9 @@ en:
relat8:
page_header: ""
check_answer_label: "Person 8’s relationship to the lead tenant"
check_answer_label: "Person 8 lead tenant’s partner"
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:
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."
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."
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:
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."
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."
void_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."
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:
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."
value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £136,400 for properties in London."
value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £102,400 for properties outside of London."
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 £103,400 for properties outside of London."
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_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."
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."
value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £136,400 for properties in London."
value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £102,400 for properties outside of London."
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 £103,400 for properties outside of London."
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."
out_of_range: "Loan, grants or subsidies must be between £9,000 and £16,000."
@ -119,14 +119,14 @@ en:
staircase:
mortgage_used_value: "You must answer either ‘yes’ or ‘no’ to the question ‘was a mortgage used’ for staircasing transactions."
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_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_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 £103,400 for properties outside of London."
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_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_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 £103,400 for properties outside of London."
postcode_full:
value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £136,400 for properties in London."
value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £102,400 for properties outside of London."
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 £103,400 for properties outside of London."
numstair:
must_be_greater_than_one: "The number of staircasing transactions must be greater than 1 when this is not the first staircasing transaction."
firststair:

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

@ -9,7 +9,9 @@ en:
above_min: "%{field} must be at least %{min}."
whole_number: "%{field} must be a whole number."
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_step: "${field} must be given to the nearest %{step}."
normal_format: "Enter 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
context "when during crossover period" do
before do
Timecop.freeze(2023, 5, 1)
Timecop.freeze(2024, 5, 1)
end
after do
@ -38,15 +38,15 @@ RSpec.describe "Bulk upload sales log" do
click_button("Continue")
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_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")
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")
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")
click_button("Continue")
click_button("Continue")
choose("2023 to 2024")
choose("2024 to 2025")
click_button("Continue")
click_button("Continue")
@ -96,7 +96,7 @@ RSpec.describe "Bulk upload sales log" do
context "when not in crossover period" do
before do
Timecop.freeze(2024, 2, 1)
Timecop.freeze(2025, 2, 1)
end
after do
@ -108,7 +108,7 @@ RSpec.describe "Bulk upload sales log" do
expect(page).to have_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")
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
allow(section).to receive(:form).and_return(form)
allow(form).to receive(:start_year_2025_or_later?).and_return(false)
end
it "has correct section" do
@ -304,6 +305,161 @@ RSpec.describe Form::Lettings::Subsections::HouseholdCharacteristics, type: :mod
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
expect(household_characteristics.id).to eq("household_characteristics")
end

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

@ -5,40 +5,77 @@ RSpec.describe Form::Lettings::Subsections::IncomeAndBenefits, type: :model do
let(:subsection_id) { 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:) }
before do
allow(form).to receive(:start_year_2025_or_later?).and_return(start_year_2025_or_later)
end
it "has correct section" do
expect(income_and_benefits.section).to eq(section)
end
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
care_home_weekly
care_home_bi_weekly
care_home_4_weekly
care_home_monthly
care_home_charges_value_check
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
],
)
context "with 2024 form" do
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
care_home_weekly
care_home_bi_weekly
care_home_4_weekly
care_home_monthly
care_home_charges_value_check
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
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

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))
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
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)

123
spec/models/validations/date_validations_spec.rb

@ -21,22 +21,6 @@ RSpec.describe Validations::DateValidations do
expect(record.errors["startdate"]).to be_empty
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
record.startdate = Time.zone.today - 30.days
record.scheme = scheme
@ -59,6 +43,8 @@ RSpec.describe Validations::DateValidations do
date_validator.validate_property_major_repairs(record)
expect(record.errors["mrcdate"])
.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
it "must be before the tenancy start date" do
@ -68,23 +54,44 @@ RSpec.describe Validations::DateValidations do
expect(record.errors["mrcdate"]).to be_empty
end
it "cannot be more than 10 years before the tenancy start date" do
record.startdate = Time.zone.local(2022, 2, 1)
record.mrcdate = Time.zone.local(2012, 1, 1)
date_validator.validate_property_major_repairs(record)
date_validator.validate_startdate(record)
expect(record.errors["mrcdate"])
.to include(match I18n.t("validations.lettings.date.mrcdate.ten_years_before_tenancy_start"))
expect(record.errors["startdate"])
.to include(match I18n.t("validations.lettings.date.startdate.ten_years_after_mrc_date"))
context "with 2024 logs or earlier" do
it "cannot be more than 10 years before the tenancy start date" do
record.startdate = Time.zone.local(2024, 2, 1)
record.mrcdate = Time.zone.local(2014, 1, 31)
date_validator.validate_property_major_repairs(record)
expect(record.errors["mrcdate"])
.to include(match I18n.t("validations.lettings.date.mrcdate.ten_years_before_tenancy_start"))
expect(record.errors["startdate"])
.to include(match I18n.t("validations.lettings.date.startdate.ten_years_after_mrc_date"))
end
it "must be within 10 years of the tenancy start date" do
record.startdate = Time.zone.local(2024, 2, 1)
record.mrcdate = Time.zone.local(2014, 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
it "must be within 10 years of the tenancy start date" do
record.startdate = Time.zone.local(2022, 2, 1)
record.mrcdate = Time.zone.local(2012, 3, 1)
date_validator.validate_property_major_repairs(record)
expect(record.errors["mrcdate"]).to be_empty
expect(record.errors["startdate"]).to be_empty
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
@ -130,6 +137,8 @@ RSpec.describe Validations::DateValidations do
date_validator.validate_property_void_date(record)
expect(record.errors["voiddate"])
.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
it "must be before the tenancy start date" do
@ -139,23 +148,45 @@ RSpec.describe Validations::DateValidations do
expect(record.errors["voiddate"]).to be_empty
end
it "cannot be more than 10 years before the tenancy start date" do
record.startdate = Time.zone.local(2022, 2, 1)
record.voiddate = Time.zone.local(2012, 1, 1)
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.ten_years_before_tenancy_start"))
expect(record.errors["startdate"])
.to include(match I18n.t("validations.lettings.date.startdate.ten_years_after_void_date"))
context "with 2024 logs or earlier" do
it "cannot be more than 10 years before the tenancy start date" do
record.startdate = Time.zone.local(2024, 2, 1)
record.voiddate = Time.zone.local(2014, 1, 31)
date_validator.validate_property_void_date(record)
expect(record.errors["voiddate"])
.to include(match I18n.t("validations.lettings.date.void_date.ten_years_before_tenancy_start"))
expect(record.errors["startdate"])
.to include(match I18n.t("validations.lettings.date.startdate.ten_years_after_void_date"))
end
it "must be within 10 years of the tenancy start date" do
record.startdate = Time.zone.local(2024, 2, 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
it "must be within 10 years of the tenancy start date" do
record.startdate = Time.zone.local(2022, 2, 1)
record.voiddate = Time.zone.local(2012, 3, 1)
date_validator.validate_property_void_date(record)
expect(record.errors["voiddate"]).to be_empty
expect(record.errors["startdate"]).to be_empty
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)
expect(record.errors["voiddate"]).to be_empty
expect(record.errors["startdate"]).to be_empty
end
end
context "when major repairs have been carried out" 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
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

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

@ -795,17 +795,22 @@ RSpec.describe Validations::Sales::SaleInformationValidations do
record.la = "E09000001"
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
discount_value = "£160,000.00"
sale_information_validator.validate_discount_and_value(record)
expect(record.errors["value"]).to include("The percentage discount multiplied by the purchase price is £160,000.00. This figure should not be more than £136,400 for properties in London.")
expect(record.errors["discount"]).to include("The percentage discount multiplied by the purchase price is £160,000.00. This figure should not be more than £136,400 for properties in London.")
expect(record.errors["la"]).to include("The percentage discount multiplied by the purchase price is £160,000.00. This figure should not be more than £136,400 for properties in London.")
expect(record.errors["postcode_full"]).to include("The percentage discount multiplied by the purchase price is £160,000.00. This figure should not be more than £136,400 for properties in London.")
expect(record.errors["uprn"]).to include("The percentage discount multiplied by the purchase price is £160,000.00. This figure should not be more than £136,400 for properties in London.")
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(I18n.t("validations.sales.sale_information.discount.value_over_discounted_london_max", discount_value:))
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(I18n.t("validations.sales.sale_information.postcode_full.value_over_discounted_london_max", discount_value:))
expect(record.errors["uprn"]).to include(I18n.t("validations.sales.sale_information.uprn.value_over_discounted_london_max", discount_value:))
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)
expect(record.errors["value"]).to be_empty
expect(record.errors["discount"]).to be_empty
@ -820,17 +825,22 @@ RSpec.describe Validations::Sales::SaleInformationValidations do
record.la = "E06000015"
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
discount_value = "£104,000.00"
sale_information_validator.validate_discount_and_value(record)
expect(record.errors["value"]).to include("The percentage discount multiplied by the purchase price is £104,000.00. This figure should not be more than £102,400 for properties outside of London.")
expect(record.errors["discount"]).to include("The percentage discount multiplied by the purchase price is £104,000.00. This figure should not be more than £102,400 for properties outside of London.")
expect(record.errors["la"]).to include("The percentage discount multiplied by the purchase price is £104,000.00. This figure should not be more than £102,400 for properties outside of London.")
expect(record.errors["postcode_full"]).to include("The percentage discount multiplied by the purchase price is £104,000.00. This figure should not be more than £102,400 for properties outside of London.")
expect(record.errors["uprn"]).to include("The percentage discount multiplied by the purchase price is £104,000.00. This figure should not be more than £102,400 for properties outside of London.")
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(I18n.t("validations.sales.sale_information.discount.value_over_discounted_max", discount_value:))
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(I18n.t("validations.sales.sale_information.postcode_full.value_over_discounted_max", discount_value:))
expect(record.errors["uprn"]).to include(I18n.t("validations.sales.sale_information.uprn.value_over_discounted_max", discount_value:))
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)
expect(record.errors["value"]).to be_empty
expect(record.errors["discount"]).to be_empty

122
spec/models/validations/shared_validations_spec.rb

@ -116,19 +116,23 @@ RSpec.describe Validations::SharedValidations do
end
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
let(:step) { 1 }
it "adds an error if input is a decimal" do
sales_log.income1 = 30_000.5
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 "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
sales_log.income1 = 30_000
shared_validator.validate_numeric_step(sales_log)
@ -137,80 +141,96 @@ RSpec.describe Validations::SharedValidations do
end
context "when validating a question with a step of 10" do
it "adds an error if input is not a multiple of ten" do
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
let(:step) { 10 }
it "adds an error if the user attempts to input a number in exponent format" do
sales_log.savings = "3e5"
sales_log.jointpur = 1
it "adds an error if input is not a multiple of ten" do
sales_log.income1 = 30_005
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
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)
expect(sales_log.errors).to be_empty
end
end
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
sales_log.mscharge = 30.7418
sales_log.income1 = 30_123.7418
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
it "does not add an error if the user attempts to input a number in exponent format" do
sales_log.mscharge = "3e1"
it "does not add an error if input has 2 or fewer decimal places" do
sales_log.income1 = 30_123.74
shared_validator.validate_numeric_step(sales_log)
expect(sales_log.errors).to be_empty
end
end
it "does not add an error if input has 2 or fewer decimal places" do
sales_log.mscharge = 30.74
context "when validating a question with a step of 0.1" do
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)
expect(sales_log.errors).to be_empty
end
end
%i[sales_log lettings_log].each do |log_type|
describe "validate_owning_organisation_data_sharing_agremeent_signed" do
it "is valid if the Data Protection Confirmation is signed" do
log = build(log_type, :in_progress, owning_organisation: create(:organisation))
context "when validating a question with an unusual step" do
let(:step) { 0.001 }
expect(log).to be_valid
end
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
it "is valid when owning_organisation nil" do
log = build(log_type, owning_organisation: nil)
%i[sales_log lettings_log].each do |log_type|
describe "validate_owning_organisation_data_sharing_agremeent_signed" do
it "is valid if the Data Protection Confirmation is signed" do
log = build(log_type, :in_progress, owning_organisation: create(:organisation))
expect(log).to be_valid
end
expect(log).to be_valid
end
it "is not valid if the Data Protection Confirmation is not signed" do
log = build(log_type, owning_organisation: create(:organisation, :without_dpc))
it "is valid when owning_organisation nil" do
log = build(log_type, owning_organisation: nil)
expect(log).not_to be_valid
expect(log.errors[:owning_organisation_id]).to eq(["The organisation must accept the Data Sharing Agreement before it can be selected as the owning organisation."])
end
expect(log).to be_valid
end
it "is not valid if the Data Protection Confirmation is not signed" do
log = build(log_type, owning_organisation: create(:organisation, :without_dpc))
context "when updating" do
let(:log) { create(log_type, :in_progress) }
let(:org_with_dpc) { create(:organisation) }
let(:org_without_dpc) { create(:organisation, :without_dpc) }
expect(log).not_to be_valid
expect(log.errors[:owning_organisation_id]).to eq(["The organisation must accept the Data Sharing Agreement before it can be selected as the owning organisation."])
end
it "is valid when changing to another org with a signed Data Protection Confirmation" do
expect { log.owning_organisation = org_with_dpc }.not_to change(log, :valid?)
end
context "when updating" do
let(:log) { create(log_type, :in_progress) }
let(:org_with_dpc) { create(:organisation) }
let(:org_without_dpc) { create(:organisation, :without_dpc) }
it "is valid when changing to another org with a signed Data Protection Confirmation" do
expect { log.owning_organisation = org_with_dpc }.not_to change(log, :valid?)
end
it "invalid when changing to another org without a signed Data Protection Confirmation" do
expect { log.owning_organisation = org_without_dpc }.to change(log, :valid?).from(true).to(false).and(change { log.errors[:owning_organisation_id] }.to(["The organisation must accept the Data Sharing Agreement before it can be selected as the owning organisation."]))
end
it "invalid when changing to another org without a signed Data Protection Confirmation" do
expect { log.owning_organisation = org_without_dpc }.to change(log, :valid?).from(true).to(false).and(change { log.errors[:owning_organisation_id] }.to(["The organisation must accept the Data Sharing Agreement before it can be selected as the owning organisation."]))
end
end
end
@ -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")
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
sales_log.income1 = "300"
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
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)
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
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)
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.")
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
describe "log_already_exists?" do

Loading…
Cancel
Save