Browse Source

Merge branch 'main' into CLDC-4178-add-gender-same-as-sex-question-for-sales

# Conflicts:
#	app/helpers/bulk_upload/sales_log_to_csv.rb
#	app/models/form/sales/subsections/household_characteristics.rb
#	app/services/bulk_upload/sales/year2026/csv_parser.rb
#	app/services/bulk_upload/sales/year2026/row_parser.rb
#	config/locales/forms/2026/sales/household_characteristics.en.yml
#	db/schema.rb
#	spec/fixtures/files/2026_27_sales_bulk_upload.csv
#	spec/fixtures/files/sales_logs_csv_export_codes_26.csv
#	spec/fixtures/files/sales_logs_csv_export_labels_26.csv
#	spec/fixtures/files/sales_logs_csv_export_non_support_codes_26.csv
#	spec/fixtures/files/sales_logs_csv_export_non_support_labels_26.csv
#	spec/models/form/sales/subsections/household_characteristics_spec.rb
#	spec/services/bulk_upload/sales/year2026/row_parser_spec.rb
pull/3188/head
Nat Dean-Lewis 3 months ago
parent
commit
58191735e5
  1. 27
      app/helpers/bulk_upload/sales_log_to_csv.rb
  2. 2
      app/models/form/sales/subsections/household_characteristics.rb
  3. 2
      app/models/validations/financial_validations.rb
  4. 29
      app/services/bulk_upload/lettings/year2025/row_parser.rb
  5. 29
      app/services/bulk_upload/lettings/year2026/row_parser.rb
  6. 31
      app/services/bulk_upload/sales/year2025/row_parser.rb
  7. 4
      app/services/bulk_upload/sales/year2026/csv_parser.rb
  8. 204
      app/services/bulk_upload/sales/year2026/row_parser.rb
  9. 13
      app/services/exports/sales_log_export_constants.rb
  10. 4
      app/services/exports/sales_log_export_service.rb
  11. 2
      app/views/form/page.html.erb
  12. 56
      config/locales/forms/2026/lettings/household_characteristics.en.yml
  13. 25
      db/migrate/20260225135309_add_composite_indexes_for_logs_organisation_lookup.rb
  14. 6
      db/schema.rb
  15. 6
      spec/factories/sales_log.rb
  16. 18
      spec/fixtures/exports/sales_log_26_27.xml
  17. 10
      spec/fixtures/files/2026_27_sales_bulk_upload.csv
  18. 3
      spec/fixtures/files/sales_logs_csv_export_codes_23.csv
  19. 2
      spec/fixtures/files/sales_logs_csv_export_codes_24.csv
  20. 2
      spec/fixtures/files/sales_logs_csv_export_codes_25.csv
  21. 6
      spec/fixtures/files/sales_logs_csv_export_codes_26.csv
  22. 3
      spec/fixtures/files/sales_logs_csv_export_labels_23.csv
  23. 2
      spec/fixtures/files/sales_logs_csv_export_labels_24.csv
  24. 2
      spec/fixtures/files/sales_logs_csv_export_labels_25.csv
  25. 6
      spec/fixtures/files/sales_logs_csv_export_labels_26.csv
  26. 6
      spec/fixtures/files/sales_logs_csv_export_non_support_codes_26.csv
  27. 2
      spec/fixtures/files/sales_logs_csv_export_non_support_labels_24.csv
  28. 2
      spec/fixtures/files/sales_logs_csv_export_non_support_labels_25.csv
  29. 6
      spec/fixtures/files/sales_logs_csv_export_non_support_labels_26.csv
  30. 7
      spec/models/form/sales/subsections/household_characteristics_spec.rb
  31. 14
      spec/services/bulk_upload/lettings/year2025/row_parser_spec.rb
  32. 14
      spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb
  33. 14
      spec/services/bulk_upload/sales/year2025/row_parser_spec.rb
  34. 34
      spec/services/bulk_upload/sales/year2026/row_parser_spec.rb
  35. 94
      spec/services/csv/sales_log_csv_service_spec.rb
  36. 10
      spec/services/exports/sales_log_export_service_spec.rb

27
app/helpers/bulk_upload/sales_log_to_csv.rb

@ -565,14 +565,14 @@ class BulkUpload::SalesLogToCsv
log.wchair,
log.age1,
log.sex1,
log.sexrab1,
log.ethnic, # 30
log.nationality_all_group,
log.ecstat1,
log.buy1livein,
{ "P" => 1, "X" => 2, "R" => 3 }[log.relat2],
log.age2,
log.sex2,
log.sexrab2,
log.ethnic_group2,
log.nationality_all_buyer2_group,
log.ecstat2,
@ -581,19 +581,19 @@ class BulkUpload::SalesLogToCsv
{ "P" => 1, "X" => 2, "R" => 3 }[log.relat3],
log.age3,
log.sex3,
log.sexrab3,
log.ecstat3,
{ "P" => 1, "X" => 2, "R" => 3 }[log.relat4],
log.age4,
log.sex4,
log.sexrab4,
log.ecstat4,
{ "P" => 1, "X" => 2, "R" => 3 }[log.relat5], # 50
log.age5,
log.sex5,
log.sexrab5,
log.ecstat5,
{ "P" => 1, "X" => 2, "R" => 3 }[log.relat6],
log.age6,
log.sex6,
log.sexrab6,
log.ecstat6,
log.prevten,
@ -666,26 +666,19 @@ class BulkUpload::SalesLogToCsv
log.deposit, # 120
log.mscharge,
log.sexrab1,
log.sexrab2,
log.sexrab3,
log.sexrab4,
log.sexrab5,
log.sexrab6,
log.buildheightclass,
log.buildheightclass, # 122
log.gender_same_as_sex1,
log.gender_description1, # 130
log.gender_description1,
log.gender_same_as_sex2,
log.gender_description2,
log.gender_same_as_sex3,
log.gender_description3,
log.gender_same_as_sex4,
log.gender_description4,
log.gender_same_as_sex5,
log.gender_same_as_sex5, # 131
log.gender_description5,
log.gender_same_as_sex6,
log.gender_description6, # 140
log.gender_description6, # 134
]
end

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

@ -77,9 +77,9 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection
(Form::Sales::Pages::NotRetiredValueCheck.new("age_#{person_index}_not_retired_value_check", nil, self, person_index:) if form.start_year_2024_or_later?),
(Form::Sales::Pages::PersonStudentNotChildValueCheck.new("age_#{person_index}_student_not_child_value_check", nil, self, person_index:) unless form.start_year_2025_or_later?),
(Form::Sales::Pages::PartnerUnder16ValueCheck.new("age_#{person_index}_partner_under_16_value_check", nil, self, person_index:) if form.start_year_2024_or_later?),
(Form::Sales::Pages::PersonGenderIdentity.new("person_#{person_index}_gender_identity", nil, self, person_index:) unless form.start_year_2026_or_later?),
(Form::Sales::Pages::PersonSexRegisteredAtBirth.new("person_#{person_index}_sex_registered_at_birth", nil, self, person_index:) if form.start_year_2026_or_later?),
(Form::Sales::Pages::PersonGenderSameAsSex.new("person_#{person_index}_gender_same_as_sex", nil, self, person_index:) if form.start_year_2026_or_later?),
(Form::Sales::Pages::PersonGenderIdentity.new("person_#{person_index}_gender_identity", nil, self, person_index:) unless form.start_year_2026_or_later?),
Form::Sales::Pages::PersonWorkingSituation.new("person_#{person_index}_working_situation", nil, self, person_index:),
Form::Sales::Pages::RetirementValueCheck.new("working_situation_#{person_index}_retirement_value_check", nil, self, person_index:),
(Form::Sales::Pages::NotRetiredValueCheck.new("working_situation_#{person_index}_not_retired_value_check", nil, self, person_index:) if form.start_year_2024_or_later?),

2
app/models/validations/financial_validations.rb

@ -228,7 +228,7 @@ private
end
if record.weekly_value(record["brent"]) > rent_range.hard_max
record.errors.add :brent, :over_hard_max, message: I18n.t("validations.lettings.financial.brent.above_hard_max")
record.errors.add :brent, :above_hard_max, message: I18n.t("validations.lettings.financial.brent.above_hard_max")
record.errors.add :beds, I18n.t("validations.lettings.financial.beds.rent_above_hard_max")
record.errors.add :uprn, I18n.t("validations.lettings.financial.uprn.rent_above_hard_max")
record.errors.add :la, I18n.t("validations.lettings.financial.la.rent_above_hard_max")

29
app/services/bulk_upload/lettings/year2025/row_parser.rb

@ -148,6 +148,26 @@ class BulkUpload::Lettings::Year2025::RowParser
ERROR_BASE_KEY = "validations.lettings.2025.bulk_upload".freeze
CASE_INSENSITIVE_FIELDS = [
:field_42, # What is the lead tenant’s age?
:field_48, # What is person 2’s age?
:field_52, # What is person 3’s age?
:field_56, # What is person 4’s age?
:field_60, # What is person 5’s age?
:field_64, # What is person 6’s age?
:field_68, # What is person 7’s age?
:field_72, # What is person 8’s age?
:field_43, # Which of these best describes the lead tenant’s gender identity?
:field_49, # Which of these best describes person 2’s gender identity?
:field_53, # Which of these best describes person 3’s gender identity?
:field_57, # Which of these best describes person 4’s gender identity?
:field_61, # Which of these best describes person 5’s gender identity?
:field_65, # Which of these best describes person 6’s gender identity?
:field_69, # Which of these best describes person 7’s gender identity?
:field_73, # Which of these best describes person 8’s gender identity?
].freeze
attribute :bulk_upload
attribute :block_log_creation, :boolean, default: -> { false }
@ -459,6 +479,8 @@ class BulkUpload::Lettings::Year2025::RowParser
return @valid = true if blank_row?
normalise_case_insensitive_fields
super(:before_log)
@before_errors = errors.dup
@ -560,6 +582,13 @@ class BulkUpload::Lettings::Year2025::RowParser
private
def normalise_case_insensitive_fields
CASE_INSENSITIVE_FIELDS.each do |field|
value = send(field)
send("#{field}=", value.upcase) if value.present?
end
end
def validate_valid_radio_option
log.attributes.each_key do |question_id|
question = log.form.get_question(question_id, log)

29
app/services/bulk_upload/lettings/year2026/row_parser.rb

@ -166,6 +166,26 @@ class BulkUpload::Lettings::Year2026::RowParser
ERROR_BASE_KEY = "validations.lettings.2026.bulk_upload".freeze
CASE_INSENSITIVE_FIELDS = [
:field_41, # What is the lead tenant's age?
:field_48, # What is person 2's age?
:field_54, # What is person 3's age?
:field_60, # What is person 4's age?
:field_66, # What is person 5's age?
:field_72, # What is person 6's age?
:field_78, # What is person 7's age?
:field_84, # What is person 8's age?
:field_42, # What is the lead tenant's sex?
:field_50, # What is person 2's sex?
:field_56, # What is person 3's sex?
:field_62, # What is person 4's sex?
:field_68, # What is person 5's sex?
:field_74, # What is person 6's sex?
:field_80, # What is person 7's sex?
:field_86, # What is person 8's sex?
].freeze
attribute :bulk_upload
attribute :block_log_creation, :boolean, default: -> { false }
@ -494,6 +514,8 @@ class BulkUpload::Lettings::Year2026::RowParser
return @valid = true if blank_row?
normalise_case_insensitive_fields
super(:before_log)
@before_errors = errors.dup
@ -600,6 +622,13 @@ class BulkUpload::Lettings::Year2026::RowParser
private
def normalise_case_insensitive_fields
CASE_INSENSITIVE_FIELDS.each do |field|
value = send(field)
send("#{field}=", value.upcase) if value.present?
end
end
def validate_valid_radio_option
log.attributes.each_key do |question_id|
question = log.form.get_question(question_id, log)

31
app/services/bulk_upload/sales/year2025/row_parser.rb

@ -139,6 +139,28 @@ class BulkUpload::Sales::Year2025::RowParser
ERROR_BASE_KEY = "validations.sales.2025.bulk_upload".freeze
CASE_INSENSITIVE_FIELDS = [
:field_28, # Age of buyer 1
:field_35, # Age of person 2
:field_43, # Age of person 3
:field_47, # Age of person 4
:field_51, # Age of person 5
:field_55, # Age of person 6
:field_29, # Gender identity of buyer 1
:field_36, # Gender identity of person 2
:field_44, # Gender identity of person 3
:field_48, # Gender identity of person 4
:field_52, # Gender identity of person 5
:field_56, # Gender identity of person 6
:field_64, # What was buyer 2’s previous tenure?
:field_75, # What is the total amount the buyers had in savings before they paid any deposit for the property?
:field_70, # What is buyer 1’s gross annual income?
:field_72, # What is buyer 2’s gross annual income?
].freeze
attribute :bulk_upload
attribute :block_log_creation, :boolean, default: -> { false }
@ -454,6 +476,8 @@ class BulkUpload::Sales::Year2025::RowParser
return true if blank_row?
normalise_case_insensitive_fields
super(:before_log)
@before_errors = errors.dup
@ -525,6 +549,13 @@ class BulkUpload::Sales::Year2025::RowParser
private
def normalise_case_insensitive_fields
CASE_INSENSITIVE_FIELDS.each do |field|
value = send(field)
send("#{field}=", value.upcase) if value.present?
end
end
def prevtenbuy2
case field_64
when "R"

4
app/services/bulk_upload/sales/year2026/csv_parser.rb

@ -4,7 +4,7 @@ class BulkUpload::Sales::Year2026::CsvParser
include CollectionTimeHelper
# TODO: CLDC-4162: Update when 2026 format is known
FIELDS = 140
FIELDS = 134
FORM_YEAR = 2026
attr_reader :path
@ -27,7 +27,7 @@ class BulkUpload::Sales::Year2026::CsvParser
def cols
# TODO: CLDC-4162: Update when 2026 format is known
@cols ||= ("A".."EE").to_a
@cols ||= ("A".."ED").to_a
end
def row_parsers

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

@ -35,14 +35,14 @@ class BulkUpload::Sales::Year2026::RowParser
field_27: "Is the property built or adapted to wheelchair user standards?",
field_28: "Age of buyer 1",
field_29: "Gender identity of buyer 1",
field_29: "Buyer 1's sex, as registered at birth",
field_30: "What is buyer 1’s ethnic group?",
field_31: "What is buyer 1’s nationality?",
field_32: "Working situation of buyer 1",
field_33: "Will buyer 1 live in the property?",
field_34: "Is buyer 2 or person 2 the partner of buyer 1?",
field_35: "Age of person 2",
field_36: "Gender identity of person 2",
field_36: "Buyer/Person 2's sex, as registered at birth",
field_37: "Which of the following best describes buyer 2’s ethnic background?",
field_38: "What is buyer 2’s nationality?",
field_39: "What is buyer 2 or person 2’s working situation?",
@ -51,19 +51,19 @@ class BulkUpload::Sales::Year2026::RowParser
field_42: "Is person 3 the partner of buyer 1?",
field_43: "Age of person 3",
field_44: "Gender identity of person 3",
field_44: "Person 3's sex, as registered at birth",
field_45: "Working situation of person 3",
field_46: "Is person 4 the partner of buyer 1?",
field_47: "Age of person 4",
field_48: "Gender identity of person 4",
field_48: "Person 4's sex, as registered at birth",
field_49: "Working situation of person 4",
field_50: "Is person 5 the partner of buyer 1?",
field_51: "Age of person 5",
field_52: "Gender identity of person 5",
field_52: "Person 5's sex, as registered at birth",
field_53: "Working situation of person 5",
field_54: "Is person 6 the partner of buyer 1?",
field_55: "Age of person 6",
field_56: "Gender identity of person 6",
field_56: "Person 6's sex, as registered at birth",
field_57: "Working situation of person 6",
field_58: "What was buyer 1’s previous tenure?",
@ -136,30 +136,45 @@ class BulkUpload::Sales::Year2026::RowParser
field_120: "How much was the cash deposit paid on the property?",
field_121: "What are the total monthly leasehold charges for the property?",
field_122: "Buyer 1's sex, as registered at birth",
field_123: "Buyer/Person 2's sex, as registered at birth",
field_124: "Person 3's sex, as registered at birth",
field_125: "Person 4's sex, as registered at birth",
field_126: "Person 5's sex, as registered at birth",
field_127: "Person 6's sex, as registered at birth",
field_128: "What is the building height classification?",
field_129: "Is the gender buyer 1 identifies with the same as their sex registered at birth?",
field_130: "If 'No', enter buyer 1's gender identity",
field_131: "Is the gender buyer/person 2 identifies with the same as their sex registered at birth?",
field_132: "If 'No', enter buyer/person 2's gender identity",
field_133: "Is the gender person 3 identifies with the same as their sex registered at birth?",
field_134: "If 'No', enter person 3's gender identity",
field_135: "Is the gender person 4 identifies with the same as their sex registered at birth?",
field_136: "If 'No', enter person 4's gender identity",
field_137: "Is the gender person 5 identifies with the same as their sex registered at birth?",
field_138: "If 'No', enter person 5's gender identity",
field_139: "Is the gender person 6 identifies with the same as their sex registered at birth?",
field_140: "If 'No', enter person 6's gender identity",
field_122: "What is the building height classification?",
field_123: "Is the gender buyer 1 identifies with the same as their sex registered at birth?",
field_124: "If 'No', enter buyer 1's gender identity",
field_125: "Is the gender buyer/person 2 identifies with the same as their sex registered at birth?",
field_126: "If 'No', enter buyer/person 2's gender identity",
field_127: "Is the gender person 3 identifies with the same as their sex registered at birth?",
field_128: "If 'No', enter person 3's gender identity",
field_129: "Is the gender person 4 identifies with the same as their sex registered at birth?",
field_130: "If 'No', enter person 4's gender identity",
field_131: "Is the gender person 5 identifies with the same as their sex registered at birth?",
field_132: "If 'No', enter person 5's gender identity",
field_133: "Is the gender person 6 identifies with the same as their sex registered at birth?",
field_134: "If 'No', enter person 6's gender identity",
}.freeze
ERROR_BASE_KEY = "validations.sales.2026.bulk_upload".freeze
CASE_INSENSITIVE_FIELDS = [
:field_28, # Age of buyer 1
:field_35, # Age of person 2
:field_43, # Age of person 3
:field_47, # Age of person 4
:field_51, # Age of person 5
:field_55, # Age of person 6
:field_29, # Buyer 1's sex, as registered at birth
:field_36, # Buyer/Person 2's sex, as registered at birth
:field_44, # Person 3's sex, as registered at birth
:field_48, # Person 4's sex, as registered at birth
:field_52, # Person 5's sex, as registered at birth
:field_56, # Person 6's sex, as registered at birth
:field_64, # What was buyer 2’s previous tenure?
:field_75, # What is the total amount the buyers had in savings before they paid any deposit for the property?
:field_70, # What is buyer 1’s gross annual income?
:field_72, # What is buyer 2’s gross annual income?
].freeze
attribute :bulk_upload
attribute :block_log_creation, :boolean, default: -> { false }
@ -295,27 +310,20 @@ class BulkUpload::Sales::Year2026::RowParser
attribute :field_119, :integer
attribute :field_120, :decimal
attribute :field_121, :decimal
attribute :field_122, :integer
attribute :field_122, :string
attribute :field_123, :string
attribute :field_123, :integer
attribute :field_124, :string
attribute :field_125, :string
attribute :field_125, :integer
attribute :field_126, :string
attribute :field_127, :string
attribute :field_128, :integer
attribute :field_127, :integer
attribute :field_128, :string
attribute :field_129, :integer
attribute :field_130, :string
attribute :field_131, :integer
attribute :field_132, :string
attribute :field_133, :integer
attribute :field_134, :string
attribute :field_135, :integer
attribute :field_136, :string
attribute :field_137, :integer
attribute :field_138, :string
attribute :field_139, :integer
attribute :field_140, :string
validates :field_1,
presence: {
@ -496,6 +504,8 @@ class BulkUpload::Sales::Year2026::RowParser
return true if blank_row?
normalise_case_insensitive_fields
super(:before_log)
@before_errors = errors.dup
@ -554,8 +564,7 @@ class BulkUpload::Sales::Year2026::RowParser
"field_21", # postcode
"field_22", # postcode
"field_28", # age1
"field_29", # sex1
"field_122", # sexrab1
"field_29", # sexrab1
"field_32", # ecstat1
)
end
@ -568,6 +577,13 @@ class BulkUpload::Sales::Year2026::RowParser
private
def normalise_case_insensitive_fields
CASE_INSENSITIVE_FIELDS.each do |field|
value = send(field)
send("#{field}=", value.upcase) if value.present?
end
end
def prevtenbuy2
case field_64
when "R"
@ -704,13 +720,6 @@ private
age5: %i[field_51],
age6_known: %i[field_55],
age6: %i[field_55],
sex1: %i[field_29],
sex2: %i[field_36],
sex3: %i[field_44],
sex4: %i[field_48],
sex5: %i[field_52],
sex6: %i[field_56],
relat2: %i[field_34],
relat3: %i[field_42],
relat4: %i[field_46],
@ -823,26 +832,26 @@ private
lasttransaction: %i[field_104 field_105 field_106],
initialpurchase: %i[field_100 field_101 field_102],
sexrab1: %i[field_122],
sexrab2: %i[field_123],
sexrab3: %i[field_124],
sexrab4: %i[field_125],
sexrab5: %i[field_126],
sexrab6: %i[field_127],
buildheightclass: %i[field_128],
gender_same_as_sex1: %i[field_129],
gender_description1: %i[field_130],
gender_same_as_sex2: %i[field_131],
gender_description2: %i[field_132],
gender_same_as_sex3: %i[field_133],
gender_description3: %i[field_134],
gender_same_as_sex4: %i[field_135],
gender_description4: %i[field_136],
gender_same_as_sex5: %i[field_137],
gender_description5: %i[field_138],
gender_same_as_sex6: %i[field_139],
gender_description6: %i[field_140],
sexrab1: %i[field_29],
sexrab2: %i[field_36],
sexrab3: %i[field_44],
sexrab4: %i[field_48],
sexrab5: %i[field_52],
sexrab6: %i[field_56],
buildheightclass: %i[field_122],
gender_same_as_sex1: %i[field_123],
gender_description1: %i[field_124],
gender_same_as_sex2: %i[field_125],
gender_description2: %i[field_126],
gender_same_as_sex3: %i[field_127],
gender_description3: %i[field_128],
gender_same_as_sex4: %i[field_129],
gender_description4: %i[field_130],
gender_same_as_sex5: %i[field_131],
gender_description5: %i[field_132],
gender_same_as_sex6: %i[field_133],
gender_description6: %i[field_134],
}
end
@ -871,33 +880,26 @@ private
attributes["age6_known"] = age6_known?
attributes["age6"] = field_55 if attributes["age6_known"]&.zero? && field_55&.match(/\A\d{1,3}\z|\AR\z/)
attributes["sex1"] = field_29
attributes["sex2"] = field_36
attributes["sex3"] = field_44
attributes["sex4"] = field_48
attributes["sex5"] = field_52
attributes["sex6"] = field_56
attributes["sexrab1"] = field_122
attributes["sexrab2"] = field_123
attributes["sexrab3"] = field_124
attributes["sexrab4"] = field_125
attributes["sexrab5"] = field_126
attributes["sexrab6"] = field_127
attributes["buildheightclass"] = field_128
attributes["gender_same_as_sex1"] = field_129
attributes["gender_description1"] = field_130
attributes["gender_same_as_sex2"] = field_131
attributes["gender_description2"] = field_132
attributes["gender_same_as_sex3"] = field_133
attributes["gender_description3"] = field_134
attributes["gender_same_as_sex4"] = field_135
attributes["gender_description4"] = field_136
attributes["gender_same_as_sex5"] = field_137
attributes["gender_description5"] = field_138
attributes["gender_same_as_sex6"] = field_139
attributes["gender_description6"] = field_140
attributes["sexrab1"] = field_29
attributes["sexrab2"] = field_36
attributes["sexrab3"] = field_44
attributes["sexrab4"] = field_48
attributes["sexrab5"] = field_52
attributes["sexrab6"] = field_56
attributes["buildheightclass"] = field_122
attributes["gender_same_as_sex1"] = field_123
attributes["gender_description1"] = field_124
attributes["gender_same_as_sex2"] = field_125
attributes["gender_description2"] = field_126
attributes["gender_same_as_sex3"] = field_127
attributes["gender_description3"] = field_128
attributes["gender_same_as_sex4"] = field_129
attributes["gender_description4"] = field_130
attributes["gender_same_as_sex5"] = field_131
attributes["gender_description5"] = field_132
attributes["gender_same_as_sex6"] = field_133
attributes["gender_description6"] = field_134
attributes["relat2"] = relationship_from_is_partner(field_34)
attributes["relat3"] = relationship_from_is_partner(field_42)
@ -1098,23 +1100,23 @@ private
end
def person_2_present?
field_35.present? || field_36.present? || field_34.present? || field_123.present?
field_35.present? || field_36.present? || field_34.present?
end
def person_3_present?
field_43.present? || field_44.present? || field_42.present? || field_124.present?
field_43.present? || field_44.present? || field_42.present?
end
def person_4_present?
field_47.present? || field_48.present? || field_46.present? || field_125.present?
field_47.present? || field_48.present? || field_46.present?
end
def person_5_present?
field_51.present? || field_52.present? || field_50.present? || field_126.present?
field_51.present? || field_52.present? || field_50.present?
end
def person_6_present?
field_55.present? || field_56.present? || field_54.present? || field_127.present?
field_55.present? || field_56.present? || field_54.present?
end
def relationship_from_is_partner(is_partner)
@ -1337,7 +1339,6 @@ private
saledate
age1
sexrab1
sex1
ecstat1
owning_organisation
postcode_full
@ -1514,8 +1515,7 @@ private
errors.add(:field_21, error_message) # Postcode
errors.add(:field_22, error_message) # Postcode
errors.add(:field_28, error_message) # Buyer 1 age
errors.add(:field_29, error_message) # Buyer 1 gender
errors.add(:field_122, error_message) # Buyer 1 sex registered at birth
errors.add(:field_29, error_message) # Buyer 1 sex registered at birth
errors.add(:field_32, error_message) # Buyer 1 working situation
errors.add(:field_7, error_message) # Purchaser code
end

13
app/services/exports/sales_log_export_constants.rb

@ -139,13 +139,24 @@ module Exports::SalesLogExportConstants
(1..6).each do |index|
ALL_YEAR_EXPORT_FIELDS << "AGE#{index}"
ALL_YEAR_EXPORT_FIELDS << "ECSTAT#{index}"
ALL_YEAR_EXPORT_FIELDS << "SEX#{index}"
end
(2..6).each do |index|
ALL_YEAR_EXPORT_FIELDS << "RELAT#{index}"
end
YEAR_2024_EXPORT_FIELDS = Set[]
(1..6).each do |index|
YEAR_2024_EXPORT_FIELDS << "SEX#{index}"
end
YEAR_2025_EXPORT_FIELDS = Set[]
(1..6).each do |index|
YEAR_2025_EXPORT_FIELDS << "SEX#{index}"
end
YEAR_2026_EXPORT_FIELDS = Set["BUILDHEIGHTCLASS"]
(1..6).each do |index|

4
app/services/exports/sales_log_export_service.rb

@ -159,6 +159,10 @@ module Exports
included_fields.merge(ALL_YEAR_EXPORT_FIELDS)
year_fields = case sales_log.collection_start_year
when 2024
YEAR_2024_EXPORT_FIELDS
when 2025
YEAR_2025_EXPORT_FIELDS
when 2026
YEAR_2026_EXPORT_FIELDS
else

2
app/views/form/page.html.erb

@ -76,6 +76,8 @@
<% end %>
<% if @pages_with_errors_count > 1 %>
<%# Hidden form submit ensures pressing Enter triggers "Save and continue" as the user would expect rather than "See all related answers"%>
<%= f.submit submit_button_text(@page, request.query_parameters["referrer"]), class: "govuk-visually-hidden", tabindex: -1, "aria-hidden": true %>
<div class="govuk-button-group">
<%= f.submit "See all related answers", name: "check_errors", class: "govuk-body govuk-link submit-button-link" %>
</div>

56
config/locales/forms/2026/lettings/household_characteristics.en.yml

@ -44,13 +44,6 @@ en:
hint_text: ""
question_text: "Enter gender identity"
sex1:
page_header: ""
check_answer_label: "Lead tenant’s gender identity"
check_answer_prompt: ""
hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth."
question_text: "Which of these best describes the lead tenant’s gender identity?"
ethnic_group:
page_header: ""
check_answer_label: "Lead tenant’s ethnic group"
@ -158,13 +151,6 @@ en:
hint_text: ""
question_text: "Enter gender identity"
sex2:
page_header: ""
check_answer_label: "Person 2’s gender identity"
check_answer_prompt: ""
hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth."
question_text: "Which of these best describes person 2’s gender identity?"
ecstat2:
page_header: ""
check_answer_label: "Person 2’s working situation"
@ -220,13 +206,6 @@ en:
hint_text: ""
question_text: "Enter gender identity"
sex3:
page_header: ""
check_answer_label: "Person 3’s gender identity"
check_answer_prompt: ""
hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth."
question_text: "Which of these best describes person 3’s gender identity?"
ecstat3:
page_header: ""
check_answer_label: "Person 3’s working situation"
@ -282,13 +261,6 @@ en:
hint_text: ""
question_text: "Enter gender identity"
sex4:
page_header: ""
check_answer_label: "Person 4’s gender identity"
check_answer_prompt: ""
hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth."
question_text: "Which of these best describes person 4’s gender identity?"
ecstat4:
page_header: ""
check_answer_label: "Person 4’s working situation"
@ -344,13 +316,6 @@ en:
hint_text: ""
question_text: "Enter gender identity"
sex5:
page_header: ""
check_answer_label: "Person 5’s gender identity"
check_answer_prompt: ""
hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth."
question_text: "Which of these best describes person 5’s gender identity?"
ecstat5:
page_header: ""
check_answer_label: "Person 5’s working situation"
@ -406,13 +371,6 @@ en:
hint_text: ""
question_text: "Enter gender identity"
sex6:
page_header: ""
check_answer_label: "Person 6’s gender identity"
check_answer_prompt: ""
hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth."
question_text: "Which of these best describes person 6’s gender identity?"
ecstat6:
page_header: ""
check_answer_label: "Person 6’s working situation"
@ -468,13 +426,6 @@ en:
hint_text: ""
question_text: "Enter gender identity"
sex7:
page_header: ""
check_answer_label: "Person 7’s gender identity"
check_answer_prompt: ""
hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth."
question_text: "Which of these best describes person 7’s gender identity?"
ecstat7:
page_header: ""
check_answer_label: "Person 7’s working situation"
@ -530,13 +481,6 @@ en:
hint_text: ""
question_text: "Enter gender identity"
sex8:
page_header: ""
check_answer_label: "Person 8’s gender identity"
check_answer_prompt: ""
hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth."
question_text: "Which of these best describes person 8’s gender identity?"
ecstat8:
page_header: ""
check_answer_label: "Person 8’s working situation"

25
db/migrate/20260225135309_add_composite_indexes_for_logs_organisation_lookup.rb

@ -0,0 +1,25 @@
class AddCompositeIndexesForLogsOrganisationLookup < ActiveRecord::Migration[7.2]
disable_ddl_transaction!
def change
add_index :lettings_logs, %i[owning_organisation_id id],
order: { id: :desc },
name: "index_lettings_logs_on_owning_org_and_id_desc",
algorithm: :concurrently
add_index :lettings_logs, %i[managing_organisation_id id],
order: { id: :desc },
name: "index_lettings_logs_on_managing_org_and_id_desc",
algorithm: :concurrently
add_index :sales_logs, %i[owning_organisation_id id],
order: { id: :desc },
name: "index_sales_logs_on_owning_org_and_id_desc",
algorithm: :concurrently
add_index :sales_logs, %i[managing_organisation_id id],
order: { id: :desc },
name: "index_sales_logs_on_managing_org_and_id_desc",
algorithm: :concurrently
end
end

6
db/schema.rb

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.2].define(version: 2026_02_20_141000) do
ActiveRecord::Schema[7.2].define(version: 2026_02_25_162121) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -408,8 +408,10 @@ ActiveRecord::Schema[7.2].define(version: 2026_02_20_141000) do
t.index ["bulk_upload_id"], name: "index_lettings_logs_on_bulk_upload_id"
t.index ["created_by_id"], name: "index_lettings_logs_on_created_by_id"
t.index ["location_id"], name: "index_lettings_logs_on_location_id"
t.index ["managing_organisation_id", "id"], name: "index_lettings_logs_on_managing_org_and_id_desc", order: { id: :desc }
t.index ["managing_organisation_id"], name: "index_lettings_logs_on_managing_organisation_id"
t.index ["old_id"], name: "index_lettings_logs_on_old_id", unique: true
t.index ["owning_organisation_id", "id"], name: "index_lettings_logs_on_owning_org_and_id_desc", order: { id: :desc }
t.index ["owning_organisation_id"], name: "index_lettings_logs_on_owning_organisation_id"
t.index ["scheme_id"], name: "index_lettings_logs_on_scheme_id"
t.index ["updated_by_id"], name: "index_lettings_logs_on_updated_by_id"
@ -838,8 +840,10 @@ ActiveRecord::Schema[7.2].define(version: 2026_02_20_141000) do
t.index ["assigned_to_id"], name: "index_sales_logs_on_assigned_to_id"
t.index ["bulk_upload_id"], name: "index_sales_logs_on_bulk_upload_id"
t.index ["created_by_id"], name: "index_sales_logs_on_created_by_id"
t.index ["managing_organisation_id", "id"], name: "index_sales_logs_on_managing_org_and_id_desc", order: { id: :desc }
t.index ["managing_organisation_id"], name: "index_sales_logs_on_managing_organisation_id"
t.index ["old_id"], name: "index_sales_logs_on_old_id", unique: true
t.index ["owning_organisation_id", "id"], name: "index_sales_logs_on_owning_org_and_id_desc", order: { id: :desc }
t.index ["owning_organisation_id"], name: "index_sales_logs_on_owning_organisation_id"
t.index ["updated_by_id"], name: "index_sales_logs_on_updated_by_id"
end

6
spec/factories/sales_log.rb

@ -308,7 +308,7 @@ FactoryBot.define do
builtype { 1 }
ethnic { 3 }
ethnic_group { 17 }
sexrab2 { "X" }
sexrab2 { "R" }
sex2 { "X" }
gender_same_as_sex1 { 1 }
gender_same_as_sex2 { 2 }
@ -346,11 +346,11 @@ FactoryBot.define do
prevshared { 2 }
sexrab3 { "F" }
sex3 { "F" }
sexrab4 { "X" }
sexrab4 { "R" }
sex4 { "X" }
sexrab5 { "M" }
sex5 { "M" }
sexrab6 { "X" }
sexrab6 { "R" }
sex6 { "X" }
mortgage { 20_000 }
ecstat3 { 9 }

18
spec/fixtures/exports/sales_log_26_27.xml vendored

@ -8,13 +8,13 @@
<JOINTMORE>1</JOINTMORE>
<BEDS>2</BEDS>
<AGE1>27</AGE1>
<SEX1>F</SEX1>
<SEXRAB1>F</SEXRAB1>
<ETHNIC>17</ETHNIC>
<BUILTYPE>1</BUILTYPE>
<PROPTYPE>1</PROPTYPE>
<AGE2>33</AGE2>
<RELAT2>P</RELAT2>
<SEX2>X</SEX2>
<SEXRAB2>R</SEXRAB2>
<NOINT>2</NOINT>
<ECSTAT2>1</ECSTAT2>
<PRIVACYNOTICE>1</PRIVACYNOTICE>
@ -32,7 +32,7 @@
<SAVINGSNK>1</SAVINGSNK>
<SAVINGS/>
<PREVOWN>1</PREVOWN>
<SEX3>F</SEX3>
<SEXRAB3>F</SEXRAB3>
<MORTGAGE>20000.0</MORTGAGE>
<INC2MORT>1</INC2MORT>
<ECSTAT3>9</ECSTAT3>
@ -44,9 +44,9 @@
<RELAT5>R</RELAT5>
<RELAT6>R</RELAT6>
<HB>4</HB>
<SEX4>X</SEX4>
<SEX5>M</SEX5>
<SEX6>X</SEX6>
<SEXRAB4>R</SEXRAB4>
<SEXRAB5>M</SEXRAB5>
<SEXRAB6>R</SEXRAB6>
<FROMBEDS/>
<STAIRCASE/>
<STAIRBOUGHT/>
@ -88,12 +88,6 @@
<FIRSTSTAIR/>
<NUMSTAIR/>
<MRENTPRESTAIRCASING/>
<SEXRAB1>F</SEXRAB1>
<SEXRAB2/>
<SEXRAB3>F</SEXRAB3>
<SEXRAB4/>
<SEXRAB5>M</SEXRAB5>
<SEXRAB6/>
<BUILDHEIGHTCLASS>2</BUILDHEIGHTCLASS>
<GENDER_SAME_AS_SEX1>1</GENDER_SAME_AS_SEX1>
<GENDER_DESCRIPTION1/>

10
spec/fixtures/files/2026_27_sales_bulk_upload.csv vendored

@ -1,4 +1,4 @@
Section,Setting up this sales log,,,,,,,,,,,,,,,Property information,,,,,,,,,,,,Household characteristics,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Household situation,,,,,,,Other household information,,,,,"Income, benefits and outgoings",,,,,,,,Shared ownership - initial purchase,,,,,,,,,,,,,,,,,,Shared ownership - staircasing transaction,,,,,,,,,,,,,,,,Discounted ownership,,,,,,,,,,,,,,,,,Household characteristics,Household characteristics,Household characteristics,Household characteristics,Household characteristics,Household characteristics,Household characteristics,Household characteristics,Household characteristics,Household characteristics,Household characteristics,Household characteristics
Section,Setting up this sales log,,,,,,,,,,,,,,,Property information,,,,,,,,,,,,Household characteristics,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Household situation,,,,,,,Other household information,,,,,"Income, benefits and outgoings",,,,,,,,Shared ownership - initial purchase,,,,,,,,,,,,,,,,,,Shared ownership - staircasing transaction,,,,,,,,,,,,,,,,Discounted ownership,,,,,,,,,,,Household characteristics,Household characteristics,Household characteristics,Household characteristics,Household characteristics,Household characteristics,Household characteristics,Household characteristics,Household characteristics,Household characteristics,Household characteristics,Household characteristics
Question,What is the sale completion date? - day DD,What is the sale completion date? - month MM,What is the sale completion date? - year YY,Which organisation owned this property before the sale?,Which organisation is reporting this sale?,What is the CORE username of the account this sale log should be assigned to? ,What is the purchaser code?,Is this a shared ownership or discounted ownership sale?,What is the type of shared ownership sale?,Is this a staircasing transaction?,What is the type of discounted ownership sale?,Is this a joint purchase?,Are there more than 2 joint buyers of this property?,Did you interview the buyer to answer these questions?,Has the buyer seen or been given access to the MHCLG privacy notice?,"If known, enter this property’s UPRN",Address Line 1,Address Line 2,Town or city,County,Part 1 of the property's postcode,Part 2 of the property's postcode,What is the property's local authority?,What type of unit is the property?,How many bedrooms does the property have?,Which type of building is the property?,Is the property built or adapted to wheelchair-user standards?,What is buyer 1’s age?,Which of these best describes buyer 1's gender identity?,Which of the following best describes buyer 1's ethnic background?,What is buyer 1's nationality?,Which of these best describes buyer 1’s working situation? ,Will buyer 1 live in the property?,Is buyer 2 the partner of buyer 1?,What is buyer 2 or person 2's age?,Which of these best describes buyer 2 or person 2's gender identity?,Which of the following best describes buyer 2's ethnic background?,What is buyer 2's nationality?,Which of these best describes buyer 2 or person 2’s working situation? ,Will buyer 2 live in the property?,"Besides the buyer(s), how many other people live or will live in the property?",Is person 3 the partner of buyer 1?,What is person 3's age?,Which of these best describes person 3's gender identity?,Which of these best describes person 3’s working situation? ,Is person 4 the partner of buyer 1?,What is person 4's age?,Which of these best describes person 4's gender identity?,Which of these best describes person 4’s working situation? ,Is person 5 the partner of buyer 1?,What is person 5's age?,Which of these best describes person 5's gender identity?,Which of these best describes person 5’s working situation? ,Is person 6 the partner of buyer 1?,What is person 6's age?,Which of these best describes person 6's gender identity?,Which of these best describes person 6’s working situation? ,What was buyer 1's previous tenure?,Do you know the postcode of buyer 1's last settled accommodation?,Part 1 of postcode of buyer 1's last settled accommodation,Part 2 of postcode of buyer 1's last settled accommodation,What is the local authority of buyer 1's last settled accommodation?,"At the time of purchase, was buyer 2 living at the same address as buyer 1?",What was buyer 2's previous tenure?,"Have any of the buyers ever served as a regular in the UK armed forces?
",Is the buyer still serving in the UK armed forces?,Are any of the buyers a spouse or civil partner of a UK armed forces regular who died in service within the last 2 years?,Does anyone in the household consider themselves to have a disability?,Does anyone in the household use a wheelchair?,What is buyer 1's annual income?,Was buyer 1's income used for a mortgage application?,What is buyer 2's annual income?,Was buyer 2's income used for a mortgage application?,Were the buyers receiving any of these housing-related benefits immediately before buying this property?,What is the total amount the buyers had in savings before they paid any deposit for the property?,Have any of the buyers previously owned a property?,Was the previous property under shared ownership?,Is this a resale?,How long did the buyer(s) live in the property before purchasing it?,What is the day of the practical completion or handover date? - DD,What is the month of the practical completion or handover date? - MM,What is the year of the practical completion or handover date? - YY,How many bedrooms did the buyer's previous property have?,What was the previous property type?,What was the buyer’s previous tenure?,What is the full purchase price?,What was the initial percentage share purchased?,Was a mortgage used to buy this property?,What is the mortgage amount?,What is the length of the mortgage in years?,How much was the cash deposit paid on the property?,How much cash discount was given through Social HomeBuy?,What is the basic monthly rent?,What are the total monthly service charges for the property?,What are the total monthly estate management fees for the property?,What percentage of the property has been bought in this staircasing transaction?,What percentage of the property do the buyers now own in total?,Was this transaction part of a back-to-back staircasing transaction to facilitate sale of the home on the open market?,Is this the first time the buyer has engaged in staircasing in the home?,What was the day of the initial purchase of a share in the property? DD,What was the month of the initial purchase of a share in the property? MM,What was the year of the initial purchase of a share in the property? YYYY,"Including this time, how many times has the shared owner engaged in staircasing in the home?",What was the day of the last staircasing transaction? DD,What was the month of the last staircasing transaction? MM,What was the year of the last staircasing transaction? YYYY,What is the full purchase price for this staircasing transaction?,What was the percentage share purchased in the initial transaction?,Was a mortgage used for this staircasing transaction?,What was the basic monthly rent prior to staircasing?,What is the basic monthly rent after staircasing?,How long did the buyer(s) live in the property before purchasing it?,What is the full purchase price?,"What was the amount of any loan, grant, discount or subsidy given?",What was the percentage discount?,Was a mortgage used to buy this property?,What is the mortgage amount?,What is the length of the mortgage in years?,Does this include any extra borrowing?,How much was the cash deposit paid on the property?,What are the total monthly leasehold charges for the property?,What was buyer 1's sex at birth?,What was buyer/person 2's sex registered at birth?,What was person 3's sex registered at birth?,What was person 4's sex registered at birth?,What was person 5's sex registered at birth?,What was person 6's sex registered at birth?,What is the building height classification?,Is the gender buyer 1 identifies with the same as their sex registered at birth?,"If ""No"", enter buyer 1's gender identity",Is the gender buyer/person 2 identifies with the same as their sex registered at birth?,"If ""No"", enter buyer/person 2's gender identity",Is the gender person 3 identifies with the same as their sex registered at birth?,"If ""No"", enter person 3's gender identity",Is the gender person 4 identifies with the same as their sex registered at birth?,"If ""No"", enter person 4's gender identity",Is the gender person 5 identifies with the same as their sex registered at birth?,"If ""No"", enter person 5's gender identity",Is the gender person 6 identifies with the same as their sex registered at birth?,"If ""No"", enter person 6's gender identity"
Additional info,,,,"You can find the org ID on the CORE service under 'Stock owners' or, if your organisation is the stock owner, under 'About your organisation'","You can find the org ID on the CORE service under 'Managing agents' or, if your organisation is the managing agent, under 'About your organisation'","If left empty, the sales log will be assigned to the account used to upload the log.",This is how you usually refer to this buyer on your own systems,"Sales logs are no longer required for outright and other sales.
@ -17,7 +17,7 @@ Can be empty?,No,,,No,No,Yes,Yes,No,"Yes, if the purchase was not made through a
Yes, if sale is discounted ownership (if field 8 = 2), or if the purchase is a staircasing transaction (if field 10 = 1)",,Yes,"Yes, if sale is not a joint purchase (if field 12 = 2), or if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if sale is not a joint purchase (if field 12 = 2), or if buyer is not known to have been living at the same address as buyer 1 at the time of purchase (if field 63 = 1 or 3), or if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if none of the buyers are known to have served as a regular in the UK armed forces (if field 65 = 7, 3 or 8), or if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if the purchase is a staircasing transaction (if field 10 = 1)",,,"Yes, if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if buyer 1's income is not known (if field 70 = R), or if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if sale is not a joint purchase (if field 12 = 2), or if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if sale is not a joint purchase (if field 12 = 2) or if buyer 2's income is not known (if field 70 = R), or if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if the purchase is a staircasing transaction (if field 10 = 1)",,,"Yes, if the purchasers did not previously own a property or if it is not known (if field 76 = 2 or 3), or if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if the purchase was not made through a shared ownership scheme (if field 8 = 2) or if this is a staircasing transaction (if field 10 = 1)","Yes, if the purchase was not made through a shared ownership scheme (if field 8 = 2), if this is a resale (if field 78 = 1) or if this is a staircasing transaction (if field 10 = 1)",,,,"Yes, if the purchase was not made through a shared ownership scheme (if field 8 = 2), or if the purchase is a staircasing transaction (if field 10 = 1)
Yes, if the buyer was not a private registered provider, housing association or local authority tenant immediately before sale (if field 58 is not 1 or 2)",,,"Yes, if the purchase was not made through a shared ownership scheme (if field 8 = 2), or if the purchase is a staircasing transaction (if field 10 = 1)",,,"Yes, if the purchase was not made through a shared ownership scheme (if field 8 = 2), if the purchase is a staircasing transaction (if field 10 = 1) or if a mortgage was not used (if field 88 = 2)",,"Yes, if the purchase was not made through a shared ownership scheme (if field 8 = 2), or if the purchase is a staircasing transaction (if field 10 = 1)","Yes, if the purchase was not made through a shared ownership scheme (if field 8 = 2), if the purchase is a staircasing transaction (if field 10 = 1), or if the type of shared ownership sale is not Social Homebuy (if field 9 is not 18)","Yes, if there are no monthy charges or fees, if the purchase was not made through a shared ownership scheme (if field 8 = 2), or if the purchase is a staircasing transaction (if field 10 = 1)",,,"Yes, if this is not a shared ownership transaction (if field 8 = 2), or if it is not a staircasing transaction (if field 10 = 2)",,"Yes, if this is not a shared ownership transaction (if field 8 = 2), or if it is not a staircasing transaction (if field 10 = 2), or if the buyers do not own 100% of the property (if field 97 is less than 100)","Yes, if this is not a shared ownership transaction (if field 8 = 2), or if it is not a staircasing transaction (if field 10 = 2)",,,,"Yes, if this is not a shared ownership transaction (if field 8 = 2), or if it is not a staircasing transaction (if field 10 = 2), and if this is the first time the buyer has engaged in staircasing in the home (if field 99 = 1)",,,,"Yes, if this is not a shared ownership transaction (if field 8 = 2), or if it is not a staircasing transaction (if field 10 = 2)",,,,"Yes, if this is not a shared ownership transaction (if field 8 = 2), or if it is not a staircasing transaction (if field 10 = 2), or if the buyers now own 100% of the property (if field 97 = 100)","Yes, if the purchase was not made through a discounted ownership scheme (if field 8 = 1)",,"Yes, if the purchase was not made through a discounted ownership scheme (if field 8 = 1) or if the type of discounted sale is PRTB, VRTB, RTB, or Rent to Buy (if field 11 is 9, 14, 27 or 29)","Yes, if the purchase was not made through a discounted ownership scheme (if field 8 = 1) or if the type of discounted sale is not PRTB, VRTB, RTB, or Rent to Buy (if field 11 is 8, 21, 22)","Yes, if the purchase was not made through a discounted ownership scheme (if field 8 = 1)","Yes, if the purchase was not made through a discounted ownership scheme (if field 8 = 1) or if a mortgage was not used (if field 116 = 2)",,,"Yes, if the purchase was not made through a discounted ownership scheme (if field 8 = 1)","Yes, if there are no leasehold charges or if the purchase was not made through a discounted ownership scheme (if field 8 = 1)",,,,,,,,,,,,,,,,,,,
Types of sales the question applies to,All,,,,,,,,Shared ownership only,Shared ownership only,Discounted ownership only,All,Joint purchase only,All,,All,,,,,,,,,,Shared ownership - initial purchase and discounted ownership,,All,,Shared ownership - initial purchase and discounted ownership,,,,Joint purchases,,,Joint purchases which are shared ownership - initial purchase and discounted ownership,,,,Shared ownership - initial purchase and discounted ownership,,,,,,,,,,,,,,,,,Shared ownership - initial purchase and discounted ownership,Shared ownership - initial purchase,,,Shared ownership - initial purchase and discounted ownership,Joint purchases which are shared ownership - initial purchase and discounted ownership,,Shared ownership - initial purchase and discounted ownership,,,,,Shared ownership - initial purchase and discounted ownership,,Joint purchases which are shared ownership - initial purchase and discounted ownership,,Shared ownership - initial purchase and discounted ownership,,,,Shared ownership - initial purchase only,,,,,,,,,,,,,,,,,,Shared ownership - staircasing transaction only,,,,,,,,,,,,,,,,Discounted ownership only,,,,,,,,,,,,,,,,,,,,,,,,,,,,
Duplicate check field?,Yes,,,,,,,,,,,,,,,,,,,,Yes,,,,,,,Yes,,,,Yes,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
Field number,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140
,4,9,26,ORG1,ORG1,support@example.com,1,2,,,8,2,3,1,1,,a,a,a,a,aa1,1aa,E09000001,1,1,2,3,20,M,20,GBR,10,1,,,,,,,,0,,,,,,,,,,,,,,,,,1,2,,,,,,8,,7,3,3,10000,2,,,4,20000,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,30,400000,10000,25,2,,,,390000,0,F,,,,,,,1,,,,,,,,,,,
Types of sales the question applies to,All,,,,,,,,Shared ownership only,Shared ownership only,Discounted ownership only,All,Joint purchase only,All,,All,,,,,,,,,,Shared ownership - initial purchase and discounted ownership,,All,,Shared ownership - initial purchase and discounted ownership,,,,Joint purchases,,,Joint purchases which are shared ownership - initial purchase and discounted ownership,,,,Shared ownership - initial purchase and discounted ownership,,,,,,,,,,,,,,,,,Shared ownership - initial purchase and discounted ownership,Shared ownership - initial purchase,,,Shared ownership - initial purchase and discounted ownership,Joint purchases which are shared ownership - initial purchase and discounted ownership,,Shared ownership - initial purchase and discounted ownership,,,,,Shared ownership - initial purchase and discounted ownership,,Joint purchases which are shared ownership - initial purchase and discounted ownership,,Shared ownership - initial purchase and discounted ownership,,,,Shared ownership - initial purchase only,,,,,,,,,,,,,,,,,,Shared ownership - staircasing transaction only,,,,,,,,,,,,,,,,Discounted ownership only,,,,,,,,,,,,,,,,,,,,,,
Duplicate check field?,Yes,,,,,,,,,,,,,,,,,,,,Yes,,,,,,,Yes,,,,Yes,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
Field number,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134
,4,9,26,ORG1,ORG1,support@example.com,1,2,,,8,2,3,1,1,,a,a,a,a,aa1,1aa,E09000001,1,1,2,3,20,M,20,GBR,10,1,,,,,,,,0,,,,,,,,,,,,,,,,,1,2,,,,,,8,,7,3,3,10000,2,,,4,20000,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,30,400000,10000,25,2,,,,390000,0,,1,,,,,,,,,,,

Can't render this file because it has a wrong number of fields in line 2.

3
spec/fixtures/files/sales_logs_csv_export_codes_23.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/sales_logs_csv_export_codes_24.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/sales_logs_csv_export_codes_25.csv vendored

File diff suppressed because one or more lines are too long

6
spec/fixtures/files/sales_logs_csv_export_codes_26.csv vendored

File diff suppressed because one or more lines are too long

3
spec/fixtures/files/sales_logs_csv_export_labels_23.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/sales_logs_csv_export_labels_24.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/sales_logs_csv_export_labels_25.csv vendored

File diff suppressed because one or more lines are too long

6
spec/fixtures/files/sales_logs_csv_export_labels_26.csv vendored

File diff suppressed because one or more lines are too long

6
spec/fixtures/files/sales_logs_csv_export_non_support_codes_26.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/sales_logs_csv_export_non_support_labels_24.csv vendored

File diff suppressed because one or more lines are too long

2
spec/fixtures/files/sales_logs_csv_export_non_support_labels_25.csv vendored

File diff suppressed because one or more lines are too long

6
spec/fixtures/files/sales_logs_csv_export_non_support_labels_26.csv vendored

File diff suppressed because one or more lines are too long

7
spec/models/form/sales/subsections/household_characteristics_spec.rb

@ -408,7 +408,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
age_1_old_persons_shared_ownership_value_check
buyer_1_sex_registered_at_birth
buyer_1_gender_same_as_sex
buyer_1_gender_identity
buyer_1_ethnic_group
buyer_1_ethnic_background_black
buyer_1_ethnic_background_asian
@ -430,7 +429,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
age_2_buyer_not_retired_value_check
buyer_2_sex_registered_at_birth
buyer_2_gender_same_as_sex
buyer_2_gender_identity
buyer_2_ethnic_group
buyer_2_ethnic_background_black
buyer_2_ethnic_background_asian
@ -456,7 +454,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
age_2_partner_under_16_value_check
person_2_sex_registered_at_birth
person_2_gender_same_as_sex
person_2_gender_identity
person_2_working_situation
working_situation_2_retirement_value_check
working_situation_2_not_retired_value_check
@ -470,7 +467,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
age_3_partner_under_16_value_check
person_3_sex_registered_at_birth
person_3_gender_same_as_sex
person_3_gender_identity
person_3_working_situation
working_situation_3_retirement_value_check
working_situation_3_not_retired_value_check
@ -484,7 +480,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
age_4_partner_under_16_value_check
person_4_sex_registered_at_birth
person_4_gender_same_as_sex
person_4_gender_identity
person_4_working_situation
working_situation_4_retirement_value_check
working_situation_4_not_retired_value_check
@ -498,7 +493,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
age_5_partner_under_16_value_check
person_5_sex_registered_at_birth
person_5_gender_same_as_sex
person_5_gender_identity
person_5_working_situation
working_situation_5_retirement_value_check
working_situation_5_not_retired_value_check
@ -512,7 +506,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
age_6_partner_under_16_value_check
person_6_sex_registered_at_birth
person_6_gender_same_as_sex
person_6_gender_identity
person_6_working_situation
working_situation_6_retirement_value_check
working_situation_6_not_retired_value_check

14
spec/services/bulk_upload/lettings/year2025/row_parser_spec.rb

@ -541,6 +541,20 @@ RSpec.describe BulkUpload::Lettings::Year2025::RowParser do
end
end
end
context "and case insensitive fields are set to lowercase" do
let(:case_insensitive_fields) { %w[field_43 field_49 field_53 field_57 field_61 field_65 field_69 field_73] }
let(:case_insensitive_integer_fields_with_r_option) { %w[field_42 field_48 field_52 field_56 field_60 field_64 field_68 field_72] }
let(:attributes) do
valid_attributes
.merge(case_insensitive_fields.each_with_object({}) { |field, h| h[field.to_sym] = valid_attributes[field.to_sym]&.downcase })
.merge(case_insensitive_integer_fields_with_r_option.each_with_object({}) { |field, h| h[field.to_sym] = "r" })
end
it "is still valid" do
expect(parser).to be_valid
end
end
end
context "when valid row with valid decimal (integer) field_11" do

14
spec/services/bulk_upload/lettings/year2026/row_parser_spec.rb

@ -434,6 +434,20 @@ RSpec.describe BulkUpload::Lettings::Year2026::RowParser do
end
end
end
context "and case insensitive fields are set to lowercase" do
let(:case_insensitive_fields) { %w[field_42 field_50 field_56 field_62 field_68 field_74 field_80 field_86] }
let(:case_insensitive_integer_fields_with_r_option) { %w[field_41 field_48 field_54 field_60 field_66 field_72 field_78 field_84] }
let(:attributes) do
valid_attributes
.merge(case_insensitive_fields.each_with_object({}) { |field, h| h[field.to_sym] = valid_attributes[field.to_sym]&.downcase })
.merge(case_insensitive_integer_fields_with_r_option.each_with_object({}) { |field, h| h[field.to_sym] = "r" })
end
it "is still valid" do
expect(parser).to be_valid
end
end
end
context "when valid row with valid decimal (integer) field_11" do

14
spec/services/bulk_upload/sales/year2025/row_parser_spec.rb

@ -292,6 +292,20 @@ RSpec.describe BulkUpload::Sales::Year2025::RowParser do
expect(questions.map(&:id).size).to eq(0)
expect(questions.map(&:id)).to eql([])
end
context "and case insensitive fields are set to lowercase" do
let(:case_insensitive_fields) { %w[field_29 field_36 field_44 field_48 field_52 field_56] }
let(:case_insensitive_integer_fields_with_r_option) { %w[field_28 field_35 field_43 field_47 field_51 field_55 field_64 field_75 field_70 field_72] }
let(:attributes) do
valid_attributes
.merge(case_insensitive_fields.each_with_object({}) { |field, h| h[field.to_sym] = valid_attributes[field.to_sym]&.downcase })
.merge(case_insensitive_integer_fields_with_r_option.each_with_object({}) { |field, h| h[field.to_sym] = "r" })
end
it "is still valid" do
expect(parser).to be_valid
end
end
end
describe "#validate_nulls" do

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

@ -112,16 +112,10 @@ RSpec.describe BulkUpload::Sales::Year2026::RowParser do
field_105: "07",
field_106: "2023",
field_110: "900",
field_122: "F",
field_123: "F",
field_124: "M",
field_125: "M",
field_126: "R",
field_127: "R",
field_128: "1",
field_129: "1",
field_131: "2",
field_132: "Non-binary",
field_122: "1",
field_123: "1",
field_125: "2",
field_126: "Non-binary",
}
end
@ -302,6 +296,20 @@ RSpec.describe BulkUpload::Sales::Year2026::RowParser do
expect(questions.map(&:id).size).to eq(0)
expect(questions.map(&:id)).to eql([])
end
context "and case insensitive fields are set to lowercase" do
let(:case_insensitive_fields) { %w[field_29 field_36 field_44 field_48 field_52 field_56] }
let(:case_insensitive_integer_fields_with_r_option) { %w[field_28 field_35 field_43 field_47 field_51 field_55 field_64 field_75 field_70 field_72] }
let(:attributes) do
valid_attributes
.merge(case_insensitive_fields.each_with_object({}) { |field, h| h[field.to_sym] = valid_attributes[field.to_sym]&.downcase })
.merge(case_insensitive_integer_fields_with_r_option.each_with_object({}) { |field, h| h[field.to_sym] = "r" })
end
it "is still valid" do
expect(parser).to be_valid
end
end
end
describe "#validate_nulls" do
@ -310,7 +318,7 @@ RSpec.describe BulkUpload::Sales::Year2026::RowParser do
it "fetches the question's check_answer_label if it exists" do
parser.valid?
expect(parser.errors[:field_29]).to eql([I18n.t("validations.not_answered", question: "buyer 1’s gender identity.")])
expect(parser.errors[:field_29]).to eql([I18n.t("validations.not_answered", question: "buyer 1’s sex registered at birth.")])
end
end
@ -783,7 +791,7 @@ RSpec.describe BulkUpload::Sales::Year2026::RowParser do
:field_21, # Postcode
:field_22, # Postcode
:field_28, # Buyer 1 age
:field_29, # Buyer 1 gender
:field_29, # Buyer 1 sex registered at birth
:field_32, # Buyer 1 working situation
:field_7, # Purchaser code
].each do |field|
@ -813,7 +821,7 @@ RSpec.describe BulkUpload::Sales::Year2026::RowParser do
:field_21, # Postcode
:field_22, # Postcode
:field_28, # Buyer 1 age
:field_29, # Buyer 1 gender
:field_29, # Buyer 1 sex registered at birth
:field_32, # Buyer 1 working situation
:field_7, # Purchaser code
].each do |field|

94
spec/services/csv/sales_log_csv_service_spec.rb

@ -1,6 +1,8 @@
require "rails_helper"
RSpec.describe Csv::SalesLogCsvService do
include CollectionTimeHelper
subject(:task) { Rake::Task["data_import:add_variable_definitions"] }
let(:form_handler_mock) { instance_double(FormHandler) }
@ -205,25 +207,10 @@ RSpec.describe Csv::SalesLogCsvService do
expect(la_label_value).to eq "Westminster"
end
context "when the requested form is 2023" do
let(:now) { Time.zone.local(2024, 1, 1) }
let(:year) { 2023 }
it "exports the CSV with the 2023 ordering and all values correct" do
expected_content = CSV.read("spec/fixtures/files/sales_logs_csv_export_labels_23.csv")
values_to_delete = %w[ID]
values_to_delete.each do |attribute|
index = attribute_line.index(attribute)
content_line[index] = nil
end
expect(csv).to eq expected_content
end
end
context "when the requested form is 2024" do
let(:now) { Time.zone.local(2024, 5, 1) }
context "when the requested form is 2024", metadata: { year: 24 } do
let(:now) { collection_start_date_for_year(2024) }
let(:year) { 2024 }
let(:fixed_time) { Time.zone.local(2024, 5, 1) }
let(:fixed_time) { collection_start_date_for_year(2024) }
before do
log.update!(nationality_all: 36, manual_address_entry_selected: false, uprn: "1", uprn_known: 1)
@ -240,10 +227,10 @@ RSpec.describe Csv::SalesLogCsvService do
end
end
context "when the requested form is 2025" do
let(:now) { Time.zone.local(2025, 5, 1) }
context "when the requested form is 2025", metadata: { year: 25 } do
let(:now) { collection_start_date_for_year(2025) }
let(:year) { 2025 }
let(:fixed_time) { Time.zone.local(2025, 5, 1) }
let(:fixed_time) { collection_start_date_for_year(2025) }
before do
log.update!(nationality_all: 36, manual_address_entry_selected: false, uprn: "1", uprn_known: 1)
@ -260,13 +247,13 @@ RSpec.describe Csv::SalesLogCsvService do
end
end
context "when the requested form is 2026" do
let(:now) { Time.zone.local(2026, 5, 1) }
context "when the requested form is 2026", metadata: { year: 26 } do
let(:now) { collection_start_date_for_year(2026) }
let(:year) { 2026 }
let(:fixed_time) { Time.zone.local(2026, 5, 1) }
let(:fixed_time) { collection_start_date_for_year(2026) }
before do
log.update!(nationality_all: 36, manual_address_entry_selected: false, uprn: "1", uprn_known: 1, buildheightclass: 2)
log.update!(nationality_all: 36, manual_address_entry_selected: false, uprn: "1", uprn_known: 1)
end
it "exports the CSV with the 2026 ordering and all values correct" do
@ -276,7 +263,7 @@ RSpec.describe Csv::SalesLogCsvService do
index = attribute_line.index(attribute)
content_line[index] = nil
end
expect(csv[1..]).to eq expected_content[1..] # Skip the first line as it contains the definitions
expect(csv).to eq expected_content
end
end
@ -333,24 +320,9 @@ RSpec.describe Csv::SalesLogCsvService do
expect(la_label_value).to eq "Westminster"
end
context "when the requested form is 2023" do
let(:now) { Time.zone.local(2024, 1, 1) }
let(:year) { 2023 }
it "exports the CSV with all values correct" do
expected_content = CSV.read("spec/fixtures/files/sales_logs_csv_export_codes_23.csv")
values_to_delete = %w[ID]
values_to_delete.each do |attribute|
index = attribute_line.index(attribute)
content_line[index] = nil
end
expect(csv).to eq expected_content
end
end
context "when the requested form is 2024" do
let(:now) { Time.zone.local(2024, 5, 1) }
let(:fixed_time) { Time.zone.local(2024, 5, 1) }
context "when the requested form is 2024", metadata: { year: 24 } do
let(:now) { collection_start_date_for_year(2024) }
let(:fixed_time) { collection_start_date_for_year(2024) }
let(:year) { 2024 }
before do
@ -368,9 +340,9 @@ RSpec.describe Csv::SalesLogCsvService do
end
end
context "when the requested form is 2025" do
let(:now) { Time.zone.local(2025, 5, 1) }
let(:fixed_time) { Time.zone.local(2025, 5, 1) }
context "when the requested form is 2025", metadata: { year: 25 } do
let(:now) { collection_start_date_for_year(2025) }
let(:fixed_time) { collection_start_date_for_year(2025) }
let(:year) { 2025 }
before do
@ -388,13 +360,13 @@ RSpec.describe Csv::SalesLogCsvService do
end
end
context "when the requested form is 2026" do
let(:now) { Time.zone.local(2026, 5, 1) }
let(:fixed_time) { Time.zone.local(2026, 5, 1) }
context "when the requested form is 2026", metadata: { year: 26 } do
let(:now) { collection_start_date_for_year(2026) }
let(:fixed_time) { collection_start_date_for_year(2026) }
let(:year) { 2026 }
before do
log.update!(manual_address_entry_selected: false, uprn: "1", uprn_known: 1, buildheightclass: 2)
log.update!(manual_address_entry_selected: false, uprn: "1", uprn_known: 1)
end
it "exports the CSV with all values correct" do
@ -404,7 +376,7 @@ RSpec.describe Csv::SalesLogCsvService do
index = attribute_line.index(attribute)
content_line[index] = nil
end
expect(csv[1..]).to eq expected_content[1..] # Skip the first line as it contains the definitions
expect(csv).to eq expected_content
end
end
@ -428,10 +400,10 @@ RSpec.describe Csv::SalesLogCsvService do
expect(attribute_line).not_to include(*%w[address_line1_as_entered address_line2_as_entered town_or_city_as_entered county_as_entered postcode_full_as_entered la_as_entered created_by value_value_check monthly_charges_value_check])
end
context "and the requested form is 2024" do
context "and the requested form is 2024", metadata: { year: 24 } do
let(:year) { 2024 }
let(:now) { Time.zone.local(2024, 5, 1) }
let(:fixed_time) { Time.zone.local(2024, 5, 1) }
let(:now) { collection_start_date_for_year(2024) }
let(:fixed_time) { collection_start_date_for_year(2024) }
before do
log.update!(nationality_all: 36, manual_address_entry_selected: false, uprn: "1", uprn_known: 1)
@ -452,10 +424,10 @@ RSpec.describe Csv::SalesLogCsvService do
end
end
context "and the requested form is 2025" do
context "and the requested form is 2025", metadata: { year: 25 } do
let(:year) { 2025 }
let(:now) { Time.zone.local(2025, 5, 1) }
let(:fixed_time) { Time.zone.local(2025, 5, 1) }
let(:now) { collection_start_date_for_year(2025) }
let(:fixed_time) { collection_start_date_for_year(2025) }
before do
log.update!(nationality_all: 36, manual_address_entry_selected: false, uprn: "1", uprn_known: 1)
@ -476,10 +448,10 @@ RSpec.describe Csv::SalesLogCsvService do
end
end
context "and the requested form is 2026" do
context "and the requested form is 2026", metadata: { year: 26 } do
let(:year) { 2026 }
let(:now) { Time.zone.local(2026, 5, 1) }
let(:fixed_time) { Time.zone.local(2026, 5, 1) }
let(:now) { collection_start_date_for_year(2026) }
let(:fixed_time) { collection_start_date_for_year(2026) }
before do
log.update!(nationality_all: 36, manual_address_entry_selected: false, uprn: "1", uprn_known: 1, buildheightclass: 2)

10
spec/services/exports/sales_log_export_service_spec.rb

@ -333,7 +333,7 @@ RSpec.describe Exports::SalesLogExportService do
end
end
context "when exporting only 24/25 collection period" do
context "when exporting only 24/25 collection period", metadata: { year: 24 } do
let(:start_time) { Time.zone.local(2024, 4, 3) }
before do
@ -365,8 +365,8 @@ RSpec.describe Exports::SalesLogExportService do
end
end
context "when exporting only 25/26 collection period" do
let(:start_time) { Time.zone.local(2025, 4, 1) }
context "when exporting only 25/26 collection period", metadata: { year: 25 } do
let(:start_time) { collection_start_date_for_year(2025) }
before do
Timecop.freeze(start_time)
@ -397,8 +397,8 @@ RSpec.describe Exports::SalesLogExportService do
end
end
context "when exporting only 26/27 collection period" do
let(:start_time) { Time.zone.local(2026, 4, 1) }
context "when exporting only 26/27 collection period", metadata: { year: 26 } do
let(:start_time) { collection_start_date_for_year(2026) }
before do
Timecop.freeze(start_time)

Loading…
Cancel
Save