diff --git a/app/controllers/bulk_upload_sales_logs_controller.rb b/app/controllers/bulk_upload_sales_logs_controller.rb
index de70ffc5d..c1004ced4 100644
--- a/app/controllers/bulk_upload_sales_logs_controller.rb
+++ b/app/controllers/bulk_upload_sales_logs_controller.rb
@@ -35,6 +35,8 @@ private
end
def in_crossover_period?
+ return true if FeatureToggle.force_crossover?
+
FormHandler.instance.sales_in_crossover_period?
end
diff --git a/app/controllers/start_controller.rb b/app/controllers/start_controller.rb
index e07615901..afc6e4a49 100644
--- a/app/controllers/start_controller.rb
+++ b/app/controllers/start_controller.rb
@@ -21,14 +21,6 @@ class StartController < ApplicationController
)
end
- def download_22_23_sales_form
- send_file(
- Rails.root.join("public/files/2022_23_sales_paper_form.pdf"),
- filename: "2022-23 Sales paper form.pdf",
- type: "application/pdf",
- )
- end
-
def download_24_25_lettings_form
send_file(
Rails.root.join("public/files/2024_25_lettings_paper_form.pdf"),
@@ -45,14 +37,6 @@ class StartController < ApplicationController
)
end
- def download_22_23_lettings_form
- send_file(
- Rails.root.join("public/files/2022_23_lettings_paper_form.pdf"),
- filename: "2022-23 Lettings paper form.pdf",
- type: "application/pdf",
- )
- end
-
def download_24_25_lettings_bulk_upload_template
send_file(
Rails.root.join("public/files/bulk-upload-lettings-template-2024-25.xlsx"),
@@ -132,36 +116,4 @@ class StartController < ApplicationController
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
end
-
- def download_22_23_lettings_bulk_upload_template
- send_file(
- Rails.root.join("public/files/bulk-upload-lettings-template-2022-23.xlsx"),
- filename: "2022-23-lettings-bulk-upload-template.xlsx",
- type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
- )
- end
-
- def download_22_23_lettings_bulk_upload_specification
- send_file(
- Rails.root.join("public/files/bulk-upload-lettings-specification-2022-23.xlsx"),
- filename: "2022-23-lettings-bulk-upload-specification.xlsx",
- type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
- )
- end
-
- def download_22_23_sales_bulk_upload_template
- send_file(
- Rails.root.join("public/files/bulk-upload-sales-template-2022-23.xlsx"),
- filename: "2022-23-sales-bulk-upload-template.xlsx",
- type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
- )
- end
-
- def download_22_23_sales_bulk_upload_specification
- send_file(
- Rails.root.join("public/files/bulk-upload-sales-specification-2022-23.xlsx"),
- filename: "2022-23-sales-bulk-upload-specification.xlsx",
- type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
- )
- end
end
diff --git a/app/models/forms/bulk_upload_lettings/prepare_your_file.rb b/app/models/forms/bulk_upload_lettings/prepare_your_file.rb
index 601f09848..042ec132a 100644
--- a/app/models/forms/bulk_upload_lettings/prepare_your_file.rb
+++ b/app/models/forms/bulk_upload_lettings/prepare_your_file.rb
@@ -10,9 +10,7 @@ module Forms
def view_path
case year
- when 2022
- "bulk_upload_lettings_logs/forms/prepare_your_file_2022"
- else
+ when 2023
"bulk_upload_lettings_logs/forms/prepare_your_file_2023"
end
end
@@ -32,27 +30,21 @@ module Forms
def legacy_template_path
case year
- when 2022
- "/files/bulk-upload-lettings-template-2022-23.xlsx"
- else
+ when 2023
"/files/bulk-upload-lettings-legacy-template-2023-24.xlsx"
end
end
def template_path
case year
- when 2022
- "/files/bulk-upload-lettings-template-2022-23.xlsx"
- else
+ when 2023
"/files/bulk-upload-lettings-template-2023-24.xlsx"
end
end
def specification_path
case year
- when 2022
- "/files/bulk-upload-lettings-specification-2022-23.xlsx"
- else
+ when 2023
"/files/bulk-upload-lettings-specification-2023-24.xlsx"
end
end
diff --git a/app/models/forms/bulk_upload_sales/prepare_your_file.rb b/app/models/forms/bulk_upload_sales/prepare_your_file.rb
index 9425a6662..04d52802f 100644
--- a/app/models/forms/bulk_upload_sales/prepare_your_file.rb
+++ b/app/models/forms/bulk_upload_sales/prepare_your_file.rb
@@ -9,9 +9,7 @@ module Forms
def view_path
case year
- when 2022
- "bulk_upload_sales_logs/forms/prepare_your_file_2022"
- else
+ when 2023
"bulk_upload_sales_logs/forms/prepare_your_file_2023"
end
end
@@ -30,17 +28,13 @@ module Forms
def legacy_template_path
case year
- when 2022
- "/files/bulk-upload-sales-template-2022-23.xlsx"
- else
+ when 2023
"/files/bulk-upload-sales-legacy-template-2023-24.xlsx"
end
end
def template_path
case year
- when 2022
- "/files/bulk-upload-sales-template-2022-23.xlsx"
when 2023
"/files/bulk-upload-sales-template-2023-24.xlsx"
end
@@ -48,8 +42,7 @@ module Forms
def specification_path
case year
- when 2022
- "/files/bulk-upload-sales-specification-2022-23.xlsx"
+
when 2023
"/files/bulk-upload-sales-specification-2023-24.xlsx"
end
@@ -66,6 +59,8 @@ module Forms
private
def in_crossover_period?
+ return true if FeatureToggle.force_crossover?
+
FormHandler.instance.sales_in_crossover_period?
end
end
diff --git a/app/services/bulk_upload/lettings/log_creator.rb b/app/services/bulk_upload/lettings/log_creator.rb
index 99cfd4390..dd8820b35 100644
--- a/app/services/bulk_upload/lettings/log_creator.rb
+++ b/app/services/bulk_upload/lettings/log_creator.rb
@@ -31,8 +31,6 @@ private
def csv_parser
@csv_parser ||= case bulk_upload.year
- when 2022
- BulkUpload::Lettings::Year2022::CsvParser.new(path:)
when 2023
BulkUpload::Lettings::Year2023::CsvParser.new(path:)
else
diff --git a/app/services/bulk_upload/lettings/validator.rb b/app/services/bulk_upload/lettings/validator.rb
index 7f811f709..59d837acf 100644
--- a/app/services/bulk_upload/lettings/validator.rb
+++ b/app/services/bulk_upload/lettings/validator.rb
@@ -98,8 +98,6 @@ private
def csv_parser
@csv_parser ||= case bulk_upload.year
- when 2022
- BulkUpload::Lettings::Year2022::CsvParser.new(path:)
when 2023
BulkUpload::Lettings::Year2023::CsvParser.new(path:)
else
diff --git a/app/services/bulk_upload/lettings/year2022/csv_parser.rb b/app/services/bulk_upload/lettings/year2022/csv_parser.rb
deleted file mode 100644
index 9e672ade5..000000000
--- a/app/services/bulk_upload/lettings/year2022/csv_parser.rb
+++ /dev/null
@@ -1,109 +0,0 @@
-require "csv"
-
-class BulkUpload::Lettings::Year2022::CsvParser
- FIELDS = 134
- MAX_COLUMNS = 135
- FORM_YEAR = 2022
-
- attr_reader :path
-
- def initialize(path:)
- @path = path
- end
-
- def row_offset
- if with_headers?
- rows.find_index { |row| row[0].match(/field number/i) } + 1
- else
- 0
- end
- end
-
- def col_offset
- with_headers? ? 1 : 0
- end
-
- def cols
- @cols ||= ("A".."EE").to_a
- end
-
- def row_parsers
- @row_parsers ||= body_rows.map do |row|
- stripped_row = row[col_offset..]
- hash = Hash[field_numbers.zip(stripped_row)]
-
- BulkUpload::Lettings::Year2022::RowParser.new(hash)
- end
- end
-
- def body_rows
- rows[row_offset..]
- end
-
- def rows
- @rows ||= CSV.parse(normalised_string, row_sep:)
- end
-
- def column_for_field(field)
- cols[field_numbers.find_index(field) + col_offset]
- end
-
- def correct_field_count?
- valid_field_numbers_count = field_numbers.count { |f| f != "field_blank" }
-
- valid_field_numbers_count == FIELDS
- end
-
- def too_many_columns?
- return if with_headers?
-
- max_columns_count = body_rows.map(&:size).max - col_offset
-
- max_columns_count > MAX_COLUMNS
- end
-
- def wrong_template_for_year?
- !(first_record_start_date >= form.start_date && first_record_start_date <= form.new_logs_end_date)
- end
-
-private
-
- def form
- @form ||= FormHandler.instance.lettings_form_for_start_year(FORM_YEAR)
- end
-
- def first_record_start_date
- @first_record_start_date ||= row_parsers.first.startdate || Date.new
- end
-
- def default_field_numbers
- ("field_1".."field_#{FIELDS}").to_a
- end
-
- def field_numbers
- @field_numbers ||= if with_headers?
- rows[row_offset - 1][col_offset..].map { |h| h.present? && h.match?(/^[0-9]+$/) ? "field_#{h}" : "field_blank" }
- else
- default_field_numbers
- end
- end
-
- def with_headers?
- rows.map { |r| r[0] }.any? { |cell| cell&.match?(/field number/i) }
- end
-
- def row_sep
- "\n"
- end
-
- def normalised_string
- return @normalised_string if @normalised_string
-
- @normalised_string = File.read(path, encoding: "bom|utf-8")
- @normalised_string.gsub!("\r\n", "\n")
- @normalised_string.scrub!("")
- @normalised_string.tr!("\r", "\n")
-
- @normalised_string
- end
-end
diff --git a/app/services/bulk_upload/lettings/year2022/row_parser.rb b/app/services/bulk_upload/lettings/year2022/row_parser.rb
deleted file mode 100644
index 8aaa38b43..000000000
--- a/app/services/bulk_upload/lettings/year2022/row_parser.rb
+++ /dev/null
@@ -1,1476 +0,0 @@
-class BulkUpload::Lettings::Year2022::RowParser
- include ActiveModel::Model
- include ActiveModel::Attributes
- include InterruptionScreenHelper
-
- QUESTIONS = {
- field_1: "What is the letting type?",
- field_2: "This question has been removed",
- field_3: "This question has been removed",
- field_4: "Management group code",
- field_5: "Scheme code",
- field_6: "This question has been removed",
- field_7: "What is the tenant code?",
- field_8: "Is this a starter tenancy?",
- field_9: "What is the tenancy type?",
- field_10: "If 'Other', what is the tenancy type?",
- field_11: "What is the length of the fixed-term tenancy to the nearest year?",
- field_12: "Age of person 1",
- field_13: "Age of person 2",
- field_14: "Age of person 3",
- field_15: "Age of person 4",
- field_16: "Age of person 5",
- field_17: "Age of person 6",
- field_18: "Age of person 7",
- field_19: "Age of person 8",
- field_20: "Gender identity of person 1",
- field_21: "Gender identity of person 2",
- field_22: "Gender identity of person 3",
- field_23: "Gender identity of person 4",
- field_24: "Gender identity of person 5",
- field_25: "Gender identity of person 6",
- field_26: "Gender identity of person 7",
- field_27: "Gender identity of person 8",
- field_28: "Relationship to person 1 for person 2",
- field_29: "Relationship to person 1 for person 3",
- field_30: "Relationship to person 1 for person 4",
- field_31: "Relationship to person 1 for person 5",
- field_32: "Relationship to person 1 for person 6",
- field_33: "Relationship to person 1 for person 7",
- field_34: "Relationship to person 1 for person 8",
- field_35: "Working situation of person 1",
- field_36: "Working situation of person 2",
- field_37: "Working situation of person 3",
- field_38: "Working situation of person 4",
- field_39: "Working situation of person 5",
- field_40: "Working situation of person 6",
- field_41: "Working situation of person 7",
- field_42: "Working situation of person 8",
- field_43: "What is the lead tenant's ethnic group?",
- field_44: "What is the lead tenant's nationality?",
- field_45: "Does anybody in the household have links to the UK armed forces?",
- field_46: "Was the person seriously injured or ill as a result of serving in the UK armed forces?",
- field_47: "Is anybody in the household pregnant?",
- field_48: "Is the tenant likely to be receiving benefits related to housing?",
- field_49: "How much of the household's income is from Universal Credit, state pensions or benefits?",
- field_50: "How much income does the household have in total?",
- field_51: "Do you know the household's income?",
- field_52: "What is the tenant's main reason for the household leaving their last settled home?",
- field_53: "If 'Other', what was the main reason for leaving their last settled home?",
- field_54: "This question has been removed",
- field_55: "Does anybody in the household have any disabled access needs?",
- field_56: "Does anybody in the household have any disabled access needs?",
- field_57: "Does anybody in the household have any disabled access needs?",
- field_58: "Does anybody in the household have any disabled access needs?",
- field_59: "Does anybody in the household have any disabled access needs?",
- field_60: "Does anybody in the household have any disabled access needs?",
- field_61: "Where was the household immediately before this letting?",
- field_62: "What is the local authority of the household's last settled home?",
- field_63: "Part 1 of postcode of last settled home",
- field_64: "Part 2 of postcode of last settled home",
- field_65: "Do you know the postcode of last settled home?",
- field_66: "How long has the household continuously lived in the local authority area of the new letting?",
- field_67: "How long has the household been on the waiting list for the new letting?",
- field_68: "Was the tenant homeless directly before this tenancy?",
- field_69: "Was the household given 'reasonable preference' by the local authority?",
- field_70: "Reasonable preference. They were homeless or about to lose their home (within 56 days)",
- field_71: "Reasonable preference. They were living in insanitary, overcrowded or unsatisfactory housing",
- field_72: "Reasonable preference. They needed to move on medical and welfare grounds (including a disability)",
- field_73: "Reasonable preference. They needed to move to avoid hardship to themselves or others",
- field_74: "Reasonable preference. Don't know",
- field_75: "Was the letting made under any of the following allocations systems?",
- field_76: "Was the letting made under any of the following allocations systems?",
- field_77: "Was the letting made under any of the following allocations systems?",
- field_78: "What was the source of referral for this letting?",
- field_79: "How often does the household pay rent and other charges?",
- field_80: "What is the basic rent?",
- field_81: "What is the service charge?",
- field_82: "What is the personal service charge?",
- field_83: "What is the support charge?",
- field_84: "Total Charge",
- field_85: "If this is a care home, how much does the household pay every [time period]?",
- field_86: "Does the household pay rent or other charges for the accommodation?",
- field_87: "After the household has received any housing-related benefits, will they still need to pay basic rent and other charges?",
- field_88: "What do you expect the outstanding amount to be?",
- field_89: "What is the void date?",
- field_90: "What is the void date?",
- field_91: "What is the void date?",
- field_92: "What date were major repairs completed on?",
- field_93: "What date were major repairs completed on?",
- field_94: "What date were major repairs completed on?",
- field_95: "This question has been removed",
- field_96: "What date did the tenancy start?",
- field_97: "What date did the tenancy start?",
- field_98: "What date did the tenancy start?",
- field_99: "Since becoming available, how many times has the property been previously offered?",
- field_100: "What is the property reference?",
- field_101: "How many bedrooms does the property have?",
- field_102: "What type of unit is the property?",
- field_103: "Which type of building is the property?",
- field_104: "Is the property built or adapted to wheelchair-user standards?",
- field_105: "What type was the property most recently let as?",
- field_106: "What is the reason for the property being vacant?",
- field_107: "What is the local authority of the property?",
- field_108: "Part 1 of postcode of the property",
- field_109: "Part 2 of postcode of the property",
- field_110: "This question has been removed",
- field_111: "Which organisation owns this property?",
- field_112: "Username field",
- field_113: "Which organisation manages this property?",
- field_114: "Is the person still serving in the UK armed forces?",
- field_115: "This question has been removed",
- field_116: "How often does the household receive income?",
- field_117: "Is this letting sheltered accommodation?",
- field_118: "Does anybody in the household have a physical or mental health condition (or other illness) expected to last for 12 months or more?",
- field_119: "Vision, for example blindness or partial sight",
- field_120: "Hearing, for example deafness or partial hearing",
- field_121: "Mobility, for example walking short distances or climbing stairs",
- field_122: "Dexterity, for example lifting and carrying objects, using a keyboard",
- field_123: "Learning or understanding or concentrating",
- field_124: "Memory",
- field_125: "Mental health",
- field_126: "Stamina or breathing or fatigue",
- field_127: "Socially or behaviourally, for example associated with autism spectral disorder (ASD) which include Aspergers' or attention deficit hyperactivity disorder (ADHD)",
- field_128: "Other",
- field_129: "Is this letting a London Affordable Rent letting?",
- field_130: "Which type of Intermediate Rent is this letting?",
- field_131: "Which 'Other' type of Intermediate Rent is this letting?",
- field_132: "Data Protection",
- field_133: "Is this a joint tenancy?",
- field_134: "Is this letting a renewal?",
- }.freeze
-
- attribute :bulk_upload
- attribute :block_log_creation, :boolean, default: -> { false }
-
- attribute :field_blank
-
- attribute :field_1, :integer
- attribute :field_2
- attribute :field_3
- attribute :field_4, :string
- attribute :field_5, :integer
- attribute :field_6
- attribute :field_7, :string
- attribute :field_8, :integer
- attribute :field_9, :integer
- attribute :field_10, :string
- attribute :field_11, :integer
- attribute :field_12, :string
- attribute :field_13, :string
- attribute :field_14, :string
- attribute :field_15, :string
- attribute :field_16, :string
- attribute :field_17, :string
- attribute :field_18, :string
- attribute :field_19, :string
- attribute :field_20, :string
- attribute :field_21, :string
- attribute :field_22, :string
- attribute :field_23, :string
- attribute :field_24, :string
- attribute :field_25, :string
- attribute :field_26, :string
- attribute :field_27, :string
- attribute :field_28, :string
- attribute :field_29, :string
- attribute :field_30, :string
- attribute :field_31, :string
- attribute :field_32, :string
- attribute :field_33, :string
- attribute :field_34, :string
- attribute :field_35, :integer
- attribute :field_36, :integer
- attribute :field_37, :integer
- attribute :field_38, :integer
- attribute :field_39, :integer
- attribute :field_40, :integer
- attribute :field_41, :integer
- attribute :field_42, :integer
- attribute :field_43, :integer
- attribute :field_44, :integer
- attribute :field_45, :integer
- attribute :field_46, :integer
- attribute :field_47, :integer
- attribute :field_48, :integer
- attribute :field_49, :integer
- attribute :field_50, :decimal
- attribute :field_51, :integer
- attribute :field_52, :integer
- attribute :field_53, :string
- attribute :field_54
- attribute :field_55, :integer
- attribute :field_56, :integer
- attribute :field_57, :integer
- attribute :field_58, :integer
- attribute :field_59, :integer
- attribute :field_60, :integer
- attribute :field_61, :integer
- attribute :field_62, :string
- attribute :field_63, :string
- attribute :field_64, :string
- attribute :field_65, :integer
- attribute :field_66, :integer
- attribute :field_67, :integer
- attribute :field_68, :integer
- attribute :field_69, :integer
- attribute :field_70, :integer
- attribute :field_71, :integer
- attribute :field_72, :integer
- attribute :field_73, :integer
- attribute :field_74, :integer
- attribute :field_75, :integer
- attribute :field_76, :integer
- attribute :field_77, :integer
- attribute :field_78, :integer
- attribute :field_79, :integer
- attribute :field_80, :decimal
- attribute :field_81, :decimal
- attribute :field_82, :decimal
- attribute :field_83, :decimal
- attribute :field_84, :decimal
- attribute :field_85, :decimal
- attribute :field_86, :integer
- attribute :field_87, :integer
- attribute :field_88, :decimal
- attribute :field_89, :integer
- attribute :field_90, :integer
- attribute :field_91, :integer
- attribute :field_92, :integer
- attribute :field_93, :integer
- attribute :field_94, :integer
- attribute :field_95
- attribute :field_96, :integer
- attribute :field_97, :integer
- attribute :field_98, :integer
- attribute :field_99, :integer
- attribute :field_100, :string
- attribute :field_101, :integer
- attribute :field_102, :integer
- attribute :field_103, :integer
- attribute :field_104, :integer
- attribute :field_105, :integer
- attribute :field_106, :integer
- attribute :field_107, :string
- attribute :field_108, :string
- attribute :field_109, :string
- attribute :field_110
- attribute :field_111, :string
- attribute :field_112, :string
- attribute :field_113, :string
- attribute :field_114, :integer
- attribute :field_115
- attribute :field_116, :integer
- attribute :field_117, :integer
- attribute :field_118, :integer
- attribute :field_119, :integer
- attribute :field_120, :integer
- attribute :field_121, :integer
- attribute :field_122, :integer
- attribute :field_123, :integer
- attribute :field_124, :integer
- attribute :field_125, :integer
- attribute :field_126, :integer
- attribute :field_127, :integer
- attribute :field_128, :integer
- attribute :field_129, :integer
- attribute :field_130, :integer
- attribute :field_131, :string
- attribute :field_132, :integer
- attribute :field_133, :integer
- attribute :field_134, :integer
-
- validate :validate_valid_radio_option, on: :before_log
-
- validates :field_1,
- presence: {
- message: I18n.t("validations.not_answered", question: "letting type"),
- category: :setup,
- },
- inclusion: {
- in: (1..12).to_a,
- message: I18n.t("validations.invalid_option", question: "letting type"),
- category: :setup,
- unless: -> { field_1.blank? },
- },
- on: :after_log
-
- validates :field_12, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 1 must be a number or the letter R" }, on: :after_log
- validates :field_13, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 2 must be a number or the letter R" }, allow_blank: true, on: :after_log
- validates :field_14, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 3 must be a number or the letter R" }, allow_blank: true, on: :after_log
- validates :field_15, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 4 must be a number or the letter R" }, allow_blank: true, on: :after_log
- validates :field_16, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 5 must be a number or the letter R" }, allow_blank: true, on: :after_log
- validates :field_17, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 6 must be a number or the letter R" }, allow_blank: true, on: :after_log
- validates :field_18, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 7 must be a number or the letter R" }, allow_blank: true, on: :after_log
- validates :field_19, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 8 must be a number or the letter R" }, allow_blank: true, on: :after_log
-
- validates :field_96,
- presence: {
- message: I18n.t("validations.not_answered", question: "tenancy start date (day)"),
- category: :setup,
- }, on: :after_log
-
- validates :field_97,
- presence: {
- message: I18n.t("validations.not_answered", question: "tenancy start date (month)"),
- category: :setup,
- },
- on: :after_log
-
- validates :field_98,
- presence: {
- message: I18n.t("validations.not_answered", question: "tenancy start date (year)"),
- category: :setup,
- },
- format: {
- with: /\A\d{2}\z/,
- message: I18n.t("validations.setup.startdate.year_not_two_digits"),
- unless: -> { field_98.blank? },
- category: :setup,
- },
- on: :after_log
-
- validate :validate_data_types, on: :after_log
- validate :validate_relevant_collection_window, on: :after_log
- validate :validate_la_with_local_housing_referral, on: :after_log
- validate :validate_cannot_be_la_referral_if_general_needs_and_la, on: :after_log
- validate :validate_leaving_reason_for_renewal, on: :after_log
- validate :validate_lettings_type_matches_bulk_upload, on: :after_log
- validate :validate_only_one_housing_needs_type, on: :after_log
- validate :validate_no_disabled_needs_conjunction, on: :after_log
- validate :validate_dont_know_disabled_needs_conjunction, on: :after_log
- validate :validate_no_and_dont_know_disabled_needs_conjunction, on: :after_log
- validate :validate_no_housing_needs_questions_answered, on: :after_log
- validate :validate_reasonable_preference_homeless, on: :after_log
- validate :validate_condition_effects, on: :after_log
- validate :validate_if_log_already_exists, on: :after_log, if: -> { FeatureToggle.bulk_upload_duplicate_log_check_enabled? }
-
- validate :validate_owning_org_data_given, on: :after_log
- validate :validate_owning_org_exists, on: :after_log
- validate :validate_owning_org_owns_stock, on: :after_log
- validate :validate_owning_org_permitted, on: :after_log
-
- validate :validate_managing_org_data_given, on: :after_log
- validate :validate_managing_org_exists, on: :after_log
- validate :validate_managing_org_related, on: :after_log
-
- validate :validate_related_scheme_exists, on: :after_log
- validate :validate_scheme_data_given, on: :after_log
-
- validate :validate_related_location_exists, on: :after_log
- validate :validate_location_data_given, on: :after_log
-
- validate :validate_created_by_exists, on: :after_log
- validate :validate_created_by_related, on: :after_log
- validate :validate_rent_type, on: :after_log
-
- validate :validate_declaration_acceptance, on: :after_log
-
- validate :validate_incomplete_soft_validations, on: :after_log
-
- validate :validate_nulls, on: :after_log
-
- def self.question_for_field(field)
- QUESTIONS[field]
- end
-
- def valid?
- return @valid if @valid
-
- errors.clear
-
- return @valid = true if blank_row?
-
- super(:before_log)
- before_errors = errors.dup
-
- log.valid?
-
- super(:after_log)
- errors.merge!(before_errors)
-
- log.errors.each do |error|
- fields = field_mapping_for_errors[error.attribute] || []
-
- fields.each do |field|
- unless errors.include?(field)
- errors.add(field, error.message)
- end
- end
- end
-
- @valid = errors.blank?
- end
-
- def blank_row?
- attribute_set
- .to_hash
- .reject { |k, _| %w[bulk_upload block_log_creation field_blank].include?(k) }
- .values
- .reject(&:blank?)
- .compact
- .empty?
- end
-
- def log
- @log ||= LettingsLog.new(attributes_for_log)
- end
-
- def block_log_creation!
- self.block_log_creation = true
- end
-
- def block_log_creation?
- block_log_creation
- end
-
- def tenant_code
- field_7
- end
-
- def property_ref
- field_100
- end
-
- def log_already_exists?
- @log_already_exists ||= LettingsLog
- .where(status: %w[not_started in_progress completed])
- .exists?(duplicate_check_fields.index_with { |field| log.public_send(field) })
- end
-
- def spreadsheet_duplicate_hash
- attributes.slice(
- bulk_upload.needstype != 1 ? "field_5" : nil, # location
- "field_12", # age1
- "field_20", # sex1
- "field_35", # ecstat1
- "field_84", # tcharge
- "field_96", # startdate
- "field_97", # startdate
- "field_98", # startdate
- bulk_upload.needstype != 2 ? "field_108" : nil, # postcode
- bulk_upload.needstype != 2 ? "field_109" : nil, # postcode
- "field_111", # owning org
- )
- end
-
- def add_duplicate_found_in_spreadsheet_errors
- spreadsheet_duplicate_hash.each_key do |field|
- errors.add(field, :spreadsheet_dupe, category: :setup)
- end
- end
-
- def startdate
- Date.new(field_98 + 2000, field_97, field_96) if field_98.present? && field_97.present? && field_96.present?
- rescue Date::Error
- Date.new
- end
-
-private
-
- def validate_declaration_acceptance
- unless field_132 == 1
- errors.add(:field_132, I18n.t("validations.declaration.missing"), category: :setup)
- end
- end
-
- def validate_valid_radio_option
- log.attributes.each do |question_id, _v|
- question = log.form.get_question(question_id, log)
-
- next unless question&.type == "radio"
- next if log[question_id].blank? || question.answer_options.key?(log[question_id].to_s) || !question.page.routed_to?(log, nil)
-
- fields = field_mapping_for_errors[question_id.to_sym] || []
-
- fields.each do |field|
- if setup_question?(question)
- errors.add(field, I18n.t("validations.invalid_option", question: QUESTIONS[field]), category: :setup)
- else
- errors.add(field, I18n.t("validations.invalid_option", question: QUESTIONS[field]))
- end
- end
- end
- end
-
- def validate_created_by_exists
- return if field_112.blank?
-
- unless created_by
- errors.add(:field_112, "User with the specified email could not be found")
- end
- end
-
- def validate_created_by_related
- return unless created_by
-
- unless (created_by.organisation == owning_organisation) || (created_by.organisation == managing_organisation)
- block_log_creation!
- errors.add(:field_112, "User must be related to owning organisation or managing organisation")
- end
- end
-
- def created_by
- @created_by ||= User.where("lower(email) = ?", field_112&.downcase).first
- end
-
- def duplicate_check_fields
- [
- "startdate",
- "age1",
- "sex1",
- "ecstat1",
- "owning_organisation",
- "tcharge",
- bulk_upload.needstype != 2 ? "postcode_full" : nil,
- bulk_upload.needstype != 1 ? "location" : nil,
- log.chcharge.present? ? "chcharge" : nil,
- ].compact
- end
-
- def location
- return if scheme.nil?
-
- @location ||= scheme.locations.find_by_id_on_multiple_fields(field_5)
- end
-
- def validate_related_location_exists
- if scheme && field_5.present? && location.nil?
- block_log_creation!
- errors.add(:field_5, "Scheme code must relate to a scheme that is owned by the owning organisation or managing organisation", category: :setup)
- end
- end
-
- def validate_location_data_given
- if bulk_upload.supported_housing? && field_5.blank?
- block_log_creation!
- errors.add(:field_5, I18n.t("validations.not_answered", question: "scheme code"), category: :setup)
- end
- end
-
- def validate_related_scheme_exists
- if field_4.present? && owning_organisation.present? && managing_organisation.present? && scheme.nil?
- block_log_creation!
- errors.add(:field_4, "This management group code does not belong to the owning organisation or managing organisation", category: :setup)
- end
- end
-
- def validate_scheme_data_given
- if bulk_upload.supported_housing? && field_4.blank?
- block_log_creation!
- errors.add(:field_4, I18n.t("validations.not_answered", question: "management group code"), category: :setup)
- end
- end
-
- def validate_managing_org_related
- if owning_organisation && managing_organisation && !owning_organisation.can_be_managed_by?(organisation: managing_organisation)
- block_log_creation!
-
- if errors[:field_113].blank?
- errors.add(:field_113, "This managing organisation does not have a relationship with the owning organisation", category: :setup)
- end
- end
- end
-
- def validate_managing_org_exists
- if managing_organisation.nil?
- block_log_creation!
-
- if errors[:field_113].blank?
- errors.add(:field_113, "The managing organisation code is incorrect", category: :setup)
- end
- end
- end
-
- def validate_managing_org_data_given
- if field_113.blank?
- block_log_creation!
- errors.add(:field_113, I18n.t("validations.not_answered", question: "managing organisation"), category: :setup)
- end
- end
-
- def validate_owning_org_owns_stock
- if owning_organisation && !owning_organisation.holds_own_stock?
- block_log_creation!
-
- if errors[:field_111].blank?
- errors.add(:field_111, "The owning organisation code provided is for an organisation that does not own stock", category: :setup)
- end
- end
- end
-
- def validate_owning_org_exists
- if owning_organisation.nil?
- block_log_creation!
-
- if errors[:field_111].blank?
- errors.add(:field_111, "The owning organisation code is incorrect", category: :setup)
- end
- end
- end
-
- def validate_owning_org_data_given
- if field_111.blank?
- block_log_creation!
-
- if errors[:field_111].blank?
- errors.add(:field_111, I18n.t("validations.not_answered", question: "owning organisation"), category: :setup)
- end
- end
- end
-
- def validate_owning_org_permitted
- if owning_organisation && !bulk_upload.user.organisation.affiliated_stock_owners.include?(owning_organisation)
- block_log_creation!
-
- if errors[:field_111].blank?
- errors.add(:field_111, "You do not have permission to add logs for this owning organisation", category: :setup)
- end
- end
- end
-
- def validate_no_and_dont_know_disabled_needs_conjunction
- if field_59 == 1 && field_60 == 1
- errors.add(:field_59, I18n.t("validations.household.housingneeds.no_and_dont_know_disabled_needs_conjunction"))
- errors.add(:field_60, I18n.t("validations.household.housingneeds.no_and_dont_know_disabled_needs_conjunction"))
- end
- end
-
- def validate_dont_know_disabled_needs_conjunction
- if field_60 == 1 && [field_55, field_56, field_57, field_58].count(1).positive?
- %i[field_60 field_55 field_56 field_57 field_58].each do |field|
- errors.add(field, I18n.t("validations.household.housingneeds.dont_know_disabled_needs_conjunction")) if send(field) == 1
- end
- end
- end
-
- def validate_no_disabled_needs_conjunction
- if field_59 == 1 && [field_55, field_56, field_57, field_58].count(1).positive?
- %i[field_59 field_55 field_56 field_57 field_58].each do |field|
- errors.add(field, I18n.t("validations.household.housingneeds.no_disabled_needs_conjunction")) if send(field) == 1
- end
- end
- end
-
- def validate_only_one_housing_needs_type
- if [field_55, field_56, field_57].count(1) > 1
- %i[field_55 field_56 field_57].each do |field|
- errors.add(field, I18n.t("validations.household.housingneeds_type.only_one_option_permitted")) if send(field) == 1
- end
- end
- end
-
- def validate_no_housing_needs_questions_answered
- if [field_55, field_56, field_57, field_58, field_59, field_60].all?(&:blank?)
- errors.add(:field_59, I18n.t("validations.not_answered", question: "anybody with disabled access needs"))
- errors.add(:field_58, I18n.t("validations.not_answered", question: "other access needs"))
- %i[field_55 field_56 field_57].each do |field|
- errors.add(field, I18n.t("validations.not_answered", question: "disabled access needs type"))
- end
- end
- end
-
- def validate_reasonable_preference_homeless
- reason_fields = %i[field_70 field_71 field_72 field_73 field_74]
- if field_69 == 1 && reason_fields.all? { |field| attributes[field.to_s].blank? }
- reason_fields.each do |field|
- errors.add(field, I18n.t("validations.not_answered", question: "reason for reasonable preference"))
- end
- end
- end
-
- def validate_condition_effects
- illness_option_fields = %i[field_119 field_120 field_121 field_122 field_123 field_124 field_125 field_126 field_127 field_128]
- if household_no_illness?
- illness_option_fields.each do |field|
- if attributes[field.to_s] == 1
- errors.add(field, I18n.t("validations.household.condition_effects.no_choices"))
- end
- end
- elsif illness_option_fields.all? { |field| attributes[field.to_s].blank? }
- illness_option_fields.each do |field|
- errors.add(field, I18n.t("validations.not_answered", question: "how is person affected by condition or illness"))
- end
- end
- end
-
- def household_no_illness?
- field_118 != 1
- end
-
- def validate_lettings_type_matches_bulk_upload
- if [1, 3, 5, 7, 9, 11].include?(field_1) && !bulk_upload.general_needs?
- errors.add(:field_1, I18n.t("validations.setup.lettype.supported_housing_mismatch"))
- end
-
- if [2, 4, 6, 8, 10, 12].include?(field_1) && !bulk_upload.supported_housing?
- errors.add(:field_1, I18n.t("validations.setup.lettype.general_needs_mismatch"))
- end
- end
-
- def validate_cannot_be_la_referral_if_general_needs_and_la
- if field_78 == 4 && bulk_upload.general_needs? && owning_organisation && owning_organisation.la?
- errors.add :field_78, I18n.t("validations.household.referral.la_general_needs.prp_referred_by_la")
- end
- end
-
- def validate_la_with_local_housing_referral
- if field_78 == 3 && owning_organisation && owning_organisation.la?
- errors.add(:field_78, I18n.t("validations.household.referral.nominated_by_local_ha_but_la"))
- end
- end
-
- def validate_leaving_reason_for_renewal
- if field_134 == 1 && ![40, 42].include?(field_52)
- errors.add(:field_52, I18n.t("validations.household.reason.renewal_reason_needed"))
- end
- end
-
- def validate_relevant_collection_window
- return if start_date.blank? || bulk_upload.form.blank?
-
- unless bulk_upload.form.valid_start_date_for_form?(start_date)
- errors.add(:field_96, I18n.t("validations.date.outside_collection_window", year_combo: bulk_upload.year_combo, start_year: bulk_upload.year, end_year: bulk_upload.end_year), category: :setup)
- errors.add(:field_97, I18n.t("validations.date.outside_collection_window", year_combo: bulk_upload.year_combo, start_year: bulk_upload.year, end_year: bulk_upload.end_year), category: :setup)
- errors.add(:field_98, I18n.t("validations.date.outside_collection_window", year_combo: bulk_upload.year_combo, start_year: bulk_upload.year, end_year: bulk_upload.end_year), category: :setup)
- end
- end
-
- def start_date
- return if field_98.blank? || field_97.blank? || field_96.blank?
-
- Date.parse("20#{field_98.to_s.rjust(2, '0')}-#{field_97}-#{field_96}")
- rescue StandardError
- nil
- end
-
- def attribute_set
- @attribute_set ||= instance_variable_get(:@attributes)
- end
-
- def validate_data_types
- unless attribute_set["field_1"].value_before_type_cast&.match?(/^\d+\.?0*$/)
- errors.add(:field_1, I18n.t("validations.invalid_number", question: "letting type"))
- end
- end
-
- def validate_rent_type
- if [9, 10, 11, 12].include?(field_1) && field_130.blank?
- errors.add(:field_130, I18n.t("validations.not_answered", question: "intermediate rent type"), category: :setup)
- elsif [5, 6, 7, 8].include?(field_1) && field_129.blank?
- errors.add(:field_129, I18n.t("validations.not_answered", question: "affordable rent type"), category: :setup)
- end
- end
-
- def postcode_full
- "#{field_108} #{field_109}" if field_108 && field_109
- end
-
- def postcode_known
- if postcode_full.present?
- 1
- elsif field_107.present?
- 0
- end
- end
-
- def questions
- @questions ||= log.form.subsections.flat_map { |ss| ss.applicable_questions(log) }
- end
-
- def validate_nulls
- field_mapping_for_errors.each do |error_key, fields|
- question_id = error_key.to_s
- question = questions.find { |q| q.id == question_id }
-
- next unless question
- next if log.optional_fields.include?(question.id)
- next if question.completed?(log)
-
- if setup_question?(question)
- fields.each do |field|
- if errors.select { |e| fields.include?(e.attribute) }.none?
- errors.add(field, I18n.t("validations.not_answered", question: question.error_display_label&.downcase), category: :setup)
- end
- end
- else
- fields.each do |field|
- unless errors.any? { |e| fields.include?(e.attribute) }
- errors.add(field, I18n.t("validations.not_answered", question: question.error_display_label&.downcase))
- end
- end
- end
- end
- end
-
- def validate_incomplete_soft_validations
- routed_to_soft_validation_questions = log.form.questions.filter { |q| q.type == "interruption_screen" && q.page.routed_to?(log, nil) }.compact
- routed_to_soft_validation_questions.each do |question|
- next if question.completed?(log)
-
- question.page.interruption_screen_question_ids.each do |interruption_screen_question_id|
- next if log.form.questions.none? { |q| q.id == interruption_screen_question_id && q.page.routed_to?(log, nil) }
-
- field_mapping_for_errors[interruption_screen_question_id.to_sym]&.each do |field|
- if errors.none? { |e| e.options[:category] == :soft_validation && field_mapping_for_errors[interruption_screen_question_id.to_sym].include?(e.attribute) }
- error_message = [display_title_text(question.page.title_text, log), display_informative_text(question.page.informative_text, log)].reject(&:empty?).join(". ")
- errors.add(field, message: error_message, category: :soft_validation)
- end
- end
- end
- end
- end
-
- def setup_question?(question)
- log.form.setup_sections[0].subsections[0].questions.include?(question)
- end
-
- def validate_if_log_already_exists
- if log_already_exists?
- error_message = "This is a duplicate log"
-
- errors.add(:field_5, error_message) if bulk_upload.needstype != 1 # location
- errors.add(:field_7, error_message) # tenancycode
- errors.add(:field_12, error_message) # age1
- errors.add(:field_20, error_message) # sex1
- errors.add(:field_35, error_message) # ecstat1
- errors.add(:field_84, error_message) # tcharge
- errors.add(:field_85, error_message) if log.chcharge.present? # chcharge
- errors.add(:field_86, error_message) if bulk_upload.needstype != 1 # household_charge
- errors.add(:field_96, error_message) # startdate
- errors.add(:field_97, error_message) # startdate
- errors.add(:field_98, error_message) # startdate
- errors.add(:field_108, error_message) if bulk_upload.needstype != 2 # postcode_full
- errors.add(:field_109, error_message) if bulk_upload.needstype != 2 # postcode_full
- errors.add(:field_111, error_message) # owning_organisation
- end
- end
-
- def field_mapping_for_errors
- {
- lettype: [:field_1],
- tenancycode: [:field_7],
- postcode_known: %i[field_107 field_108 field_109],
- postcode_full: %i[field_107 field_108 field_109],
- la: %i[field_107],
- owning_organisation: [:field_111],
- managing_organisation: [:field_113],
- owning_organisation_id: [:field_111],
- managing_organisation_id: [:field_113],
- renewal: [:field_134],
- scheme: %i[field_4 field_5],
- created_by: [:field_112],
- needstype: [],
- rent_type: %i[field_1 field_129 field_130],
- startdate: %i[field_98 field_97 field_96],
- unittype_gn: %i[field_102],
- builtype: %i[field_103],
- wchair: %i[field_104],
- beds: %i[field_101],
- joint: %i[field_133],
- startertenancy: %i[field_8],
- tenancy: %i[field_9],
- tenancyother: %i[field_10],
- tenancylength: %i[field_11],
- declaration: %i[field_132],
-
- age1_known: %i[field_12],
- age1: %i[field_12],
- age2_known: %i[field_13],
- age2: %i[field_13],
- age3_known: %i[field_14],
- age3: %i[field_14],
- age4_known: %i[field_15],
- age4: %i[field_15],
- age5_known: %i[field_16],
- age5: %i[field_16],
- age6_known: %i[field_17],
- age6: %i[field_17],
- age7_known: %i[field_18],
- age7: %i[field_18],
- age8_known: %i[field_19],
- age8: %i[field_19],
-
- sex1: %i[field_20],
- sex2: %i[field_21],
- sex3: %i[field_22],
- sex4: %i[field_23],
- sex5: %i[field_24],
- sex6: %i[field_25],
- sex7: %i[field_26],
- sex8: %i[field_27],
-
- ethnic_group: %i[field_43],
- ethnic: %i[field_43],
- national: %i[field_44],
-
- relat2: %i[field_28],
- relat3: %i[field_29],
- relat4: %i[field_30],
- relat5: %i[field_31],
- relat6: %i[field_32],
- relat7: %i[field_33],
- relat8: %i[field_34],
-
- ecstat1: %i[field_35],
- ecstat2: %i[field_36],
- ecstat3: %i[field_37],
- ecstat4: %i[field_38],
- ecstat5: %i[field_39],
- ecstat6: %i[field_40],
- ecstat7: %i[field_41],
- ecstat8: %i[field_42],
-
- armedforces: %i[field_45],
- leftreg: %i[field_114],
- reservist: %i[field_46],
-
- preg_occ: %i[field_47],
-
- housingneeds: %i[field_47],
-
- illness: %i[field_118],
-
- layear: %i[field_66],
- waityear: %i[field_67],
- reason: %i[field_52],
- reasonother: %i[field_53],
- prevten: %i[field_61],
- homeless: %i[field_68],
-
- prevloc: %i[field_62],
- previous_la_known: %i[field_62],
- ppcodenk: %i[field_65],
- ppostcode_full: %i[field_63 field_64],
-
- reasonpref: %i[field_69],
- rp_homeless: %i[field_70],
- rp_insan_unsat: %i[field_71],
- rp_medwel: %i[field_72],
- rp_hardship: %i[field_73],
- rp_dontknow: %i[field_74],
-
- cbl: %i[field_75],
- chr: %i[field_76],
- cap: %i[field_77],
- letting_allocation: %i[field_75 field_76 field_77],
-
- referral: %i[field_78],
-
- net_income_known: %i[field_51],
- earnings: %i[field_50],
- incfreq: %i[field_116],
- hb: %i[field_48],
- benefits: %i[field_49],
-
- period: %i[field_79],
- brent: %i[field_80],
- scharge: %i[field_81],
- pscharge: %i[field_82],
- supcharg: %i[field_83],
- tcharge: %i[field_84],
- chcharge: %i[field_85],
- household_charge: %i[field_86],
- hbrentshortfall: %i[field_87],
- tshortfall: %i[field_88],
-
- unitletas: %i[field_105],
- rsnvac: %i[field_106],
- sheltered: %i[field_117],
-
- illness_type_1: %i[field_119],
- illness_type_2: %i[field_120],
- illness_type_3: %i[field_121],
- illness_type_4: %i[field_122],
- illness_type_5: %i[field_123],
- illness_type_6: %i[field_124],
- illness_type_7: %i[field_125],
- illness_type_8: %i[field_126],
- illness_type_9: %i[field_127],
- illness_type_10: %i[field_128],
-
- irproduct_other: %i[field_131],
-
- offered: %i[field_99],
-
- propcode: %i[field_100],
-
- majorrepairs: %i[field_92 field_93 field_94],
- mrcdate: %i[field_92 field_93 field_94],
-
- voiddate: %i[field_89 field_90 field_91],
- is_carehome: %i[field_85],
- }
- end
-
- def renttype
- case field_1
- when 1, 2, 3, 4
- :social
- when 5, 6, 7, 8
- :affordable
- when 9, 10, 11, 12
- :intermediate
- end
- end
-
- def rent_type
- case renttype
- when :social
- LettingsLog::RENT_TYPE[:social_rent]
- when :affordable
- if field_129 == 1
- LettingsLog::RENT_TYPE[:london_affordable_rent]
- else
- LettingsLog::RENT_TYPE[:affordable_rent]
- end
- when :intermediate
- case field_130
- when 1
- LettingsLog::RENT_TYPE[:rent_to_buy]
- when 2
- LettingsLog::RENT_TYPE[:london_living_rent]
- when 3
- LettingsLog::RENT_TYPE[:other_intermediate_rent_product]
- end
- end
- end
-
- def owning_organisation
- Organisation.find_by_id_on_multiple_fields(field_111)
- end
-
- def managing_organisation
- Organisation.find_by_id_on_multiple_fields(field_113)
- end
-
- def attributes_for_log
- attributes = {}
-
- attributes["lettype"] = field_1
- attributes["tenancycode"] = field_7
- attributes["la"] = field_107
- attributes["postcode_known"] = postcode_known
- attributes["postcode_full"] = postcode_full
- attributes["owning_organisation"] = owning_organisation
- attributes["managing_organisation"] = managing_organisation
- attributes["renewal"] = renewal
- attributes["scheme"] = scheme
- attributes["location"] = location
- attributes["created_by"] = created_by || bulk_upload.user
- attributes["needstype"] = bulk_upload.needstype
- attributes["rent_type"] = rent_type
- attributes["startdate"] = startdate
- attributes["unittype_gn"] = field_102
- attributes["builtype"] = field_103
- attributes["wchair"] = field_104
- attributes["beds"] = field_101
- attributes["joint"] = field_133
- attributes["startertenancy"] = field_8
- attributes["tenancy"] = field_9
- attributes["tenancyother"] = field_10
- attributes["tenancylength"] = field_11
- attributes["declaration"] = field_132
-
- attributes["age1_known"] = age1_known?
- attributes["age1"] = field_12 if attributes["age1_known"].zero? && field_12&.match(/\A\d{1,3}\z|\AR\z/)
-
- attributes["age2_known"] = age2_known?
- attributes["age2"] = field_13 if attributes["age2_known"].zero? && field_13&.match(/\A\d{1,3}\z|\AR\z/)
-
- attributes["age3_known"] = age3_known?
- attributes["age3"] = field_14 if attributes["age3_known"].zero? && field_14&.match(/\A\d{1,3}\z|\AR\z/)
-
- attributes["age4_known"] = age4_known?
- attributes["age4"] = field_15 if attributes["age4_known"].zero? && field_15&.match(/\A\d{1,3}\z|\AR\z/)
-
- attributes["age5_known"] = age5_known?
- attributes["age5"] = field_16 if attributes["age5_known"].zero? && field_16&.match(/\A\d{1,3}\z|\AR\z/)
-
- attributes["age6_known"] = age6_known?
- attributes["age6"] = field_17 if attributes["age6_known"].zero? && field_17&.match(/\A\d{1,3}\z|\AR\z/)
-
- attributes["age7_known"] = age7_known?
- attributes["age7"] = field_18 if attributes["age7_known"].zero? && field_18&.match(/\A\d{1,3}\z|\AR\z/)
-
- attributes["age8_known"] = age8_known?
- attributes["age8"] = field_19 if attributes["age8_known"].zero? && field_19&.match(/\A\d{1,3}\z|\AR\z/)
-
- attributes["sex1"] = field_20
- attributes["sex2"] = field_21
- attributes["sex3"] = field_22
- attributes["sex4"] = field_23
- attributes["sex5"] = field_24
- attributes["sex6"] = field_25
- attributes["sex7"] = field_26
- attributes["sex8"] = field_27
-
- attributes["ethnic_group"] = ethnic_group_from_ethnic
- attributes["ethnic"] = field_43
- attributes["national"] = field_44
-
- attributes["relat2"] = field_28
- attributes["relat3"] = field_29
- attributes["relat4"] = field_30
- attributes["relat5"] = field_31
- attributes["relat6"] = field_32
- attributes["relat7"] = field_33
- attributes["relat8"] = field_34
-
- attributes["ecstat1"] = field_35
- attributes["ecstat2"] = field_36
- attributes["ecstat3"] = field_37
- attributes["ecstat4"] = field_38
- attributes["ecstat5"] = field_39
- attributes["ecstat6"] = field_40
- attributes["ecstat7"] = field_41
- attributes["ecstat8"] = field_42
-
- attributes["details_known_2"] = details_known?(2)
- attributes["details_known_3"] = details_known?(3)
- attributes["details_known_4"] = details_known?(4)
- attributes["details_known_5"] = details_known?(5)
- attributes["details_known_6"] = details_known?(6)
- attributes["details_known_7"] = details_known?(7)
- attributes["details_known_8"] = details_known?(8)
-
- attributes["armedforces"] = field_45
- attributes["leftreg"] = leftreg
- attributes["reservist"] = field_46
-
- attributes["preg_occ"] = field_47
-
- attributes["housingneeds"] = housingneeds
- attributes["housingneeds_type"] = housingneeds_type
- attributes["housingneeds_other"] = housingneeds_other
-
- attributes["illness"] = field_118
-
- attributes["layear"] = field_66
- attributes["waityear"] = field_67
- attributes["reason"] = field_52
- attributes["reasonother"] = field_53
- attributes["prevten"] = field_61
- attributes["homeless"] = homeless
-
- attributes["prevloc"] = prevloc
- attributes["previous_la_known"] = previous_la_known
- attributes["ppcodenk"] = ppcodenk
- attributes["ppostcode_full"] = ppostcode_full
-
- attributes["reasonpref"] = field_69
- attributes["rp_homeless"] = field_70
- attributes["rp_insan_unsat"] = field_71
- attributes["rp_medwel"] = field_72
- attributes["rp_hardship"] = field_73
- attributes["rp_dontknow"] = field_74
-
- attributes["cbl"] = cbl
- attributes["chr"] = chr
- attributes["cap"] = cap
- attributes["letting_allocation_unknown"] = letting_allocation_unknown
-
- attributes["referral"] = field_78
-
- attributes["net_income_known"] = net_income_known
- attributes["earnings"] = earnings
- attributes["incfreq"] = field_116
- attributes["hb"] = field_48
- attributes["benefits"] = field_49
-
- attributes["period"] = field_79
- attributes["brent"] = field_80
- attributes["scharge"] = field_81
- attributes["pscharge"] = field_82
- attributes["supcharg"] = field_83
- attributes["tcharge"] = field_84
- attributes["chcharge"] = field_85
- attributes["is_carehome"] = field_85.present? ? 1 : 0
- attributes["household_charge"] = field_86
- attributes["hbrentshortfall"] = field_87
- attributes["tshortfall_known"] = tshortfall_known
- attributes["tshortfall"] = field_88
-
- attributes["hhmemb"] = hhmemb
-
- attributes["unitletas"] = field_105
- attributes["rsnvac"] = rsnvac
- attributes["sheltered"] = field_117
-
- attributes["illness_type_1"] = field_119
- attributes["illness_type_2"] = field_120
- attributes["illness_type_3"] = field_121
- attributes["illness_type_4"] = field_122
- attributes["illness_type_5"] = field_123
- attributes["illness_type_6"] = field_124
- attributes["illness_type_7"] = field_125
- attributes["illness_type_8"] = field_126
- attributes["illness_type_9"] = field_127
- attributes["illness_type_10"] = field_128
-
- attributes["irproduct_other"] = field_131
-
- attributes["offered"] = field_99
-
- attributes["propcode"] = field_100
-
- attributes["majorrepairs"] = majorrepairs
-
- attributes["mrcdate"] = mrcdate
-
- attributes["voiddate"] = voiddate
-
- attributes["first_time_property_let_as_social_housing"] = first_time_property_let_as_social_housing
-
- attributes
- end
-
- def first_time_property_let_as_social_housing
- case rsnvac
- when 15, 16, 17
- 1
- else
- 0
- end
- end
-
- def rsnvac
- field_106
- end
-
- def voiddate
- Date.new(field_91 + 2000, field_90, field_89) if field_91.present? && field_90.present? && field_89.present?
- rescue Date::Error
- Date.new
- end
-
- def majorrepairs
- mrcdate.present? ? 1 : 0
- end
-
- def mrcdate
- Date.new(field_94 + 2000, field_93, field_92) if field_94.present? && field_93.present? && field_92.present?
- rescue Date::Error
- Date.new
- end
-
- def prevloc
- field_62
- end
-
- def previous_la_known
- prevloc.present? ? 1 : 0
- end
-
- def ppcodenk
- case field_65
- when 1
- 0
- when 2
- 1
- end
- end
-
- def earnings
- field_50.round if field_50.present?
- end
-
- def net_income_known
- case field_51
- when 1
- 0
- when 2
- 1
- when 3
- 1
- when 4
- 2
- end
- end
-
- def leftreg
- field_114
- end
-
- def homeless
- case field_68
- when 1
- 1
- when 12
- 11
- end
- end
-
- def renewal
- case field_134
- when 1
- 1
- when 2
- 0
- when nil
- rsnvac == 14 ? 1 : 0
- else
- field_134
- end
- end
-
- def age1_known?
- return 1 if field_12 == "R"
- return 1 if field_12.blank?
-
- 0
- end
-
- [
- { person: 2, field: :field_13 },
- { person: 3, field: :field_14 },
- { person: 4, field: :field_15 },
- { person: 5, field: :field_16 },
- { person: 6, field: :field_17 },
- { person: 7, field: :field_18 },
- { person: 8, field: :field_19 },
- ].each do |hash|
- define_method("age#{hash[:person]}_known?") do
- return 1 if public_send(hash[:field]) == "R"
- return 0 if send("person_#{hash[:person]}_present?")
- return 1 if public_send(hash[:field]).blank?
-
- 0
- end
- end
-
- def details_known?(person_n)
- send("person_#{person_n}_present?") ? 0 : 1
- end
-
- def hhmemb
- [
- person_2_present?,
- person_3_present?,
- person_4_present?,
- person_5_present?,
- person_6_present?,
- person_7_present?,
- person_8_present?,
- ].count(true) + 1
- end
-
- def person_2_present?
- field_13.present? || field_21.present? || field_28.present?
- end
-
- def person_3_present?
- field_14.present? || field_22.present? || field_29.present?
- end
-
- def person_4_present?
- field_15.present? || field_23.present? || field_30.present?
- end
-
- def person_5_present?
- field_16.present? || field_24.present? || field_31.present?
- end
-
- def person_6_present?
- field_17.present? || field_25.present? || field_32.present?
- end
-
- def person_7_present?
- field_18.present? || field_26.present? || field_33.present?
- end
-
- def person_8_present?
- field_19.present? || field_27.present? || field_34.present?
- end
-
- def tshortfall_known
- field_87 == 1 ? 0 : 1
- end
-
- def letting_allocation_unknown
- [cbl, chr, cap].all?(0) ? 1 : 0
- end
-
- def cbl
- case field_75
- when 2
- 0
- when 1
- 1
- end
- end
-
- def chr
- case field_76
- when 2
- 0
- when 1
- 1
- end
- end
-
- def cap
- case field_77
- when 2
- 0
- when 1
- 1
- end
- end
-
- def ppostcode_full
- "#{field_63} #{field_64}".strip.gsub(/\s+/, " ")
- end
-
- def housingneeds
- if field_59 == 1
- 2
- elsif field_60 == 1
- 3
- elsif field_59.blank? || field_59&.zero?
- 1
- end
- end
-
- def housingneeds_type
- if field_55 == 1
- 0
- elsif field_56 == 1
- 1
- elsif field_57 == 1
- 2
- else
- 3
- end
- end
-
- def housingneeds_other
- return 1 if field_58 == 1
- return 0 if [field_55, field_56, field_57].include?(1)
- end
-
- def ethnic_group_from_ethnic
- return nil if field_43.blank?
-
- case field_43
- when 1, 2, 3, 18
- 0
- when 4, 5, 6, 7
- 1
- when 8, 9, 10, 11, 15
- 2
- when 12, 13, 14
- 3
- when 16, 19
- 4
- when 17
- 17
- end
- end
-
- def scheme
- return if field_4.nil? || owning_organisation.nil? || managing_organisation.nil?
-
- @scheme ||= Scheme.where(id: (owning_organisation.owned_schemes + managing_organisation.owned_schemes).map(&:id)).find_by_id_on_multiple_fields(field_4, field_5)
- end
-end
diff --git a/app/services/bulk_upload/sales/log_creator.rb b/app/services/bulk_upload/sales/log_creator.rb
index 3028914b9..1036ac8ff 100644
--- a/app/services/bulk_upload/sales/log_creator.rb
+++ b/app/services/bulk_upload/sales/log_creator.rb
@@ -31,8 +31,6 @@ private
def csv_parser
@csv_parser ||= case bulk_upload.year
- when 2022
- BulkUpload::Sales::Year2022::CsvParser.new(path:)
when 2023
BulkUpload::Sales::Year2023::CsvParser.new(path:)
else
diff --git a/app/services/bulk_upload/sales/validator.rb b/app/services/bulk_upload/sales/validator.rb
index aaee69849..1a3234911 100644
--- a/app/services/bulk_upload/sales/validator.rb
+++ b/app/services/bulk_upload/sales/validator.rb
@@ -92,8 +92,6 @@ private
def csv_parser
@csv_parser ||= case bulk_upload.year
- when 2022
- BulkUpload::Sales::Year2022::CsvParser.new(path:)
when 2023
BulkUpload::Sales::Year2023::CsvParser.new(path:)
else
diff --git a/app/services/bulk_upload/sales/year2022/csv_parser.rb b/app/services/bulk_upload/sales/year2022/csv_parser.rb
deleted file mode 100644
index d14284f50..000000000
--- a/app/services/bulk_upload/sales/year2022/csv_parser.rb
+++ /dev/null
@@ -1,83 +0,0 @@
-require "csv"
-
-class BulkUpload::Sales::Year2022::CsvParser
- MAX_COLUMNS = 126
- FORM_YEAR = 2022
-
- attr_reader :path
-
- def initialize(path:)
- @path = path
- end
-
- def row_offset
- with_headers? ? 5 : 0
- end
-
- def col_offset
- with_headers? ? 1 : 0
- end
-
- def cols
- @cols ||= ("A".."DV").to_a
- end
-
- def row_parsers
- @row_parsers ||= body_rows.map do |row|
- stripped_row = row[col_offset..]
- headers = ("field_1".."field_125").to_a
- hash = Hash[headers.zip(stripped_row)]
-
- BulkUpload::Sales::Year2022::RowParser.new(hash)
- end
- end
-
- def body_rows
- rows[row_offset..]
- end
-
- def rows
- @rows ||= CSV.parse(normalised_string, row_sep:)
- end
-
- def column_for_field(field)
- cols[headers.find_index(field) + col_offset]
- end
-
- def wrong_template_for_year?
- !(first_record_sale_date >= form.start_date && first_record_sale_date <= form.new_logs_end_date)
- end
-
-private
-
- def form
- @form ||= FormHandler.instance.sales_form_for_start_year(FORM_YEAR)
- end
-
- def first_record_sale_date
- @first_record_sale_date ||= row_parsers.first.saledate || Date.new
- end
-
- def headers
- @headers ||= ("field_1".."field_125").to_a
- end
-
- def with_headers?
- rows.map { |r| r[0] }.any? { |cell| cell&.match?(/field number/i) }
- end
-
- def row_sep
- "\n"
- end
-
- def normalised_string
- return @normalised_string if @normalised_string
-
- @normalised_string = File.read(path, encoding: "bom|utf-8")
- @normalised_string.gsub!("\r\n", "\n")
- @normalised_string.scrub!("")
- @normalised_string.tr!("\r", "\n")
-
- @normalised_string
- end
-end
diff --git a/app/services/bulk_upload/sales/year2022/row_parser.rb b/app/services/bulk_upload/sales/year2022/row_parser.rb
deleted file mode 100644
index 603860bcf..000000000
--- a/app/services/bulk_upload/sales/year2022/row_parser.rb
+++ /dev/null
@@ -1,1156 +0,0 @@
-class BulkUpload::Sales::Year2022::RowParser
- include ActiveModel::Model
- include ActiveModel::Attributes
- include InterruptionScreenHelper
-
- QUESTIONS = {
- field_1: "What is the purchaser code?",
- field_2: "What is the day of the sale completion date? - DD",
- field_3: "What is the month of the sale completion date? - MM",
- field_4: "What is the year of the sale completion date? - YY",
- field_5: "This question has been removed",
- field_6: "Was the buyer interviewed for any of the answers you will provide on this log?",
- field_7: "Age of buyer 1",
- field_8: "Age of person 2",
- field_9: "Age of person 3",
- field_10: "Age of person 4",
- field_11: "Age of person 5",
- field_12: "Age of person 6",
- field_13: "Gender identity of buyer 1",
- field_14: "Gender identity of person 2",
- field_15: "Gender identity of person 3",
- field_16: "Gender identity of person 4",
- field_17: "Gender identity of person 5",
- field_18: "Gender identity of person 6",
- field_19: "Relationship to buyer 1 for person 2",
- field_20: "Relationship to buyer 1 for person 3",
- field_21: "Relationship to buyer 1 for person 4",
- field_22: "Relationship to buyer 1 for person 5",
- field_23: "Relationship to buyer 1 for person 6",
- field_24: "Working situation of buyer 1",
- field_25: "Working situation of person 2",
- field_26: "Working situation of person 3",
- field_27: "Working situation of person 4",
- field_28: "Working situation of person 5",
- field_29: "Working situation of person 6",
- field_30: "What is buyer 1's ethnic group?",
- field_31: "What is buyer 1's nationality?",
- field_32: "What is buyer 1's gross annual income?",
- field_33: "What is buyer 2's gross annual income?",
- field_34: "Was buyer 1's income used for a mortgage application?",
- field_35: "Was buyer 2's income used for a mortgage application?",
- field_36: "What is the total amount the buyers had in savings before they paid any deposit for the property?",
- field_37: "Have any of the purchasers previously owned a property?",
- field_38: "This question has been removed",
- field_39: "What was buyer 1's previous tenure?",
- field_40: "What is the local authority of buyer 1's last settled home?",
- field_41: "Part 1 of postcode of buyer 1's last settled home",
- field_42: "Part 2 of postcode of buyer 1's last settled home",
- field_43: "Do you know the postcode of buyer 1's last settled home?",
- field_44: "Was the buyer registered with their PRP (HA)?",
- field_45: "Was the buyer registered with the local authority?",
- field_46: "Was the buyer registered with a Help to Buy agent?",
- field_47: "Was the buyer registered with another PRP (HA)?",
- field_48: "Does anyone in the household consider themselves to have a disability?",
- field_49: "Does anyone in the household use a wheelchair?",
- field_50: "How many bedrooms does the property have?",
- field_51: "What type of unit is the property?",
- field_52: "Which type of bulding is the property?",
- field_53: "What is the local authority of the property?",
- field_54: "Part 1 of postcode of property",
- field_55: "Part 2 of postcode of property",
- field_56: "Is the property built or adapted to wheelchair user standards?",
- field_57: "What is the type of shared ownership sale?",
- field_58: "Is this a resale?",
- field_59: "What is the day of the practical completion or handover date?",
- field_60: "What is the month of the practical completion or handover date?",
- field_61: "What is the year of the practical completion or handover date?",
- field_62: "What is the day of the exchange of contracts date?",
- field_63: "What is the month of the exchange of contracts date?",
- field_64: "What is the year of the exchange of contracts date?",
- field_65: "Was the household re-housed under a local authority nominations agreement?",
- field_66: "How many bedrooms did the buyer's previous property have?",
- field_67: "What was the type of the buyer's previous property?",
- field_68: "What was the full purchase price?",
- field_69: "What was the initial percentage equity stake purchased?",
- field_70: "What is the mortgage amount?",
- field_71: "Does this include any extra borrowing?",
- field_72: "How much was the cash deposit paid on the property?",
- field_73: "How much cash discount was given through Social Homebuy?",
- field_74: "What is the basic monthly rent?",
- field_75: "What are the total monthly leasehold charges for the property?",
- field_76: "What is the type of discounted ownership sale?",
- field_77: "What was the full purchase price?",
- field_78: "What was the amount of any loan, grant, discount or subsidy given?",
- field_79: "What was the percentage discount?",
- field_80: "What is the mortgage amount?",
- field_81: "Does this include any extra borrowing?",
- field_82: "How much was the cash deposit paid on the property?",
- field_83: "What are the total monthly leasehold charges for the property?",
- field_84: "What is the type of outright sale?",
- field_85: "If 'other', what is the 'other' type?",
- field_86: "This question has been removed",
- field_87: "What is the full purchase price?",
- field_88: "What is the mortgage amount?",
- field_89: "Does this include any extra borrowing?",
- field_90: "How much was the cash deposit paid on the property?",
- field_91: "What are the total monthly leasehold charges for the property?",
- field_92: "Which organisation owned this property before the sale?",
- field_93: "Username",
- field_94: "This question has been removed",
- field_95: "Has the buyer ever served in the UK Armed Forces and for how long?",
- field_96: "This question has been removed",
- field_97: "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?",
- field_98: "What is the name of the mortgage lender? - Shared ownership",
- field_99: "If 'other', what is the name of the mortgage lender?",
- field_100: "What is the name of the mortgage lender? - Discounted ownership",
- field_101: "If 'other', what is the name of the mortgage lender?",
- field_102: "What is the name of the mortgage lender? - Outright sale",
- field_103: "If 'other', what is the name of the mortgage lender?",
- field_104: "Were the buyers receiving any of these housing-related benefits immediately before buying this property?",
- field_105: "What is the length of the mortgage in years? - Shared ownership",
- field_106: "What is the length of the mortgage in years? - Discounted ownership",
- field_107: "What is the length of the mortgage in years? - Outright sale",
- field_108: "How long have the buyers been living in the property before the purchase? - Discounted ownership",
- field_109: "Are there more than two joint purchasers of this property?",
- field_110: "How long have the buyers been living in the property before the purchase? - Shared ownership",
- field_111: "Is this a staircasing transaction?",
- field_112: "Data Protection question",
- field_113: "Was this purchase made through an ownership scheme?",
- field_114: "Is the buyer a company?",
- field_115: "Will the buyers live in the property?",
- field_116: "Is this a joint purchase?",
- field_117: "Will buyer 1 live in the property?",
- field_118: "Will buyer 2 live in the property?",
- field_119: "Besides the buyers, how many people will live in the property?",
- field_120: "What percentage of the property has been bought in this staircasing transaction?",
- field_121: "What percentage of the property does the buyer now own in total?",
- field_122: "What was the rent type of the buyer's previous property?",
- field_123: "Was a mortgage used for the purchase of this property? - Shared ownership",
- field_124: "Was a mortgage used for the purchase of this property? - Discounted ownership",
- field_125: "Was a mortgage used for the purchase of this property? - Outright sale",
- }.freeze
-
- attribute :bulk_upload
- attribute :block_log_creation, :boolean, default: -> { false }
-
- attribute :field_1, :string
- attribute :field_2, :integer
- attribute :field_3, :integer
- attribute :field_4, :integer
- attribute :field_5
- attribute :field_6, :integer
- attribute :field_7, :string
- attribute :field_8, :string
- attribute :field_9, :string
- attribute :field_10, :string
- attribute :field_11, :string
- attribute :field_12, :string
- attribute :field_13, :string
- attribute :field_14, :string
- attribute :field_15, :string
- attribute :field_16, :string
- attribute :field_17, :string
- attribute :field_18, :string
- attribute :field_19, :string
- attribute :field_20, :string
- attribute :field_21, :string
- attribute :field_22, :string
- attribute :field_23, :string
- attribute :field_24, :integer
- attribute :field_25, :integer
- attribute :field_26, :integer
- attribute :field_27, :integer
- attribute :field_28, :integer
- attribute :field_29, :integer
- attribute :field_30, :integer
- attribute :field_31, :integer
- attribute :field_32, :integer
- attribute :field_33, :integer
- attribute :field_34, :integer
- attribute :field_35, :integer
- attribute :field_36, :integer
- attribute :field_37, :integer
- attribute :field_38
- attribute :field_39, :integer
- attribute :field_40, :string
- attribute :field_41, :string
- attribute :field_42, :string
- attribute :field_43, :integer
- attribute :field_44, :integer
- attribute :field_45, :integer
- attribute :field_46, :integer
- attribute :field_47, :integer
- attribute :field_48, :integer
- attribute :field_49, :integer
- attribute :field_50, :integer
- attribute :field_51, :integer
- attribute :field_52, :integer
- attribute :field_53, :string
- attribute :field_54, :string
- attribute :field_55, :string
- attribute :field_56, :integer
- attribute :field_57, :integer
- attribute :field_58, :integer
- attribute :field_59, :integer
- attribute :field_60, :integer
- attribute :field_61, :integer
- attribute :field_62, :integer
- attribute :field_63, :integer
- attribute :field_64, :integer
- attribute :field_65, :integer
- attribute :field_66, :integer
- attribute :field_67, :integer
- attribute :field_68, :integer
- attribute :field_69, :integer
- attribute :field_70, :integer
- attribute :field_71, :integer
- attribute :field_72, :integer
- attribute :field_73, :integer
- attribute :field_74, :decimal
- attribute :field_75, :decimal
- attribute :field_76, :integer
- attribute :field_77, :integer
- attribute :field_78, :integer
- attribute :field_79, :integer
- attribute :field_80, :integer
- attribute :field_81, :integer
- attribute :field_82, :integer
- attribute :field_83, :integer
- attribute :field_84, :integer
- attribute :field_85, :string
- attribute :field_86
- attribute :field_87, :integer
- attribute :field_88, :integer
- attribute :field_89, :integer
- attribute :field_90, :integer
- attribute :field_91, :integer
- attribute :field_92, :string
- attribute :field_93, :string
- attribute :field_94
- attribute :field_95, :integer
- attribute :field_96
- attribute :field_97, :integer
- attribute :field_98, :integer
- attribute :field_99, :string
- attribute :field_100, :integer
- attribute :field_101, :string
- attribute :field_102, :integer
- attribute :field_103, :string
- attribute :field_104, :integer
- attribute :field_105, :integer
- attribute :field_106, :integer
- attribute :field_107, :integer
- attribute :field_108, :integer
- attribute :field_109, :integer
- attribute :field_110, :integer
- attribute :field_111, :integer
- attribute :field_112, :integer
- attribute :field_113, :integer
- attribute :field_114, :integer
- attribute :field_115, :integer
- attribute :field_116, :integer
- attribute :field_117, :integer
- attribute :field_118, :integer
- attribute :field_119, :integer
- attribute :field_120, :integer
- attribute :field_121, :integer
- attribute :field_122, :integer
- attribute :field_123, :integer
- attribute :field_124, :integer
- attribute :field_125, :integer
-
- validates :field_2, presence: { message: I18n.t("validations.not_answered", question: "sale completion date (day)"), category: :setup }, on: :after_log
- validates :field_3, presence: { message: I18n.t("validations.not_answered", question: "sale completion date (month)"), category: :setup }, on: :after_log
- validates :field_4, presence: { message: I18n.t("validations.not_answered", question: "sale completion date (year)"), category: :setup }, on: :after_log
- validates :field_4, format: { with: /\A\d{2}\z/, message: I18n.t("validations.setup.saledate.year_not_two_digits") }, on: :after_log
-
- validates :field_57,
- inclusion: {
- in: [2, 16, 18, 24, 28, 30, 31],
- category: :setup,
- question: QUESTIONS[:field_57].downcase,
- },
- if: proc { field_57.present? && shared_ownership? },
- on: :before_log
-
- validates :field_57,
- presence: {
- message: I18n.t("validations.not_answered", question: "type of shared ownership sale"),
- category: :setup,
- },
- if: :shared_ownership?,
- on: :after_log
-
- validates :field_76,
- inclusion: {
- in: [8, 14, 27, 9, 29, 21, 22],
- category: :setup,
- question: QUESTIONS[:field_76].downcase,
- },
- if: proc { field_76.present? && discounted_ownership? },
- on: :before_log
-
- validates :field_76,
- presence: {
- message: I18n.t("validations.not_answered", question: "type of discounted ownership sale"),
- category: :setup,
- },
- if: :discounted_ownership?,
- on: :after_log
-
- validates :field_84,
- inclusion: {
- in: [10, 12],
- category: :setup,
- question: QUESTIONS[:field_84].downcase,
- },
- if: proc { field_84.present? && outright_sale? },
- on: :before_log
-
- validates :field_84,
- presence: {
- message: I18n.t("validations.not_answered", question: "type of outright sale"),
- category: :setup,
- },
- if: :outright_sale?,
- on: :after_log
-
- validates :field_85,
- presence: {
- message: I18n.t("validations.not_answered", question: "type of outright sale"),
- category: :setup,
- },
- if: proc { field_84 == 12 },
- on: :after_log
-
- validates :field_68,
- presence: {
- message: I18n.t("validations.not_answered", question: "What was the full purchase price?"),
- },
- if: :shared_ownership?,
- on: :after_log
-
- validates :field_77,
- presence: {
- message: I18n.t("validations.not_answered", question: "What was the full purchase price?"),
- },
- if: :discounted_ownership?,
- on: :after_log
-
- validates :field_87,
- presence: {
- message: I18n.t("validations.not_answered", question: "What was the full purchase price?"),
- },
- if: :outright_sale?,
- on: :after_log
-
- validates :field_109, presence: { message: I18n.t("validations.not_answered", question: "more than 2 joint buyers"), category: :setup }, if: :joint_purchase?, on: :after_log
-
- validates :field_113, presence: { message: I18n.t("validations.not_answered", question: "purchase made under ownership scheme"), category: :setup }, on: :after_log
-
- validates :field_114, presence: { message: I18n.t("validations.not_answered", question: "company buyer"), category: :setup }, if: :outright_sale?, on: :after_log
-
- validates :field_115, presence: { message: I18n.t("validations.not_answered", question: "buyers living in property"), category: :setup }, if: :outright_sale?, on: :after_log
-
- validates :field_116, presence: { message: I18n.t("validations.not_answered", question: "joint purchase"), category: :setup }, if: :joint_purchase_asked?, on: :after_log
-
- validate :validate_buyer1_economic_status, on: :before_log
- validate :validate_nulls, on: :after_log
- validate :validate_valid_radio_option, on: :before_log
-
- validate :validate_owning_org_data_given, on: :after_log
- validate :validate_owning_org_exists, on: :after_log
- validate :validate_owning_org_permitted, on: :after_log
-
- validate :validate_created_by_exists, on: :after_log
- validate :validate_created_by_related, on: :after_log
- validate :validate_relevant_collection_window, on: :after_log
- validate :validate_incomplete_soft_validations, on: :after_log
- validate :validate_if_log_already_exists, on: :after_log, if: -> { FeatureToggle.bulk_upload_duplicate_log_check_enabled? }
-
- validate :validate_data_protection_answered, on: :after_log
- validate :validate_buyers_organisations, on: :after_log
-
- def self.question_for_field(field)
- QUESTIONS[field]
- end
-
- def attribute_set
- @attribute_set ||= instance_variable_get(:@attributes)
- end
-
- def blank_row?
- attribute_set
- .to_hash
- .reject { |k, _| %w[bulk_upload block_log_creation].include?(k) }
- .values
- .compact
- .empty?
- end
-
- def log
- @log ||= SalesLog.new(attributes_for_log)
- end
-
- def valid?
- errors.clear
-
- return true if blank_row?
-
- super(:before_log)
- before_errors = errors.dup
-
- log.valid?
-
- super(:after_log)
- errors.merge!(before_errors)
-
- log.errors.each do |error|
- fields = field_mapping_for_errors[error.attribute] || []
-
- fields.each do |field|
- unless errors.include?(field)
- errors.add(field, error.message)
- end
- end
- end
-
- errors.blank?
- end
-
- def block_log_creation?
- block_log_creation
- end
-
- def log_already_exists?
- @log_already_exists ||= SalesLog
- .where(status: %w[not_started in_progress completed])
- .exists?(duplicate_check_fields.index_with { |field| log.public_send(field) })
- end
-
- def purchaser_code
- field_1
- end
-
- def spreadsheet_duplicate_hash
- attributes.slice(
- "field_1", # purcahser code
- "field_2", # saledate
- "field_3", # saledate
- "field_4", # saledate
- "field_7", # age1
- "field_13", # sex1
- "field_24", # ecstat1
- "field_54", # postcode
- "field_55", # postcode
- "field_92", # owning org
- )
- end
-
- def add_duplicate_found_in_spreadsheet_errors
- spreadsheet_duplicate_hash.each_key do |field|
- errors.add(field, :spreadsheet_dupe, category: :setup)
- end
- end
-
- def saledate
- Date.new(field_4 + 2000, field_3, field_2) if field_2.present? && field_3.present? && field_4.present?
- rescue Date::Error
- Date.new
- end
-
-private
-
- def validate_data_protection_answered
- unless field_112 == 1
- errors.add(:field_112, I18n.t("validations.not_answered", question: QUESTIONS[:field_112].downcase), category: :setup)
- end
- end
-
- def validate_buyers_organisations
- organisations_fields = %i[field_44 field_45 field_46 field_47]
- if organisations_fields.all? { |field| attributes[field.to_s].blank? }
- organisations_fields.each do |field|
- errors.add(field, "At least one option must be selected of these four")
- end
- end
- end
-
- def buyer_not_interviewed?
- field_6 == 1
- end
-
- def shared_ownership?
- field_113 == 1
- end
-
- def discounted_ownership?
- field_113 == 2
- end
-
- def outright_sale?
- field_113 == 3
- end
-
- def joint_purchase?
- field_116 == 1
- end
-
- def joint_purchase_asked?
- shared_ownership? || discounted_ownership? || field_114 == 2
- end
-
- def field_mapping_for_errors
- {
- purchid: %i[field_1],
- saledate: %i[field_2 field_3 field_4],
- noint: %i[field_6],
- age1_known: %i[field_7],
- age1: %i[field_7],
- age2_known: %i[field_8],
- age2: %i[field_8],
- age3_known: %i[field_9],
- age3: %i[field_9],
- age4_known: %i[field_10],
- age4: %i[field_10],
- age5_known: %i[field_11],
- age5: %i[field_11],
- age6_known: %i[field_12],
- age6: %i[field_12],
- sex1: %i[field_13],
- sex2: %i[field_14],
- sex3: %i[field_15],
- sex4: %i[field_16],
- sex5: %i[field_17],
- sex6: %i[field_18],
- relat2: %i[field_19],
- relat3: %i[field_20],
- relat4: %i[field_21],
- relat5: %i[field_22],
- relat6: %i[field_23],
- ecstat1: %i[field_24],
- ecstat2: %i[field_25],
- ecstat3: %i[field_26],
- ecstat4: %i[field_27],
- ecstat5: %i[field_28],
- ecstat6: %i[field_29],
- ethnic_group: %i[field_30],
- ethnic: %i[field_30],
- national: %i[field_31],
- income1nk: %i[field_32],
- income1: %i[field_32],
- income2nk: %i[field_33],
- income2: %i[field_33],
- inc1mort: %i[field_34],
- inc2mort: %i[field_35],
- savingsnk: %i[field_36],
- savings: %i[field_36],
- prevown: %i[field_37],
- prevten: %i[field_39],
- prevloc: %i[field_40],
- previous_la_known: %i[field_40],
- ppcodenk: %i[field_43],
- ppostcode_full: %i[field_41 field_42],
- pregyrha: %i[field_44],
- pregla: %i[field_45],
- pregghb: %i[field_46],
- pregother: %i[field_47],
- disabled: %i[field_48],
- wheel: %i[field_49],
- beds: %i[field_50],
- proptype: %i[field_51],
- builtype: %i[field_52],
- la_known: %i[field_53],
- la: %i[field_53],
- is_la_inferred: %i[field_53],
- pcodenk: %i[field_54 field_55],
- postcode_full: %i[field_54 field_55],
- wchair: %i[field_56],
- type: %i[field_57 field_76 field_84 field_113],
- resale: %i[field_58],
- hodate: %i[field_59 field_60 field_61],
- exdate: %i[field_62 field_63 field_64],
- lanomagr: %i[field_65],
- frombeds: %i[field_66],
- fromprop: %i[field_67],
- equity: %i[field_69],
- mortgage: %i[field_70 field_80 field_88],
- extrabor: %i[field_71 field_81 field_89],
- deposit: %i[field_72 field_82 field_90],
- cashdis: %i[field_73],
- mrent: %i[field_74],
- has_mscharge: %i[field_75 field_83 field_91],
- mscharge: %i[field_75 field_83 field_91],
- grant: %i[field_78],
- discount: %i[field_79],
- othtype: %i[field_85],
- owning_organisation_id: %i[field_92],
- created_by: %i[field_93],
- hhregres: %i[field_95],
- hhregresstill: %i[field_95],
- armedforcesspouse: %i[field_97],
- mortgagelender: %i[field_98 field_100 field_102],
- mortgagelenderother: %i[field_99 field_101 field_103],
- hb: %i[field_104],
- mortlen: %i[field_105 field_106 field_107],
- proplen: %i[field_108 field_110],
- jointmore: %i[field_109],
- staircase: %i[field_111],
- privacynotice: %i[field_112],
- ownershipsch: %i[field_113],
- companybuy: %i[field_114],
- buylivein: %i[field_115],
- jointpur: %i[field_116],
- buy1livein: %i[field_117],
- buy2livein: %i[field_118],
- hholdcount: %i[field_119],
- stairbought: %i[field_120],
- stairowned: %i[field_121],
- socprevten: %i[field_122],
- mortgageused: %i[field_123 field_124 field_125],
- soctenant: %i[field_39 field_113],
- uprn: %i[],
- }
- end
-
- def attributes_for_log
- attributes = {}
- attributes["purchid"] = purchaser_code
- attributes["saledate"] = saledate
-
- attributes["noint"] = field_6
-
- attributes["details_known_2"] = details_known?(2)
- attributes["details_known_3"] = details_known?(3)
- attributes["details_known_4"] = details_known?(4)
- attributes["details_known_5"] = details_known?(5)
- attributes["details_known_6"] = details_known?(6)
-
- attributes["age1_known"] = age1_known?
- attributes["age1"] = field_7 if attributes["age1_known"].zero? && field_7&.match(/\A\d{1,3}\z|\AR\z/)
-
- attributes["age2_known"] = age2_known?
- attributes["age2"] = field_8 if attributes["age2_known"].zero? && field_8&.match(/\A\d{1,3}\z|\AR\z/)
-
- attributes["age3_known"] = age3_known?
- attributes["age3"] = field_9 if attributes["age3_known"].zero? && field_9&.match(/\A\d{1,3}\z|\AR\z/)
-
- attributes["age4_known"] = age4_known?
- attributes["age4"] = field_10 if attributes["age4_known"].zero? && field_10&.match(/\A\d{1,3}\z|\AR\z/)
-
- attributes["age5_known"] = age5_known?
- attributes["age5"] = field_11 if attributes["age5_known"].zero? && field_11&.match(/\A\d{1,3}\z|\AR\z/)
-
- attributes["age6_known"] = age6_known?
- attributes["age6"] = field_12 if attributes["age6_known"].zero? && field_12&.match(/\A\d{1,3}\z|\AR\z/)
-
- attributes["sex1"] = field_13
- attributes["sex2"] = field_14
- attributes["sex3"] = field_15
- attributes["sex4"] = field_16
- attributes["sex5"] = field_17
- attributes["sex6"] = field_18
-
- attributes["relat2"] = field_19
- attributes["relat3"] = field_20
- attributes["relat4"] = field_21
- attributes["relat5"] = field_22
- attributes["relat6"] = field_23
-
- attributes["ecstat1"] = buyer_not_interviewed? && field_24.blank? ? 10 : field_24
- attributes["ecstat2"] = field_25
- attributes["ecstat3"] = field_26
- attributes["ecstat4"] = field_27
- attributes["ecstat5"] = field_28
- attributes["ecstat6"] = field_29
-
- attributes["ethnic_group"] = ethnic_group_from_ethnic
- attributes["ethnic"] = field_30
- attributes["national"] = buyer_not_interviewed? && field_31.blank? ? 13 : field_31
- attributes["income1nk"] = income1nk
- attributes["income1"] = field_32
- attributes["income2nk"] = field_33.present? ? 0 : 1
- attributes["income2"] = field_33
- attributes["inc1mort"] = buyer_not_interviewed? && field_32.blank? ? 2 : field_34
- attributes["inc2mort"] = field_35
- attributes["savingsnk"] = savingsnk
- attributes["savings"] = field_36
- attributes["prevown"] = buyer_not_interviewed? && field_37.blank? ? 3 : field_37
-
- attributes["prevten"] = buyer_not_interviewed? && field_39.blank? ? 0 : field_39
- attributes["prevloc"] = field_40
- attributes["previous_la_known"] = previous_la_known
- attributes["ppcodenk"] = previous_postcode_known
- attributes["ppostcode_full"] = ppostcode_full
-
- attributes["pregyrha"] = field_44
- attributes["pregla"] = field_45
- attributes["pregghb"] = field_46
- attributes["pregother"] = field_47
-
- attributes["disabled"] = buyer_not_interviewed? && field_48.blank? ? 3 : field_48
- attributes["wheel"] = buyer_not_interviewed? && field_49.blank? ? 3 : field_49
- attributes["beds"] = field_50
- attributes["proptype"] = field_51
- attributes["builtype"] = field_52
- attributes["la_known"] = field_53.present? ? 1 : 0
- attributes["la"] = field_53
- attributes["is_la_inferred"] = false
- attributes["pcodenk"] = 0 if postcode_full.present?
- attributes["postcode_full"] = postcode_full
- attributes["wchair"] = field_56
-
- attributes["type"] = sale_type
-
- attributes["resale"] = field_58
-
- attributes["hodate"] = hodate
- attributes["exdate"] = exdate
-
- attributes["lanomagr"] = field_65
-
- attributes["frombeds"] = field_66
- attributes["fromprop"] = field_67
-
- attributes["value"] = value
- attributes["equity"] = field_69
- attributes["mortgage"] = mortgage
- attributes["extrabor"] = extrabor
- attributes["deposit"] = deposit
- attributes["cashdis"] = field_73
- attributes["mrent"] = field_74
- attributes["mscharge"] = mscharge if mscharge&.positive?
- attributes["has_mscharge"] = attributes["mscharge"].present? ? 1 : 0
- attributes["grant"] = field_78
- attributes["discount"] = field_79
-
- attributes["othtype"] = field_85
-
- attributes["owning_organisation"] = owning_organisation
- attributes["managing_organisation"] = owning_organisation
- attributes["created_by"] = created_by || bulk_upload.user
- attributes["hhregres"] = hhregres
- attributes["hhregresstill"] = hhregresstill
- attributes["armedforcesspouse"] = field_97
-
- attributes["mortgagelender"] = mortgagelender
- attributes["mortgagelenderother"] = mortgagelenderother
-
- attributes["hb"] = field_104
-
- attributes["mortlen"] = mortlen
-
- attributes["proplen"] = proplen
- attributes["jointmore"] = field_109
- attributes["staircase"] = field_111
- attributes["privacynotice"] = field_112
- attributes["ownershipsch"] = field_113
- attributes["companybuy"] = field_114
- attributes["buylivein"] = field_115
- attributes["jointpur"] = field_116
- attributes["buy1livein"] = field_117
- attributes["buy2livein"] = field_118
- attributes["hholdcount"] = field_119
- attributes["stairbought"] = field_120
- attributes["stairowned"] = field_121
- attributes["socprevten"] = field_122
- attributes["mortgageused"] = mortgageused
- attributes["soctenant"] = soctenant
-
- attributes
- end
-
- def income1nk
- if field_32.present?
- 0
- else
- buyer_not_interviewed? ? 1 : nil
- end
- end
-
- def savingsnk
- if field_36.present?
- 0
- else
- buyer_not_interviewed? ? 1 : nil
- end
- end
-
- def hodate
- Date.new(field_61 + 2000, field_60, field_59) if field_59.present? && field_60.present? && field_61.present?
- rescue Date::Error
- Date.new
- end
-
- def exdate
- Date.new(field_64 + 2000, field_63, field_62) if field_62.present? && field_63.present? && field_64.present?
- rescue Date::Error
- Date.new
- end
-
- def age1_known?
- return 1 if field_7 == "R"
- return 1 if field_7.blank?
-
- 0
- end
-
- [
- { person: 2, field: :field_8 },
- { person: 3, field: :field_9 },
- { person: 4, field: :field_10 },
- { person: 5, field: :field_11 },
- { person: 6, field: :field_12 },
- ].each do |hash|
- define_method("age#{hash[:person]}_known?") do
- return 1 if public_send(hash[:field]) == "R"
- return 0 if send("person_#{hash[:person]}_present?")
- return 1 if public_send(hash[:field]).blank?
-
- 0
- end
- end
-
- def person_2_present?
- field_8.present? || field_14.present? || field_19.present?
- end
-
- def person_3_present?
- field_9.present? || field_15.present? || field_20.present?
- end
-
- def person_4_present?
- field_10.present? || field_16.present? || field_21.present?
- end
-
- def person_5_present?
- field_11.present? || field_17.present? || field_22.present?
- end
-
- def person_6_present?
- field_12.present? || field_18.present? || field_23.present?
- end
-
- def details_known?(person_n)
- send("person_#{person_n}_present?") ? 1 : 2
- end
-
- def ethnic_group_from_ethnic
- if field_30.blank?
- buyer_not_interviewed? ? 17 : nil
- else
- case field_30
- when 1, 2, 3, 18
- 0
- when 4, 5, 6, 7
- 1
- when 8, 9, 10, 11, 15
- 2
- when 12, 13, 14
- 3
- when 16, 19
- 4
- when 17
- 17
- end
- end
- end
-
- def postcode_full
- "#{field_54} #{field_55}" if field_54 && field_55
- end
-
- def ppostcode_full
- "#{field_41} #{field_42}" if field_41 && field_42
- end
-
- def sale_type
- return field_57 if shared_ownership?
- return field_76 if discounted_ownership?
- return field_84 if outright_sale?
- end
-
- def value
- return field_68 if shared_ownership?
- return field_77 if discounted_ownership?
- return field_87 if outright_sale?
- end
-
- def mortgage
- return field_70 if shared_ownership?
- return field_80 if discounted_ownership?
- return field_88 if outright_sale?
- end
-
- def extrabor
- return field_71 if shared_ownership?
- return field_81 if discounted_ownership?
- return field_89 if outright_sale?
- end
-
- def deposit
- return field_72 if shared_ownership?
- return field_82 if discounted_ownership?
- return field_90 if outright_sale?
- end
-
- def mscharge
- return field_75 if shared_ownership?
- return field_83 if discounted_ownership?
- return field_91 if outright_sale?
- end
-
- def mortgagelender
- return field_98 if shared_ownership?
- return field_100 if discounted_ownership?
- return field_102 if outright_sale?
- end
-
- def mortgagelenderother
- return field_99 if shared_ownership?
- return field_101 if discounted_ownership?
- return field_103 if outright_sale?
- end
-
- def mortlen
- return field_105 if shared_ownership?
- return field_106 if discounted_ownership?
- return field_107 if outright_sale?
- end
-
- def proplen
- return field_110 if shared_ownership?
- return field_108 if discounted_ownership?
- end
-
- def mortgageused
- return field_123 if shared_ownership?
- return field_124 if discounted_ownership?
- return field_125 if outright_sale?
- end
-
- def owning_organisation
- @owning_organisation ||= Organisation.find_by_id_on_multiple_fields(field_92)
- end
-
- def created_by
- @created_by ||= User.where("lower(email) = ?", field_93&.downcase).first
- end
-
- def hhregres
- case field_95
- when 3 then 3
- when 4, 5, 6 then 1
- when 7 then 7
- when 8 then 8
- end
- end
-
- def hhregresstill
- return unless hhregres == 1
-
- field_95
- end
-
- def previous_la_known
- field_40.present? ? 1 : 0
- end
-
- def previous_postcode_known
- return 1 if field_43.blank?
-
- 0 if field_43 == 1
- end
-
- def soctenant
- return unless field_39 && field_113
-
- if (field_39 == 1 || field_39 == 2) && field_113 == 1
- 1
- elsif field_113 == 1
- 2
- end
- end
-
- def block_log_creation!
- self.block_log_creation = true
- end
-
- def questions
- @questions ||= log.form.subsections.flat_map { |ss| ss.applicable_questions(log) }
- end
-
- def duplicate_check_fields
- %w[
- saledate
- age1
- sex1
- ecstat1
- owning_organisation
- postcode_full
- purchid
- ]
- end
-
- def validate_owning_org_data_given
- if field_92.blank?
- block_log_creation!
-
- if errors[:field_92].blank?
- errors.add(:field_92, "The owning organisation code is incorrect", category: :setup)
- end
- end
- end
-
- def validate_owning_org_exists
- if owning_organisation.nil?
- block_log_creation!
-
- if errors[:field_92].blank?
- errors.add(:field_92, "The owning organisation code is incorrect", category: :setup)
- end
- end
- end
-
- def validate_owning_org_owns_stock
- if owning_organisation && !owning_organisation.holds_own_stock?
- block_log_creation!
-
- if errors[:field_92].blank?
- errors.add(:field_92, "The owning organisation code provided is for an organisation that does not own stock", category: :setup)
- end
- end
- end
-
- def validate_owning_org_permitted
- if owning_organisation && !bulk_upload.user.organisation.affiliated_stock_owners.include?(owning_organisation)
- block_log_creation!
-
- if errors[:field_92].blank?
- errors.add(:field_92, "You do not have permission to add logs for this owning organisation", category: :setup)
- end
- end
- end
-
- def validate_created_by_exists
- return if field_93.blank?
-
- unless created_by
- errors.add(:field_93, "User with the specified email could not be found")
- end
- end
-
- def validate_created_by_related
- return unless created_by
-
- unless created_by.organisation == owning_organisation
- block_log_creation!
- errors.add(:field_93, "User must be related to owning organisation")
- end
- end
-
- def setup_question?(question)
- log.form.setup_sections[0].subsections[0].questions.include?(question)
- end
-
- def validate_nulls
- field_mapping_for_errors.each do |error_key, fields|
- question_id = error_key.to_s
- question = questions.find { |q| q.id == question_id }
-
- next unless question
- next if log.optional_fields.include?(question.id)
- next if question.completed?(log)
-
- fields.each do |field|
- unless errors.any? { |e| fields.include?(e.attribute) }
- if setup_question?(question)
- errors.add(field, I18n.t("validations.not_answered", question: question.error_display_label&.downcase), category: :setup)
- else
- errors.add(field, I18n.t("validations.not_answered", question: question.error_display_label&.downcase))
- end
- end
- end
- end
- end
-
- def validate_valid_radio_option
- log.attributes.each do |question_id, _v|
- next if question_id == "type"
-
- question = log.form.get_question(question_id, log)
-
- next unless question&.type == "radio"
- next if log[question_id].blank? || question.answer_options.key?(log[question_id].to_s) || !question.page.routed_to?(log, nil)
-
- fields = field_mapping_for_errors[question_id.to_sym] || []
-
- if setup_question?(question)
- fields.each do |field|
- if errors[field].none?
- block_log_creation!
- errors.add(field, I18n.t("validations.invalid_option", question: QUESTIONS[field].downcase), category: :setup)
- end
- end
- else
- fields.each do |field|
- if errors.none? { |e| fields.include?(e.attribute) }
- errors.add(field, I18n.t("validations.invalid_option", question: QUESTIONS[field]))
- end
- end
- end
- end
- end
-
- def validate_relevant_collection_window
- return if saledate.blank? || bulk_upload.form.blank?
-
- unless bulk_upload.form.valid_start_date_for_form?(saledate)
- errors.add(:field_2, I18n.t("validations.date.outside_collection_window", year_combo: bulk_upload.year_combo, start_year: bulk_upload.year, end_year: bulk_upload.end_year), category: :setup)
- errors.add(:field_3, I18n.t("validations.date.outside_collection_window", year_combo: bulk_upload.year_combo, start_year: bulk_upload.year, end_year: bulk_upload.end_year), category: :setup)
- errors.add(:field_4, I18n.t("validations.date.outside_collection_window", year_combo: bulk_upload.year_combo, start_year: bulk_upload.year, end_year: bulk_upload.end_year), category: :setup)
- end
- end
-
- def validate_incomplete_soft_validations
- routed_to_soft_validation_questions = log.form.questions.filter { |q| q.type == "interruption_screen" && q.page.routed_to?(log, nil) }.compact
- routed_to_soft_validation_questions.each do |question|
- next if question.completed?(log)
-
- question.page.interruption_screen_question_ids.each do |interruption_screen_question_id|
- next if log.form.questions.none? { |q| q.id == interruption_screen_question_id && q.page.routed_to?(log, nil) }
-
- field_mapping_for_errors[interruption_screen_question_id.to_sym]&.each do |field|
- if errors.none? { |e| e.options[:category] == :soft_validation && field_mapping_for_errors[interruption_screen_question_id.to_sym].include?(e.attribute) }
- error_message = [display_title_text(question.page.title_text, log), display_informative_text(question.page.informative_text, log)].reject(&:empty?).join(". ")
- errors.add(field, message: error_message, category: :soft_validation)
- end
- end
- end
- end
- end
-
- def validate_if_log_already_exists
- if log_already_exists?
- error_message = "This is a duplicate log"
-
- errors.add(:field_92, error_message) # Owning org
- errors.add(:field_2, error_message) # Sale completion date
- errors.add(:field_3, error_message) # Sale completion date
- errors.add(:field_4, error_message) # Sale completion date
- errors.add(:field_41, error_message) # Postcode
- errors.add(:field_42, error_message) # Postcode
- errors.add(:field_7, error_message) # Buyer 1 age
- errors.add(:field_13, error_message) # Buyer 1 gender
- errors.add(:field_24, error_message) # Buyer 1 working situation
- errors.add(:field_1, error_message) # Purchaser code
- end
- end
-
- def validate_buyer1_economic_status
- if field_24 == 9
- errors.add(:field_24, "Buyer 1 cannot be a child under 16")
- end
- end
-end
diff --git a/app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2022.html.erb b/app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2022.html.erb
deleted file mode 100644
index 60fc158a2..000000000
--- a/app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2022.html.erb
+++ /dev/null
@@ -1,41 +0,0 @@
-<% content_for :before_content do %>
- <%= govuk_back_link href: @form.back_path %>
-<% end %>
-
-
-
- <%= form_with model: @form, scope: :form, url: bulk_upload_lettings_log_path(id: "prepare-your-file"), method: :patch do |f| %>
- <%= f.hidden_field :year %>
-
-
Upload lettings logs in bulk (<%= @form.year_combo %>)
-
Prepare your file
-
-
Download template
-
-
- -
- Download and use <%= govuk_link_to "this template", @form.template_path %>.
-
-
-
-
Create your file
-
-
- - Fill in the template with CORE data from your housing management system according to the <%= govuk_link_to "Lettings #{@form.year_combo} Bulk Upload Specification", @form.specification_path %>.
- - Username field: To assign a log to someone else, enter the email address they use to log into CORE.
- - If you have to manually enter large volumes of data into the bulk upload template, we recommend creating logs directly in the service instead. <%= govuk_link_to "Find out more about exporting your data", bulk_upload_lettings_log_path(id: "guidance", form: { year: @form.year }) %>.
-
-
- <%= govuk_inset_text(text: "Upload separate files for general needs and supported housing logs for 2022/23 data.") %>
-
-
Save your file
-
-
- - Save your file as a CSV.
- - Your file should now be ready to upload.
-
-
- <%= f.govuk_submit class: "govuk-!-margin-top-7" %>
- <% end %>
-
-
diff --git a/app/views/bulk_upload_sales_logs/forms/prepare_your_file_2022.html.erb b/app/views/bulk_upload_sales_logs/forms/prepare_your_file_2022.html.erb
deleted file mode 100644
index fc1c1c31c..000000000
--- a/app/views/bulk_upload_sales_logs/forms/prepare_your_file_2022.html.erb
+++ /dev/null
@@ -1,34 +0,0 @@
-<% content_for :before_content do %>
- <%= govuk_back_link href: @form.back_path %>
-<% end %>
-
-
-
- <%= form_with model: @form, scope: :form, url: bulk_upload_sales_log_path(id: "prepare-your-file"), method: :patch do |f| %>
- <%= f.hidden_field :year %>
-
-
Upload sales logs in bulk (<%= @form.year_combo %>)
-
Prepare your file
-
-
Download template
-
- - Download and use <%= govuk_link_to "this template", @form.legacy_template_path %>.
-
-
-
Create your file
-
- - Fill in the template with CORE data from your housing management system according to the <%= govuk_link_to "Sales #{@form.year_combo} Bulk Upload Specification", @form.specification_path %>.
- - Username field: To assign a log to someone else, enter the email address they use to log into CORE.
- - If you have to manually enter large volumes of data into the bulk upload template, we recommend creating logs directly in the service instead. <%= govuk_link_to "Find out more about exporting your data", bulk_upload_sales_log_path(id: "guidance", form: { year: @form.year }) %>.
-
-
-
Save your file
-
- - Save your file as a CSV.
- - Your file should now be ready to upload.
-
-
- <%= f.govuk_submit %>
- <% end %>
-
-
diff --git a/config/routes.rb b/config/routes.rb
index 6749cbbac..97f6315c3 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -39,14 +39,6 @@ Rails.application.routes.draw do
get "/service-moved", to: "maintenance#service_moved"
get "/service-unavailable", to: "maintenance#service_unavailable"
- get "/download-22-23-lettings-form", to: "start#download_22_23_lettings_form"
- get "/download-22-23-lettings-bulk-upload-template", to: "start#download_22_23_lettings_bulk_upload_template"
- get "/download-22-23-lettings-bulk-upload-specification", to: "start#download_22_23_lettings_bulk_upload_specification"
-
- get "/download-22-23-sales-form", to: "start#download_22_23_sales_form"
- get "/download-22-23-sales-bulk-upload-template", to: "start#download_22_23_sales_bulk_upload_template"
- get "/download-22-23-sales-bulk-upload-specification", to: "start#download_22_23_sales_bulk_upload_specification"
-
get "/download-23-24-lettings-form", to: "start#download_23_24_lettings_form"
get "/download-23-24-lettings-bulk-upload-template", to: "start#download_23_24_lettings_bulk_upload_template"
get "/download-23-24-lettings-bulk-upload-legacy-template", to: "start#download_23_24_lettings_bulk_upload_legacy_template"
diff --git a/public/files/2022_23_lettings_paper_form.pdf b/public/files/2022_23_lettings_paper_form.pdf
deleted file mode 100644
index 9de28a0c0..000000000
Binary files a/public/files/2022_23_lettings_paper_form.pdf and /dev/null differ
diff --git a/public/files/2022_23_sales_paper_form.pdf b/public/files/2022_23_sales_paper_form.pdf
deleted file mode 100644
index 961350f37..000000000
Binary files a/public/files/2022_23_sales_paper_form.pdf and /dev/null differ
diff --git a/public/files/bulk-upload-lettings-specification-2022-23.xlsx b/public/files/bulk-upload-lettings-specification-2022-23.xlsx
deleted file mode 100644
index 15cec38b6..000000000
Binary files a/public/files/bulk-upload-lettings-specification-2022-23.xlsx and /dev/null differ
diff --git a/public/files/bulk-upload-lettings-template-2022-23.xlsx b/public/files/bulk-upload-lettings-template-2022-23.xlsx
deleted file mode 100644
index ca30388ba..000000000
Binary files a/public/files/bulk-upload-lettings-template-2022-23.xlsx and /dev/null differ
diff --git a/public/files/bulk-upload-sales-specification-2022-23.xlsx b/public/files/bulk-upload-sales-specification-2022-23.xlsx
deleted file mode 100644
index 5620f1d1c..000000000
Binary files a/public/files/bulk-upload-sales-specification-2022-23.xlsx and /dev/null differ
diff --git a/public/files/bulk-upload-sales-template-2022-23.xlsx b/public/files/bulk-upload-sales-template-2022-23.xlsx
deleted file mode 100644
index 2500a7bae..000000000
Binary files a/public/files/bulk-upload-sales-template-2022-23.xlsx and /dev/null differ
diff --git a/spec/components/bulk_upload_error_row_component_spec.rb b/spec/components/bulk_upload_error_row_component_spec.rb
index 85b2fe522..eb47fa99d 100644
--- a/spec/components/bulk_upload_error_row_component_spec.rb
+++ b/spec/components/bulk_upload_error_row_component_spec.rb
@@ -46,7 +46,7 @@ RSpec.describe BulkUploadErrorRowComponent, type: :component do
end
it "renders the question for lettings" do
- expected = "Is this letting a renewal?"
+ expected = "What do you expect the outstanding amount to be?"
result = render_inline(described_class.new(bulk_upload_errors:))
expect(result).to have_content(expected)
end
@@ -83,7 +83,7 @@ RSpec.describe BulkUploadErrorRowComponent, type: :component do
let(:field) { :field_87 }
it "renders the question for sales" do
- expected = "What is the full purchase price?"
+ expected = "Is this a staircasing transaction?"
result = render_inline(described_class.new(bulk_upload_errors:))
expect(result).to have_content(expected)
end
diff --git a/spec/factories/bulk_upload.rb b/spec/factories/bulk_upload.rb
index afa96db15..90a56a26b 100644
--- a/spec/factories/bulk_upload.rb
+++ b/spec/factories/bulk_upload.rb
@@ -4,7 +4,7 @@ FactoryBot.define do
factory :bulk_upload do
user
log_type { BulkUpload.log_types.values.sample }
- year { 2022 }
+ year { 2023 }
identifier { SecureRandom.uuid }
sequence(:filename) { |n| "bulk-upload-#{n}.csv" }
needstype { 1 }
diff --git a/spec/features/bulk_upload_lettings_logs_spec.rb b/spec/features/bulk_upload_lettings_logs_spec.rb
index 727e5462d..ac05d7dec 100644
--- a/spec/features/bulk_upload_lettings_logs_spec.rb
+++ b/spec/features/bulk_upload_lettings_logs_spec.rb
@@ -21,8 +21,12 @@ RSpec.describe "Bulk upload lettings log" do
# rubocop:disable RSpec/AnyInstance
context "when during crossover period" do
+ before do
+ allow(FeatureToggle).to receive(:force_crossover?).and_return(true)
+ end
+
it "shows journey with year option" do
- Timecop.freeze(2022, 6, 1) do
+ Timecop.freeze(2023, 6, 1) do
visit("/lettings-logs")
expect(page).to have_link("Upload lettings logs in bulk")
click_link("Upload lettings logs in bulk")
@@ -31,37 +35,27 @@ RSpec.describe "Bulk upload lettings log" do
click_button("Continue")
expect(page).to have_content("You must select a collection period to upload for")
- choose("2022/2023")
+ choose("2023/2024")
click_button("Continue")
click_link("Back")
- expect(page.find_field("form-year-2022-field")).to be_checked
- click_button("Continue")
-
- expect(page).to have_content("Upload lettings logs in bulk (2022/23)")
- click_button("Continue")
-
- expect(page).to have_content("What is the needs type?")
+ expect(page.find_field("form-year-2023-field")).to be_checked
click_button("Continue")
- expect(page).to have_content("You must answer needs type")
- choose("General needs")
+ expect(page).to have_content("Upload lettings logs in bulk (2023/24)")
click_button("Continue")
- click_link("Back")
+ expect(page).not_to have_content("What is the needs type?")
- expect(page.find_field("form-needstype-1-field")).to be_checked
- click_button("Continue")
-
- expect(page).to have_content("Upload lettings logs in bulk (2022/23)")
+ expect(page).to have_content("Upload lettings logs in bulk (2023/24)")
expect(page).to have_content("Upload your file")
click_button("Upload")
allow_any_instance_of(Forms::BulkUploadLettings::UploadYourFile).to receive(:`).and_return("not a csv")
expect(page).to have_content("Select which file to upload")
- attach_file "file", file_fixture("2021_22_lettings_bulk_upload.xlsx")
+ attach_file "file", file_fixture("2023_24_lettings_bulk_upload.xlsx")
click_button("Upload")
allow_any_instance_of(Forms::BulkUploadLettings::UploadYourFile).to receive(:`).and_return("text/csv")
diff --git a/spec/features/bulk_upload_sales_logs_spec.rb b/spec/features/bulk_upload_sales_logs_spec.rb
index 36d35d8fe..57d99a9e8 100644
--- a/spec/features/bulk_upload_sales_logs_spec.rb
+++ b/spec/features/bulk_upload_sales_logs_spec.rb
@@ -21,6 +21,10 @@ RSpec.describe "Bulk upload sales log" do
# rubocop:disable RSpec/AnyInstance
context "when during crossover period" do
+ before do
+ allow(FeatureToggle).to receive(:force_crossover?).and_return(true)
+ end
+
it "shows journey with year option" do
Timecop.freeze(2023, 5, 1) do
visit("/sales-logs")
@@ -31,15 +35,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("2022/2023")
+ choose("2023/2024")
click_button("Continue")
click_link("Back")
- expect(page.find_field("form-year-2022-field")).to be_checked
+ expect(page.find_field("form-year-2023-field")).to be_checked
click_button("Continue")
- expect(page).to have_content("Upload sales logs in bulk (2022/23)")
+ expect(page).to have_content("Upload sales logs in bulk (2023/24)")
click_button("Continue")
expect(page).to have_content("Upload your file")
@@ -48,7 +52,7 @@ RSpec.describe "Bulk upload sales log" do
allow_any_instance_of(Forms::BulkUploadSales::UploadYourFile).to receive(:`).and_return("not a csv")
expect(page).to have_content("Select which file to upload")
- attach_file "file", file_fixture("2021_22_lettings_bulk_upload.xlsx")
+ attach_file "file", file_fixture("2023_24_lettings_bulk_upload.xlsx")
click_button("Upload")
allow_any_instance_of(Forms::BulkUploadSales::UploadYourFile).to receive(:`).and_return("text/csv")
diff --git a/spec/fixtures/files/2021_22_lettings_bulk_upload.csv b/spec/fixtures/files/2021_22_lettings_bulk_upload.csv
deleted file mode 100644
index f1f947596..000000000
--- a/spec/fixtures/files/2021_22_lettings_bulk_upload.csv
+++ /dev/null
@@ -1,20 +0,0 @@
-Question,Type of letting,Who is the landlord?,Registration no/LA CORE code,Management group,Scheme code,First letting?,Tenant code,"Starter/
-Introductory Tenancy?",Type of tenancy,"If you responded 'Other' in Q2b, please state tenancy type",Tenancy Duration,Age of Person 1,Age of Person 2,Age of Person 3,Age of Person 4,Age of Person 5,Age of Person 6,Age of Person 7,Age of Person 8,Gender of Person 1,Gender of Person 2,Gender of Person 3,Gender of Person 4,Gender of Person 5,Gender of Person 6,Gender of Person 7,Gender of Person 8,Relationship (of Person 2) to Person 1,Relationship (of Person 3) to Person 1,Relationship (of Person 4) to Person 1,Relationship (of Person 5) to Person 1,Relationship (of Person 6) to Person 1,Relationship (of Person 7) to Person 1,Relationship (of Person 8) to Person 1,Economic Status of Person 1,Economic Status of Person 2,Economic Status of Person 3,Economic Status of Person 4,Economic Status of Person 5,Economic Status of Person 6,Economic Status of Person 7,Economic Status of Person 8,Ethnic group of person 1 as defined by applicant,Nationality of person 1 as defined by applicant,Is anyone in the household…?,Has anyone in the household been seriously injured or ill as a direct result of their time and activities serving as a regular or a reserve?,Does the household contain a pregnant person?,Is the tenant in receipt of or likely to be in receipt of the following:,"How much of your income comes from universal credit, state pensions or benefits (excluding child and housing benefit, UC housing element, and council tax support or tax credit)?","Tenant’s or tenant and partner’s net income (after tax deductions). For those receiving Universal Credit, enter net weekly income from employment, pensions and Universal Credit. Exclude child benefit, housing element of universal credit and council tax support. For those not receiving Universal Credit, please enter net weekly income from employment, pensions and other benefits. Exclude housing benefit, child benefit and council tax support.",Income refused,In the tenant's view what was the main reason the household left their last settled home?,"If you responded 'Other' in 9a, please state main reason",Was the reason for leaving a direct result of the removal of the spare room subsidy or benefit cap introduced from 2013?,a) Fully wheelchair accessible,b) Wheelchair access to essential rooms,c) Requires level access housing,f) Other disability,g) No disability,H) Don't know,The housing situation for this household immediately before this letting,Enter LA in which household lived immediately before this letting,Part 1 of postcode of previous accommodation,Part 2 of postcode of previous accommodation,If postcode of previous accommodation is unknown or if the previous accommodation was temporary,How long has the household continuously lived in the local authority area where the new letting is located?,How long has the household been on the waiting list of the local authority district where the new letting is located?,"Immediately prior to this letting, was this household...?",Was the household given Reasonable Preference (i.e. priority) for housing by the local authority?,Homeless or about to lose their home (within 56 days),"Living in insanitary, overcrowded or unsatisfactory housing",A need to move on medical and welfare grounds (including a disability),A need to move to avoid hardship to themselves or others,Don't know,Was the letting made under any of the following allocations systems? (CBL),Was the letting made under any of the following allocations systems? (CHR),Was the letting made under any of the following allocations systems? (CAP),Source of referral for this letting?,Rent and other charges period,Basic rent,Service charge,Personal Service Charge,Support charge,Total charge,total charge for care homes,Please tick if there is neither rent nor charge to the occupant for the accommodation,"After housing benefit and/or housing element of Universal Credit payment is received, will there be an outstanding amount for basic rent (18i) and/or benefit eligible charges (18ii)?","After housing benefit and/or other housing support payments are received, will there be an outstanding amount for basic rent and/or benefit eligible charges?",void date,void date,void date,Major repairs completion date,Major repairs completion date,Major repairs completion date,if the unit is in a supported scheme for stays of one month or less….,tenancy start date,tenancy start date,tenancy start date,"How many times has the unit been previously offered since becoming available for relet since the last tenancy ended or as a first let? For an Affordable Rent or Intermediate Rent Letting, only include number of offers as that type. (For a property let at the firsty attempt enter '0').",RP property reference (if applicable),Number of bedrooms,Type of unit,Type of building,Is property built or adapted to wheelchair users standards,"If this is a relet, was the property most recently let on",Reason for vacancy,Enter LA of property,Part 1 of postcode of property,Part 2 of postcode of property,If previous postcode and new postcode are the same,Managed (owning) organisation CORE ID,Username Field,Managing (Data providing) Organisation CORE ID ,"If they've ever served as a regular, did they leave…",Enter the Unique Property Reference Number if known,Is the income…?,Is this letting sheltered accommodation?,Does anyone in the household have any physical or mental health conditions or illness lasting or expected to last for 12 months or more?,Vision (e.g. blindness or partial sight),Hearing (e.g. deatness or partial hearing),Mobility (e.g. walking short distances or climbing stairs),"Dexterity (e.g. lifting and carrying objects, using a keyboard)",Learning or understanding or concentrating,Memory,Mental health,Stamina or breathing or fatigue,"Socially or behaviourally (e.g. associated with autism spectral disorder (ASD) which includes Aspergers', or attention deficit hyperactivity disorder (ADHD))",Other,Is this letting a London Affordable Rent letting?,Is this letting…?,"If you responded 'Other' in 0bi, please state product",Data Protection question,,,
-Values,1 - 12,1 - 2,6 or 7 digits,1 - 999,,1 - 2,max 13 digits,1 - 2,1 - 5,Text ,1 - 99,15 - 120 or R,,,,,,,,"F, M, X or R",,,,,,,,"P,C,X or R",,,,,,,0 - 10,,,,,,,,1 - 19,1 - 17,1 - 5,1 - 3,,"1, 3 or 6 - 9",1 - 4,0 - 9999,1 or Null,"1 - 2, 4, 7 - 14, 16 - 20, 28 - 31 or 34 - 46",Text,2 - 6,1 or null ,,,,,,"3- 4, 6 - 10, 13, 14, 18, 19, 21 or 23 - 33",ONS CODE - 4 or 9 Digits,XXX(X),XXX,1 or null,"1, 2 or 5 - 10",,"1, 7 or 11",1 - 3,1 or null,,,,,1 or 2,,,"1 - 16 (not 5, 6 or 11)",1 - 10,xxxx.xx,,,,,,1 or null,1 - 3,0 - 9999,1 - 31,1 - 12,10 - 20,1 - 31,1 - 12,13 - 20,1 or null,1 - 31,1 - 12,19 or 20,0+,12 Digits,1 - 7,"1, 2, 4 or 6 - 10",1 or 2,,1 - 4,"5, 6 or 8 - 19",ONS CODE 9 digits,,,1 or null,up to 7,username (name for signing into CORE) of person this log should be assigned to.,up to 7,3 - 6,"Numeric, up to 12 digits",1 - 3,1 - 4,1 - 3,1 or null,,,,,,,,,,1 - 3,,Text,1,,,
-Can Be Null?,No,"only if 1 = 2, 4, 6, 8, 10 or 12","only if field 1 = 2, 4, 6, 8, 10 or 12 or field 2 = 1","only if field 1 = 1, 3, 5, 7, 9 or 11",,"only if 1 = 1, 3, 5, 7, 9 or 11",No,,,"Yes, if 9 is not 3","only if 9 = 1, 2, 3 or 5",No,"Yes, if field 21, 28 and 36 are null","Yes, if 22, 29 and 37 are null","Yes, if 23, 30 and 38 are null","Yes, if 24, 31 and 39 are null","Yes, if 25, 32 and 40 are null","Yes, if 26, 33 and 41 are null","Yes, if 27, 34 and 42 are null", No,"Yes, if 13, 28 and 36 are null","Yes, if 14, 29 and 37 are null","Yes, if 15, 30 and 38 are null","Yes, if 16, 31 and 39 are null","Yes, if 17, 32 and 40 are null","Yes, if 18, 33 and 41 are null","Yes, if 19, 34 and 42 are null","Yes, if 13, 21 and 36 are null","Yes, if 14, 22 and 37 are null","Yes, if 15, 23 and 38 are null","Yes, if 16, 24 and 39 are null","Yes, if 17, 25 and 40 are null","Yes, if 18, 26 and 41 are null","Yes, if 19, 27 and 42 are null",No,"Yes, if 13, 21 and 28 are null","Yes, if 14, 22 and 29 are null","Yes, if 15, 23 and 30 are null","Yes, if 16, 24 and 31 are null","Yes, if 17, 25 and 32 are null","Yes, if 18, 26 and 33 are null","Yes, if 19, 27 and 34 are null",No,,,"Yes, must be null if 45 = 2 or 3;
-no, if 45 = 1, 4 or 5",No,,,If 51 = 1,Yes,No,"Yes, if 52 is not 20",No,"Selections are ((A or B or C)) - ((A, or B or C and F)) - ((F)) - ((G)) - ((H))",,,,,,No,,"Yes, if 65 = 1",,"Yes, if 63 and 64 contain full and valid entries",No,,,," If 69 = 1, select at least one of the 5 categories;
-If 69 = 2 or 3, then Null.",,,,,No,,,,,if 85 or 86 = 1,Yes,,,if 85 or 86 = 1,Only if fields 80 - 84 and 86 are not null,Only if fields 80 - 85 are not null,"Only if field 48 = 3, 6, 7 or 8 ","If 87 = 2 or 3;
-if 87 = 1, then a value must be entered",No,,,Yes,,,,No,,,,"Only if 1 = 2, 4, 6, 8, 10 or 12",,,,No,"Only if 1 = 2, 4, 6, 8, 10 or 12;
-or 106 = 15 - 17",No,"Only if 1 = 2, 4, 6, 8, 10 or 12",,,Yes,No,,Yes: DCLG Admin only,"Only if 45 = 2, 3 or 4",Yes,"Yes, if 51 = 1","Only if 1 = 1, 3, 5, 7, 9 or 11",No,Yes,,,,,,,,,,Only if 1 = 1 - 4 or 9 - 12.,Only if 1 = 1 - 8.,,No,,,
-eCORE Format and Duplicate check,Incorrect GN/SH combination check fields,,,,,Question Removed from 2020/21,,,,,,Duplicate check field,,,,,,,,Duplicate check field,,,,,,,,,,,,,,,Duplicate check field,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Duplicate check field,,,,,,,,,,,,Duplicate check fields,,,,Duplicate check field,General Needs lettings only,,,All lettings,General Needs lettings only,All lettings,General Needs lettings only,,,Question Removed from 2020/21,Duplicate check field, “Username does not exist”. ,,,Question removed from 21/22 onwards,,Supported Housing lettings only.,,,,,,,,,,,,Affordable Rent Lettings only,Intermediate Rent Lettings only,,,,,
-Question Number,1a,1b,,1c,,1d,1b,2a,2b,2ba,2c,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4ai,4b,5,6,7,8,,9a,9aa,9b,10,,,,,,11,12a,12b,,,12c,12d,13,14a,14b,,,,,15,,,16,17, 18ai, 18aii, 18aiii, 18aiv, 18av, 18b, 18c,18d,,19,,,,,,,1,,,20,21a,22,23,24,25,26,27,28,,,28,,,,4aii,21b,8a,1e,10ia,10ib,10ib,10ib,10ib,10ib,10ib,10ib,10ib,10ib,10ib,0a,0bi,0bii,,,,
-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,,,
-,2,,,1,1,,4626,2,5,,,47,,,,,,,,M,,,,,,,,,,,,,,,4,,,,,,,,13,1,2,,3,1,4,,1,34,,3,,,,,,1,33,E09000028,,,1,6,6,7,2,,,,,,2,2,2,4,1,90,141,5,43,279,,,3,,10,8,20,,,,,17,9,21,0,439JOE005A,,,,2,,12,,,,,107242,mt.data.gary.meyler@communities.gov.uk,107242,,439JOE005A,1,1,1,,,,,,,,,,,,,,,,,
-,2,,,1,1,,4173,2,5,,,R,,,,,,,,R,,,,,,,,,,,,,,,0,,,,,,,,17,13,3,,3,3,4,,1,28,,3,,,,,,1,25,E09000030,,,1,6,6,1,3,,,,,,2,2,2,4,1,90,141,5,43,279,,,3,,2,3,20,,,,,22,4,21,0,439JOE001D,,,,2,,12,,,,,107242,mt.data.gary.meyler@communities.gov.uk,107242,,439JOE001D,1,1,1,,,,,,,,,,,,,,,,,
-,2,,,1,1,,4196,2,5,,,46,,,,,,,,F,,,,,,,,,,,,,,,6,,,,,,,,7,1,2,,3,3,4,,1,28,,3,,,,,,1,28,E09000028,,,1,6,6,7,3,,,,,,2,2,2,4,1,90,141,5,43,279,,,3,,14,4,20,,,,,29,4,21,0,439JOE006F,,,,2,,12,,,,,107242,mt.data.gary.meyler@communities.gov.uk,107242,,439JOE006F,1,1,1,,,,,,,,,,,,,,,,,
-,2,,,1,1,,4198,2,5,,,41,,,,,,,,M,,,,,,,,,,,,,,,8,,,,,,,,1,1,3,,3,3,4,,1,28,,3,,,,,,1,28,E09000028,,,1,6,6,7,3,,,,,,2,2,2,4,1,90,141,5,43,279,,,3,,26,3,20,,,,,30,4,21,0,439JOE001A,,,,2,,12,,,,,107242,mt.data.gary.meyler@communities.gov.uk,107242,,439JOE001A,1,1,1,,,,,,,,,,,,,,,,,
-,2,,,1,1,,4220,2,5,,,R,,,,,,,,R,,,,,,,,,,,,,,,0,,,,,,,,17,13,3,,3,3,4,,1,28,,3,,,,,,1,25,E09000030,,,1,6,6,1,3,,,,,,2,2,2,4,1,90,141,5,43,279,,,3,,13,4,20,,,,,8,5,21,0,439JOE008A,,,,2,,12,,,,,107242,mt.data.gary.meyler@communities.gov.uk,107242,,439JOE008A,1,1,1,,,,,,,,,,,,,,,,,
-,2,,,1,1,,4285,2,5,,,R,,,,,,,,R,,,,,,,,,,,,,,,0,,,,,,,,17,13,3,,3,3,4,,1,28,,3,,,,,,1,25,E09000030,,,1,6,6,1,3,,,,,,2,2,2,4,1,90,141,5,43,279,,,3,,7,5,20,,,,,1,6,21,0,439JOE006B,,,,2,,8,,,,,107242,mt.data.gary.meyler@communities.gov.uk,107242,,439JOE006B,1,1,1,,,,,,,,,,,,,,,,,
-,2,,,1,1,,4189,2,5,,,27,,,,,,,,F,,,,,,,,,,,,,,,4,,,,,,,,12,1,2,,3,1,4,,1,18,,3,,,,,,1,18,E09000028,,,1,6,6,7,2,,,,,,2,2,2,4,1,90,141,5,43,279,,,3,,17,4,20,,,,,26,4,21,0,439JOE008F,,,,2,,11,,,,,107242,mt.data.gary.meyler@communities.gov.uk,107242,,439JOE008F,1,1,1,,,,,,,,,,,,,,,,,
-,2,,,1,1,,4533,2,5,,,30,,,,,,,,F,,,,,,,,,,,,,,,4,,,,,,,,1,1,3,,3,1,4,,1,7,,3,,,,,,1,7,E09000028,,,1,6,6,7,2,,,,,,2,2,2,4,1,90,141,5,43,279,,,3,,22,7,20,,,,,14,8,21,0,439KOE007F,,,,2,,6,,,,,107242,mt.data.gary.meyler@communities.gov.uk,107242,,439KOE007F,1,1,1,,,,,,,,,,,,,,,,,
-,2,,,1,1,,4366,2,5,,,34,,,,,,,,M,,,,,,,,,,,,,,,4,,,,,,,,3,7,2,,3,1,4,,1,18,,3,,,,,,1,18,E09000028,,,1,6,6,7,1,,,,,1,2,2,2,4,1,90,141,5,43,279,,,3,,30,5,20,,,,,22,6,21,0,439JOE001C,,,,2,,12,,,,,107242,mt.data.gary.meyler@communities.gov.uk,107242,,439JOE001C,1,1,1,,,,,,,,,,,,,,,,,
diff --git a/spec/fixtures/files/2021_22_lettings_bulk_upload.xlsx b/spec/fixtures/files/2021_22_lettings_bulk_upload.xlsx
deleted file mode 100644
index ee6644dd4..000000000
Binary files a/spec/fixtures/files/2021_22_lettings_bulk_upload.xlsx and /dev/null differ
diff --git a/spec/fixtures/files/2021_22_lettings_bulk_upload_empty.xlsx b/spec/fixtures/files/2021_22_lettings_bulk_upload_empty.xlsx
deleted file mode 100644
index f1b7de0bb..000000000
Binary files a/spec/fixtures/files/2021_22_lettings_bulk_upload_empty.xlsx and /dev/null differ
diff --git a/spec/fixtures/files/2022_23_lettings_bulk_upload.csv b/spec/fixtures/files/2022_23_lettings_bulk_upload.csv
deleted file mode 100644
index c20057489..000000000
--- a/spec/fixtures/files/2022_23_lettings_bulk_upload.csv
+++ /dev/null
@@ -1,74 +0,0 @@
-Question,What is the letting type?,[BLANK],[BLANK],"Management group code
-
-If supported housing","Scheme code
-
-If supported housing",[BLANK],"What is the tenant code?
-
-This is how you usually refer to this tenancy on your own systems",Is this a starter tenancy? ,What is the tenancy type? ,If 'Other' what is the tenancy type?,"What is the length of the fixed-term tenancy to the nearest year?
-
-If fixed-term tenancy",Age of Person 1,Age of Person 2,Age of Person 3,Age of Person 4,Age of Person 5,Age of Person 6,Age of Person 7,Age of Person 8,Gender identity of Person 1,Gender identity of Person 2,Gender identity of Person 3,Gender identity of Person 4,Gender identity of Person 5,Gender identity of Person 6,Gender identity of Person 7,Gender identity of Person 8,Person 2's relationship to lead tenant,Person 3's relationship to lead tenant,Person 4's relationship to lead tenant,Person 5's relationship to lead tenant,Person 6's relationship to lead tenant,Person 7's relationship to lead tenant,Person 8's relationship to lead tenant,Working situation of Person 1,Working situation of Person 2,Working situation of Person 3,Working situation of Person 4,Working situation of Person 5,Working situation of Person 6,Working situation of Person 7,Working situation of Person 8,What is the lead tenant’s ethnic group? ,What is the lead tenant’s nationality? ,Does anybody in the household have links to the UK armed forces? ,Was the person seriously injured or ill as a result of serving in the UK armed forces? ,Is anybody in the household pregnant? ,Is the tenant likely to be receiving benefits related to housing? ,"How much of the household's income is from Universal Credit, state pensions or benefits? ","How much income does the household have in total?
-
-Include any income after tax from employment, pensions, and Universal Credit
-
-Don't include National Insurance (NI) contributions and tax, housing benefit, child benefit, or council tax support ",Do you know the household's income?,What is the tenant's main reason for the household leaving their last settled home? ,"If 'other', what was the main reason for leaving their last settled home?",[BLANK],"Disabled access needs
-
-a) Fully wheelchair-accessible housing ","Disabled access needs
-
-b) Wheelchair access to essential rooms ","Disabled access needs
-
-c) Level access housing ","Disabled access needs
-
-f) Other disabled access needs ","Disabled access needs
-
-g) No disabled access needs ","Disabled access needs
-
-h) Don’t know",Where was the household immediately before this letting? ,What is the local authority of the household's last settled home?,Part 1 of postcode of last settled home,Part 2 of postcode of last settled home ,Do you know the postcode of the last settled home?,How long has the household continuously lived in the local authority area of the new letting? ,How long has the household been on the local authority waiting list for the new letting? ,Was the tenant homeless directly before this tenancy?,Was the household given 'reasonable preference' by the local authority? ,"Reasonable Preference
-
-They were homeless or about to lose their home (within 56 days) ","Reasonable Preference
-
-They were living in unsanitary, overcrowded or unsatisfactory housing ","Reasonable Preference
-
-They needed to move due to medical and welfare reasons (including disability) ","Reasonable Preference
-
-They needed to move to avoid hardship to themselves or others ","Reasonable Preference
-
-Don't know ",Was the letting made under Choice-Based Lettings (CBL)? ,Was the letting made under the Common Housing Register (CHR)? ,Was the letting made under the Common Allocation Policy (CAP)? ,What was the source of referral for this letting? ,How often does the household pay rent and other charges? ,What is the basic rent? ,What is the service charge? ,What is the personal service charge? ,What is the support charge? ,Total charge,"
-If this is a care home,
-how much does the household pay every [time period]?","Does the household pay rent or other charges for the accommodation?
-
-If supported housing ","After the household has received any housing-related benefits, will they still need to pay basic rent and other charges? ",What do you expect the outstanding amount to be? ,What is the void or renewal day? - DD,What is the void or renewal month? - MM,What is the void or renewal year? - YYYY,What day were Major Repairs completed on? - DD,What month were Major Repairs completed on? - MM,What year were Major Repairs completed on? - YY,[BLANK],What day did the tenancy start? - DD,What month did the tenancy start? - MM,What year did the tenancy start? - YY,"Since becoming available, how many times has the property been previously offered? ","What is the property reference?
-
-This is how you usually refer to this property on your own systems","How many bedrooms does the property have?
-
-If general needs","What type of unit is the property?
-
-If general needs","Which type of building is the property?
-
-If general needs","Is the property built or adapted to wheelchair-user standards?
-
-If general needs","What type was the property most recently let as?
-
-If re-let",What is the reason for the property being vacant? ,"What is the local authority of the property?
-
-If general needs","Part 1 of postcode of property
-
-If general needs","Part 2 of postcode of property
-
-If general needs",[BLANK],"Which organisation owns this property?
-
-Organisation's CORE ID",Username Field,"Which organisation manages this property?
-
-Organisation's CORE ID",Is the person still serving in the UK armed forces? ,[BLANK],How often does the household receive income? ,"Is this letting in sheltered accommodation?
-
-If supported housing",Does anybody in the household have a physical or mental health condition (or other illness) expected to last 12 months or more? ,"Vision, for example blindness or partial sight","Hearing, for example deatness or partial hearing","Mobility, for example walking short distances or climbing stairs","Dexterity, for example lifting and carrying objects, using a keyboard",Learning or understanding or concentrating,Memory,"Mental health, for example depression or anxiety",Stamina or breathing or fatigue,"Socially or behaviourally (e.g. associated with autism spectrum disorder (ASD) which includes Aspergers', or attention deficit hyperactivity disorder (ADHD))",Other illness or condition,Is this letting a London Affordable Rent letting?,Which type of Intermediate Rent is this letting?,Which 'Other' type of Intermediate Rent is this letting?,Has the tenant seen the DLUHC privacy notice? ,Is this a joint tenancy? ,Is this a renewal to the same tenant in the same property?,
-Values,1 - 12,,,1 - 999,,,max 13 digits,1 - 2,2 - 7,Text ,1 - 99,15 - 120 or R,1 - 120 or R,,,,,,,"M, F, X or R",,,,,,,,"P,C,X or R",,,,,,,0 - 11,,,,,,,,1 - 19,"12 - 13, 17 - 19",1 - 6,1 - 3,,"1, 3, 6, 9",1 - 4,0 - 99999,1 - 4,"1 - 2, 4, 7 - 14, 16 - 20, 28 - 31 or 34 - 47",Text,,1 or null ,,,,,,"3- 4, 6 - 7, 9 - 10, 13 - 14, 18 - 19, 21 or 23 - 35",ONS CODE - E + 8 Digits,XXX(X),XXX,1 - 2,1 - 2 or 5 - 10,2 or 5 - 11,1 or 12,1 - 3,1 or null,,,,,1 or 2,,,"1 - 4, 7 - 10, 12 - 17",1 - 9,xxxx.xx,,,,,,1 or null,1 - 3,0 - 9999,1 - 31,1 - 12,19 - 23,1 - 31,1 - 12,13 - 23,,1 - 31,1 - 12,22-23,0+,12 Digits,1 - 7,"1, 2, 4 or 6 - 10",1 or 2,,1 - 4,"5, 6, 8 - 14, ",ONS CODE E + 9 digits,XXX(X),XXX,,Up to 7 digits,Username of CORE account this letting log should be assigned to,Up to 7 digits,3 - 6,,1 - 3,1 - 4,1 - 3,1 or null,,,,,,,,,,1 - 3,,Text,1,1 - 3,1 - 2,
-Can be null?,No,,,"only if field 1 = 1, 3, 5, 7, 9 or 11",,,No,,,"Yes, if 9 is not 3","Yes, if 9 = 2, 3, 5 or 7",No,"Yes, if field 21, 28 and 36 are null","Yes, if 22, 29 and 37 are null","Yes, if 23, 30 and 38 are null","Yes, if 24, 31 and 39 are null","Yes, if 25, 32 and 40 are null","Yes, if 26, 33 and 41 are null","Yes, if 27, 34 and 42 are null", No,"Yes, if 13, 28 and 36 are null","Yes, if 14, 29 and 37 are null","Yes, if 15, 30 and 38 are null","Yes, if 16, 31 and 39 are null","Yes, if 17, 32 and 40 are null","Yes, if 18, 33 and 41 are null","Yes, if 19, 34 and 42 are null","Yes, if 13, 21 and 36 are null","Yes, if 14, 22 and 37 are null","Yes, if 15, 23 and 38 are null","Yes, if 16, 24 and 39 are null","Yes, if 17, 25 and 40 are null","Yes, if 18, 26 and 41 are null","Yes, if 19, 27 and 42 are null",No,"Yes, if 13, 21 and 28 are null","Yes, if 14, 22 and 29 are null","Yes, if 15, 23 and 30 are null","Yes, if 16, 24 and 31 are null","Yes, if 17, 25 and 32 are null","Yes, if 18, 26 and 33 are null","Yes, if 19, 27 and 34 are null",No,,,"Yes, must be null if 45 = 2 or 3;
-no, if 45 = 1, 4 or 5",No,,,If 51 = 4,No,No,"Yes, if 52 is not 20",,"Selections are ((A or B or C)) - ((A, or B or C and F)) - ((F)) - ((G)) - ((H))",,,,,,No,,"Yes, if 65 = 1",,"Yes, if 63 and 64 contain full and valid entries",No,,,," If 69 = 1, select at least one of the 5 categories;
-If 69 = 2 or 3, then Null.",,,,,No,,,,,Only if 85 or 86 = 1,Yes,,,Only if 85 or 86 = 1,Only if fields 80 - 84 and 86 are not null,Only if fields 80 - 85 are not null,Only if field 48 = 3 or 9 ,"If 87 = 2 or 3;
-if 87 = 1, then a value must be entered",No,,,Yes,,,,No,,,"If the property is being let for the first time, enter 0.","Only if 1 = 2, 4, 6, 8, 10 or 12",,,,No,"Only if 1 = 2, 4, 6, 8, 10 or 12;
-or 106 = 15 - 17",No,"Only if 1 = 2, 4, 6, 8, 10 or 12",,,,No,,No,"Yes, if 45 = 2, 3 or 6",,"Yes, if 50 = 1","Only if 1 = 1, 3, 5, 7, 9 or 11",No,Yes,,,,,,,,,,Only if 1 = 1 - 4 or 9 - 12.,Only if 1 = 1 - 8.,Only if 130 is not 3,No,No,Yes,
-Bulk upload format and duplicate check,All lettings,Question removed from 22/23 onwards,,Supported housing only,,Question Removed from 2020/21,,,,,,Duplicate check field,,,,,,,,Duplicate check field,,,,,,,,,,,,,,,Duplicate check field,,,,,,,,,,,,,,,,,,,Question removed from 22/23 onwards,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Duplicate check field,,,,,,,,,,,Question removed from 22/23 onwards,Duplicate check fields,,,,Duplicate check field,General Needs lettings only,,,All lettings,General Needs lettings only,All lettings,General Needs lettings only,,,Question removed from 2020/21,Duplicate check field, “Username does not exist”. ,,,Question removed from 21/22 onwards,,Supported Housing lettings only.,,,,,,,,,,,,Affordable Rent Lettings only,Intermediate Rent Lettings only,,All lettings,All lettings,,
-Bulk upload 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,
-,1,,,,,,123,1,4,,2,55,54,,,,,,,F,,,,,,,,,,,,,,,1,,,,,,,,17,13,2,,2,3,4,,2,7,,,,,,,,,3,,,,2,1,2,1,3,,,,,,,,,16,4,1000,100,100,100,1300,,,,,,,,,,,,13,1,23,,,4,1,1,2,,,,EC1N,2TD,,3,,3,,,,,2,,,,,,,,,,,,,,1,2,2
-,1,,,,,,123,1,2,,6,55,54,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
-,1,,,,,,123,1,2,,,55,54,,,,,,,"A",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,13,1,23,,,,,,,,,,,,,123,,123,,,,,,,,,,,,,,,,,,,,,2,
diff --git a/spec/fixtures/files/2023_24_lettings_bulk_upload.csv b/spec/fixtures/files/2023_24_lettings_bulk_upload.csv
new file mode 100644
index 000000000..ea5a8a0b1
--- /dev/null
+++ b/spec/fixtures/files/2023_24_lettings_bulk_upload.csv
@@ -0,0 +1,50 @@
+ ,Setting up this lettings log,,,,,,,,,,,,,,,,,Property information,,,,,,,,,,,,,,,,,,,,,Tenancy information,,,,,,Household characteristics,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Household needs,,,,,,,,,,,,,,,,,,,,,Household situation,,,,,,,,,,,,,,,,,,,,"Income, benefits and outgoings",,,,,,,,,,,,,,
+Question,Which organisation owns this property?,Which organisation manages this letting?,What is the CORE username of the account this letting log should be assigned to? ,What is the needs type?,What is the letting type?,Is this letting a renewal?,What is the tenancy start date? - day DD,What is the tenancy start date? - month MM,What is the tenancy start date? - year YY,Is this a London Affordable Rent letting?,Which type of Intermediate Rent is this letting?,Which 'Other' type of Intermediate Rent is this letting?,What is the tenant code?,What is the property reference?,What management group does this letting belong to?,What scheme does this letting belong to?,Which location is this letting for?,"If known, provide 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 was the property most recently let as?,What is the reason for the property being vacant?,How many times was the property offered between becoming vacant and this letting?,What type of unit is the property?,Which type of building is the property?,Is the property built or adapted to wheelchair-user standards?,How many bedrooms does the property have?,What is the void date? - day DD,What is the void date? - month MM,What is the void date? - year YYYY,What date were any major repairs completed on? - day DD,What date were any major repairs completed on? - month MM,What date were any major repairs completed on? - year YY,Is this a joint tenancy?,Is this a starter tenancy?,What is the type of tenancy?,"If 'Other', what is the type of tenancy?",What is the length of the fixed-term tenancy to the nearest year?,Is this letting in sheltered accommodation?,Has the tenant seen the DLUHC privacy notice?,What is the lead tenant’s age?,Which of these best describes the lead tenant’s gender identity? ,Which of these best describes the lead tenant's ethnic background?,What is the lead tenant’s nationality?,Which of these best describes the lead tenant’s working situation?,What is person 2's relationship to the lead tenant?,What is person 2's age?,Which of these best describes person 2's gender identity?,Which of these best describes person 2's working situation?,What is person 3's relationship to the lead tenant?,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?,What is person 4's relationship to the lead tenant?,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?,What is person 5's relationship to the lead tenant?,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?,What is person 6's relationship to the lead tenant?,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 is person 7's relationship to the lead tenant?,What is person 7's age?,Which of these best describes person 7's gender identity?,Which of these best describes person 7's working situation?,What is person 8's relationship to the lead tenant?,What is person 8's age?,Which of these best describes person 8's gender identity?,Which of these best describes person 8's working situation?,Does anybody in the household have links to the UK armed forces?,Is this person still serving in the UK armed forces?,Was this person seriously injured or ill as a result of serving in the UK armed forces?,Is anybody in the household pregnant?,"Disabled access needs
+
+a) Fully wheelchair-accessible housing","Disabled access needs
+
+b) Wheelchair access to essential rooms","Disabled access needs
+
+c) Level access housing","Disabled access needs
+
+f) Other disabled access needs","Disabled access needs
+
+g) No disabled access needs","Disabled access needs
+
+h) Don’t know",Does anybody in the household have a physical or mental health condition (or other illness) expected to last 12 months or more?,Does this person's condition affect their dexterity?,Does this person's condition affect their learning or understanding or concentrating?,Does this person's condition affect their hearing?,Does this person's condition affect their memory?,Does this person's condition affect their mental health?,Does this person's condition affect their mobility?,Does this person's condition affect them socially or behaviourally?,Does this person's condition affect their stamina or breathing or fatigue?,Does this person's condition affect their vision?,Does this person's condition affect them in another way?,How long has the household continuously lived in the local authority area of the new letting?,How long has the household been on the local authority waiting list for the new letting?,What is the tenant’s main reason for the household leaving their last settled home?,"If 'Other', what was the main reason for leaving their last settled home?",Where was the household immediately before this letting?,Did the household experience homelessness immediately before this letting?,Do you know the postcode of the household's last settled home?,Part 1 of postcode of last settled home,Part 2 of postcode of last settled home,What is the local authority of the household's last settled home?,Was the household given 'reasonable preference' by the local authority?,"Reasonable preference reason
+
+They were homeless or about to lose their home (within 56 days)","Reasonable preference reason
+
+They were living in unsanitary, overcrowded or unsatisfactory housing","Reasonable preference reason
+
+They needed to move due to medical and welfare reasons (including disability)","Reasonable preference reason
+
+They needed to move to avoid hardship to themselves or others","Reasonable preference reason
+
+Don't know",Was the letting made under Choice-Based Lettings (CBL)?,Was the letting made under the Common Allocation Policy (CAP)? ,Was the letting made under the Common Housing Register (CHR)?,What was the source of referral for this letting?,Do you know the household's combined total income after tax?,How often does the household receive income?,How much income does the household have in total?,Is the tenant likely to be receiving any of these housing-related benefits?,"How much of the household's income is from Universal Credit, state pensions or benefits?",Does the household pay rent or other charges for the accommodation?,How often does the household pay rent and other charges?,"If this is a care home, how much does the household pay every [time period]?",What is the basic rent?,What is the service charge?,What is the personal service charge?,What is the support charge?,Total charge,"After the household has received any housing-related benefits, will they still need to pay for rent and charges?",What do you expect the outstanding amount to be?
+Additional info,Organisation's CORE ID,,"If using new core then this will be the email address. If left empty, the letting log will be assigned to the account used to upload the log.","General needs housing includes both self-contained and shared housing without support or specific adaptations. Supported housing includes direct access hostels, group homes, residential care and nursing homes.",,This is a letting to the same tenant in the same property,,,,,,,This is how you usually refer to this tenancy on your own systems,This is how you usually refer to this property on your own systems,Provide management code if you have one,"Provide scheme code, include the 'S' at the beginning if it has one",Provide location code if you have one,"The UPRN is the Unique Property Reference Number. It's created by the Ordnance Survey so it's a unique number system used across all housing providers, all sectors (i.e. not just social housing) and all across the UK.",,,,,,,,,,"Do not include the offer that led to this letting.
+This is after the last tenancy ended. If the property is being offered for let for the first time, enter 0.",,,,"If shared accommodation, enter the number of bedrooms occupied by this household. A bedsit has 1 bedroom.","This is the date the property became available to let. For a re-let, it's the day after the previous tenant's contract ends. For a new build, conversion, acquisition or lease it's the day after the provider legally took over possession or management.",,,"Major repairs are works that could not be reasonably carried out with a tenant living at the property. For example, structural repairs.",,,,Also known as an ‘introductory period’,"Fixed-term tenancies are for a set time (up to 20 years). Licence agreements are on a rolling basis, mainly for supported housing. Local authorities mostly provide secure tenancies, and housing associations mostly provide assured (ASTs).",,Not including starter or introductory period,,Make sure the tenant has seen the attached privacy notice before completing this log,"This is the household member who does the most paid work. If several people do the same paid work, it's the oldest household member.",,,,,A child is anyone eligible for child benefit: under age 16 or under 20 if still in full-time education.,Mark children under 1 as 1.,,,A child is anyone eligible for child benefit: under age 16 or under 20 if still in full-time education.,Mark children under 1 as 1.,,,A child is anyone eligible for child benefit: under age 16 or under 20 if still in full-time education.,Mark children under 1 as 1.,,,A child is anyone eligible for child benefit: under age 16 or under 20 if still in full-time education.,Mark children under 1 as 1.,,,A child is anyone eligible for child benefit: under age 16 or under 20 if still in full-time education.,Mark children under 1 as 1.,,,A child is anyone eligible for child benefit: under age 16 or under 20 if still in full-time education.,Mark children under 1 as 1.,,,A child is anyone eligible for child benefit: under age 16 or under 20 if still in full-time education.,Mark children under 1 as 1.,,,"Excluding national service.
+If several household members have these links, answer for regular first. If no regular, answer for reserve. If no reserve, answer for spouses or civil partners.",,,,,,,,,,,"For example, lifting and carrying objects, or using a keyboard",,"For example, deafness or partial hearing",,"For example, depression or anxiety",,"Anything associated with autism spectrum disorder (ASD), including Asperger’s or attention deficit hyperactivity disorder (ADHD)",,"For example, blindness or partial sight",,,,"‘Last settled home' means last long-standing home. For tenants who had temporary accommodation or slept rough, it's where they lived previously.",,,,,,,,Social housing 'reasonable preference' is also known as 'priority need',,,,,,,,,,,,"Include any income after tax from employment, pensions, and Universal Credit. Don't include National Insurance (NI) contributions and tax, housing benefit, child benefit, or council tax support.",,,"If rent is charged on the property then answer Yes, even if tenants do not pay it themselves.",,,"Amount paid before any service charges, for example hot water or cleaning. Households may get household benefits towards basic rent.","For example, cleaning. Households may get household benefits towards service charge",For example heating or hot water. This doesn’t include housing benefit or Universal Credit.,Any support service charges included in the tenancy agreement,Unnecessary for new CORE users,,Approximate figure only
+Values,Numeric,,"Email format if using new CORE.
+Alphanumeric, except for commas if using old CORE.",1 - 2,1 - 12,1 - 2,1 - 31,1 - 12,23 - 24,1 - 3,,Text,"Alphanumeric, max 13 characters","Alphanumeric, max 12 characters",1 - 999,Alphanumeric,1 - 999,Numeric,Alphanumeric,,Text,,XX(XX),XXX,"ONS CODE: Alphanumeric, 9 characters beginning with 'E'",1 - 3 or 5 - 8,"5 - 6, or 8 - 20",0+,"1 - 2, 4 or 6 - 10",1 - 2,,1 - 7,1 - 31,1 - 12,20 - 24,1 - 31,1 - 12,14 - 24,1 - 3,1 - 2,2 - 7,Text,1 - 99,1 - 5,1,16 - 120 or R,"F, M, X or R",1 - 19,"12 - 13, 17 - 21",0 - 10,"P, C, X or R","Numeric, range 1 - 120 or text (upper case 'R')
+Must be >= 16 if working situation = 1 - 8 or 0
+Must be <16 if working situation = 9","F, M, X or R",0 - 10,"P, C, X or R","Numeric, range 1 - 120 or text (upper case 'R')
+Must be >= 16 if working situation = 1 - 8 or 0
+Must be <16 if working situation = 9","F, M, X or R",0 - 10,"P, C, X or R","Numeric, range 1 - 120 or text (upper case 'R')
+Must be >= 16 if working situation = 1 - 8 or 0
+Must be <16 if working situation = 9","F, M, X or R",0 - 10,"P, C, X or R","Numeric, range 1 - 120 or text (upper case 'R')
+Must be >= 16 if working situation = 1 - 8 or 0
+Must be <16 if working situation = 9","F, M, X or R",0 - 10,"P, C, X or R","Numeric, range 1 - 120 or text (upper case 'R')
+Must be >= 16 if working situation = 1 - 8 or 0
+Must be <16 if working situation = 9","F, M, X or R",0 - 10,"P, C, X or R","Numeric, range 1 - 120 or text (upper case 'R')
+Must be >= 16 if working situation = 1 - 8 or 0
+Must be <16 if working situation = 9","F, M, X or R",0 - 10,"P, C, X or R","Numeric, range 1 - 120 or text (upper case 'R')
+Must be >= 16 if working situation = 1 - 8 or 0
+Must be <16 if working situation = 9","F, M, X or R",0 - 10,1 - 6,3 - 6,1 - 3,,1 or empty,,,,,,1 - 3,1 or empty,,,,,,,,,,1 - 2 or 5 - 10,2 or 5 - 10,"1 - 2, 4, 8 - 14, 16 - 20, 28 - 31 or 34 - 49",Text,"3 - 4, 6 - 7, 9 - 10, 13 - 14, 18 - 19, 21 or 23 - 37",1 or 11,1 - 2,XX(XX),XXX,"ONS CODE: Alphanumeric, 9 characters beginning with 'E'",1 - 3,1 or empty,,,,,1 - 2,,,"1 - 4, 7 - 10, 12 - 17",1 - 3,,0 - 99999,"1, 3, 6, 9 or 10",1 - 4,0 - 1,1 - 10,xxxx.xx,,,,,,1 - 3,xxxx.xx
+Can be empty?,No,,Yes,No,,,No,,,"Yes, if letting type is not an Affordable Rent letting (if field 5 = 1 - 4 or 9 - 12)","Yes, if letting type is not an Intermediate Rent letting (if field 5 = 1 - 8)","Yes, if letting type is not an Intermediate Rent letting (if field 5 = 1 - 8) or if 'Other intermediate rent product' is not selected for type of Intermediate Rent (if field 11 is not 3)",Yes,,"Yes, if letting is general needs (if field 4 = 1) or if location code is provided (if field 17 is not empty)","Yes, if letting is general needs (if field 4 = 1)","Yes, if letting is general needs (if field 4 = 1) or if management code is provided (if field 15 is not empty)","Yes, if letting is supported housing (if field 4 = 2) or if the property's postcode is not empty (if fields 23 and 24 contain full and valid entries)","Yes, if letting is supported housing (if field 4 = 2) or if property's UPRN and local authority are known (if fields 18 and 25 are not empty)",Yes,"Yes, if letting is supported housing (if field 4 = 2) or if property's UPRN and local authority are known (if fields 18 and 25 are not empty)",Yes,"Yes, if letting is supported housing (if field 4 = 2) or if property's UPRN and local authority are known (if fields 18 and 25 are not empty)",,"Yes, if letting is supported housing (if field 4 = 2)","Yes, if letting is a renewal (if field 6 = 1) or a first-time let (if field 27 = 15 - 17)","Yes, if letting is a renewal (if field 6 = 1)",,"Yes, if letting is supported housing (if field 4 = 2)",,,,"Yes, if letting is a renewal (if field 6 = 1)",,,Yes,,,No,,,"Yes, if 'Other' is not selected for tenancy type (if field 41 is not 3)","Yes, if letting is not a fixed-term tenancy (if field 41 = 2, 3, 5 or 7)","Yes, if letting is general needs (if field 4 = 1)",No,No,,,,,"Yes, if the other fields about this person (fields 52, 53 and 54) are also empty","Yes, if the other fields about this person (fields 51, 53 and 54) are also empty","Yes, if the other fields about this person (fields 51, 52 and 54) are also empty","Yes, if the other fields about this person (fields 51, 52 and 53) are also empty","Yes, if the other fields about this person (fields 56, 57 and 58) are also empty","Yes, if the other fields about this person (fields 55, 57 and 58) are also empty","Yes, if the other fields about this person (fields 55, 56 and 58) are also empty","Yes, if the other fields about this person (fields 55, 56 and 57) are also empty","Yes, if the other fields about this person (fields 60, 61 and 62) are also empty","Yes, if the other fields about this person (fields 59, 61 and 62) are also empty","Yes, if the other fields about this person (fields 59, 60 and 62) are also empty","Yes, if the other fields about this person (fields 59, 60 and 61) are also empty","Yes, if the other fields about this person (fields 64, 65 and 66) are also empty","Yes, if the other fields about this person (fields 63, 65 and 66) are also empty","Yes, if the other fields about this person (fields 63, 64 and 66) are also empty","Yes, if the other fields about this person (fields 63, 64 and 65) are also empty","Yes, if the other fields about this person (fields 68, 69 and 70) are also empty","Yes, if the other fields about this person (fields 67, 69 and 70) are also empty","Yes, if the other fields about this person (fields 67, 68 and 70) are also empty","Yes, if the other fields about this person (fields 67, 68 and 69) are also empty","Yes, if the other fields about this person (fields 72, 73 and 74) are also empty","Yes, if the other fields about this person (fields 71, 73 and 74) are also empty","Yes, if the other fields about this person (fields 71, 72 and 74) are also empty","Yes, if the other fields about this person (fields 71, 72 and 73) are also empty","Yes, if the other fields about this person (fields 76, 77 and 78) are also empty","Yes, if the other fields about this person (fields 75, 77 and 78) are also empty","Yes, if the other fields about this person (fields 75, 76 and 78) are also empty","Yes, if the other fields about this person (fields 75, 76 and 77) are also empty",No,"Yes, if no one in the household is a current or former regular (if field 79 is not 1)","Yes, if no one in the household is a current or former regular or reserve (if field 79 = 2, 3, 5 or 6)",No,"Yes, if no household members have access needs or if it is unknown (if field 87 or 88 = 1)",,,,"Yes, if a household member has an access need (if at least one of fields 83, 84, 85 and 86 = 1)",,No,"Yes, if no one in the household has a physical or mental health condition (if field 89 is 2 or 3).
+If someone in the household does have such a condition (if field 89 = 1), then at least 1 of these fields must be 1.",,,,,,,,,,No,"Yes, if letting is a renewal (if field 6 = 1)",No,"Yes, if 'Other' is not selected for reason for leaving last settled home (if field 102 is not 20)","Yes, if letting is a renewal (if field 6 = 1)",No,,"Yes, if postcode of household's last settled home is not known (if 106 = 2)",,Yes,No,"If household was given 'reasonable preference' (if field 110 = 1), at least one of these fields must be 1.
+If household was not given 'reasonable preference' (if field 110 = 2 or 3), these fields will be ignored.",,,,,No,,,"Yes, if letting is a renewal (if field 6 = 1)",No,"Yes, if household's income is unknown (if field 120 = 2 or 3)",,No,,"Yes, if all rent and accommodation charges are provided (if fields 127 - 132 are not empty)",No,"Yes, if accommodation is not a care home (if field 125 = 1) and all rent and accommodation charges are provided (if fields 128 - 132 are not empty)","Yes, if the household does not pay rent (if field 125 = 1) or if the accommodation is not a care home (if field 127 is empty)",Yes,,,"Yes, if submitting data to new CORE, if the household does not pay rent (if field 125 = 1) or if the accommodation is not a care home (if field 127 is empty)","Yes, if the household doesn't receive housing benefits, or if it is unknown (if field 123 = 3, 9 or 10)","Yes, if the household does not need to pay rent/charges after receiving housing benefits (if field133 = 2 or 3)"
+Type of letting the question applies to,,,,,,,,,,Affordable Rent only,Intermediate Rent only,,,,Supported housing only,,,General needs only,,,,,,,,,,,General needs only,,,,,,,,,,,,,,,Supported housing only,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Supported housing only,,,,,,,,,
+Duplicate check field?,Yes,,,,,,Yes,,,,,,Yes,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Yes,,,,Yes,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
+Bulk upload 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
+,3,3,,1,1,1,10,10,23,,2,,54,,,,,,123,123,123,123,A1,1AA,E07000004,1,6,0,1,1,1,1,1,10,22,1,1,22,1,1,1,,1,1,1,20,M,3,4,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,2,0,,1,,,,,,,,,,,,,,24,,,4,40,1,2,,,EC1N,2TD,,,3,,3,,,,,2,,,,,,,,,,,,,,1,2,2
diff --git a/spec/fixtures/files/2023_24_lettings_bulk_upload.xlsx b/spec/fixtures/files/2023_24_lettings_bulk_upload.xlsx
new file mode 100644
index 000000000..dedd1f468
Binary files /dev/null and b/spec/fixtures/files/2023_24_lettings_bulk_upload.xlsx differ
diff --git a/spec/fixtures/files/2022_23_lettings_bulk_upload_empty_with_headers.csv b/spec/fixtures/files/2023_24_lettings_bulk_upload_invalid.csv
similarity index 98%
rename from spec/fixtures/files/2022_23_lettings_bulk_upload_empty_with_headers.csv
rename to spec/fixtures/files/2023_24_lettings_bulk_upload_invalid.csv
index 7628c034f..8c36c5884 100644
--- a/spec/fixtures/files/2022_23_lettings_bulk_upload_empty_with_headers.csv
+++ b/spec/fixtures/files/2023_24_lettings_bulk_upload_invalid.csv
@@ -47,3 +47,4 @@ If household was not given 'reasonable preference' (if field 110 = 2 or 3), thes
Type of letting the question applies to,,,,,,,,,,Affordable Rent only,Intermediate Rent only,,,,Supported housing only,,,General needs only,,,,,,,,,,,General needs only,,,,,,,,,,,,,,,Supported housing only,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Supported housing only,,,,,,,,,
Duplicate check field?,Yes,,,,,,Yes,,,,,,Yes,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Yes,,,,Yes,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
Bulk upload 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
+,3,3,,1,1,1,,10,23,,2,,123,,,,,,123,123,123,123,A1,1AA,E07000004,1,6,0,1,1,1,1,1,10,22,1,1,22,1,1,1,,1,1,1,20,M,3,4,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1,2,0,,1,,,,,,,,,,,,,,24,,,4,40,1,2,,,EC1N,2TD,,,3,,3,,,,,2,,,,,,,,,,,,,,1,2,2
diff --git a/spec/fixtures/files/2023_24_sales_bulk_upload.csv b/spec/fixtures/files/2023_24_sales_bulk_upload.csv
new file mode 100644
index 000000000..ae39051b6
--- /dev/null
+++ b/spec/fixtures/files/2023_24_sales_bulk_upload.csv
@@ -0,0 +1,12 @@
+Section,Setting up this sales log,,,,,,,,,,,,,,,Property information,,,,,,,,,,,,,,Household characteristics,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Household situation,,,,,,,,,,,Other household information,,,,,"Income, benefits and outgoings",,,,,,,,Sale information - Shared ownership,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Sale information - Discounted ownership,,,,,,,,,,,,Sale information - Outright sale,,,,,,,,
+Question,Which organisation owned this property before the sale?,What is the CORE username of the account this sale log should be assigned to? ,What is the sale completion date? - day DD,What is the sale completion date? - month MM,What is the sale completion date? - year YY,What is the purchaser code?,Was this purchase made through an ownership scheme?,What is the type of shared ownership sale?,What is the type of discounted ownership sale?,What is the type of outright sale?,"If 'Other', what is the type of outright sale?",Is the buyer a company?,Will the buyer(s) live in the property?,Is this a joint purchase?,Are there more than 2 joint buyers of this property?,How many bedrooms does the property have?,What type of unit is the property?,Which type of building is the property?,"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?,Is the property built or adapted to wheelchair-user standards?,Did you interview the buyer to answer these questions?,Has the buyer seen the DLUHC privacy notice?,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?,"If ""Any other country"", what is buyer 1's nationality?",Which of these best describes buyer 1’s working situation? ,Will buyer 1 live in the property?,What is buyer 2 or person 2's relationship to 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?,"If ""Any other country"", 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?",What is person 3's relationship to 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? ,What is person 4's relationship to 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? ,What is person 5's relationship to 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? ,What is person 6's relationship to 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?,Was the buyer registered with their PRP (HA)?,Was the buyer registered with another PRP (HA)?,Was the buyer registered with the local authority?,Was the buyer registered with a Help to Buy agent?,"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?,How long did the buyer(s) live in the property before purchasing it?,Is this a staircasing transaction?,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 a resale?,What is the day of the exchange of contracts date? - DD,What is the month of the exchange of contracts date? - MM,What is the year of the exchange of contracts date? - YY,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,Was the household rehoused under a ‘local authority nominations agreement’?,"Was the buyer a private registered provider, housing association or local authority tenant immediately before the sale?",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 equity stake purchased?,Was a mortgage used to buy this property?,What is the mortgage amount?,What is the name of the mortgage lender?,"If 'Other', what is the name of the mortgage lender?",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?,How much cash discount was given through Social HomeBuy?,What is the basic monthly rent?,What are the total monthly leasehold charges for the property?,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 name of the mortgage lender?,"If 'Other', what is the name mortgage lender?",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 is the full purchase price?,Was a mortgage used to buy this property?,What is the mortgage amount?,What is the name of the mortgage lender?,"If 'Other', what is the name of the mortgage lender?",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?
+Additional info,Provide the owning organisation's CORE ID code,"If using new CORE then this will be the email address. If left empty, the sale log will be assigned to the account used to upload the log. ",,,,,A shared ownership sale is when the purchaser buys up to 75% of the property value and pays rent to the Private Registered Provider (PRP) on the remaining portion.,,,,,,,,,"For bedsits, enter ‘1’",,,"The UPRN is the Unique Property Reference Number. It's created by the Ordnance Survey so it's a unique number system used across all housing providers, all sectors (i.e. not just social housing) and all across the UK.",,,,,,,,,You should still try to answer all questions even if the buyer wasn't interviewed in person,You should show the privacy notice to the buyer before completing this log,,,,,,,,,,,,,,,,"You can provide details for a maximum of 4 other people if there are 2 buyers, or 5 other people if there is only one buyer",,,,,,,,,,,,,,,,,,This is also known as the household's 'last settled home',,,,,,,,,,"A regular is somebody who has served in the Royal Navy, the Royal Marines, the Royal Air Force or army full time and does not include reserve forces",,,This includes any long-term health condition that has an impact on the person's day-to-day life,This can be inside or outside the home,"Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments",,"Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments",,,Enter their total savings to the nearest £10,,For any buyer,"If the buyers haven't been living in the property, enter '0'","A staircasing transaction is when the household purchases more shares in their property, increasing the proportion they own and decreasing the proportion the housing association owns. Once the household purchases 100% of the shares, they own the property.",,,,"If the social landlord has previously sold the property to another buyer and is now reselling the property, enter 'yes'. If this is the first time the property has been sold, enter 'no'.","This is the date on which the sale becomes legally binding with an exchange of signed contracts between the vendor and the buyer. Where properties are sold 'off plan' (such as while the property is still being built), the exchange of contracts date may happen before the practical completion or handover date.",,,This is the date on which the building contractor hands over responsibility for the completed property to the private registered provider (PRP).,,,A local authority nominations agreement is a written agreement between a local authority and private registered provider (PRP) that some or all of its sales vacancies are offered to local authorities for rehousing.,,"For bedsits, enter ‘1’",,,"Enter the full purchase price of the property before any discounts are applied. For shared ownership, enter the full purchase price paid for 100% equity (this is equal to the value of the share owned by the PRP plus the value bought by the purchaser).","Enter the amount of initial equity held by the purchaser (for example, 25% or 50%)",,,,,,This is 'Yes' if the mortgage includes borrowing beyond the purchase price of the property,Enter the total cash sum paid by the buyer towards the property that was not funded by the mortgage,Enter the total cash discount given on the property being purchased through the Social HomeBuy scheme,Before any charges,"For example, service and management charges",,"For all schemes, including Right to Acquire (RTA), Right to Buy (RTB), Voluntary Right to Buy (VRTB) or Preserved Right to Buy (PRTB) sales, enter the full price of the property without any discount","For all schemes except Right to Buy (RTB), Preserved Right to Buy (PRTB), Voluntary Right to Buy (VRTB) and Rent to Buy","For Right to Buy (RTB), Preserved Right to Buy (PRTB) and Voluntary Right to Buy (VRTB). For capped discount, enter capped %. If property is sold to an existing tenant under RTB, PRTB or VRTB, enter % discount from full market value.",,,,,,This is 'Yes' if the mortgage includes borrowing beyond the purchase price of the property,Enter the total cash sum paid by the purchaser towards the property that was not funded by the mortgage. This excludes any grant or loan.,"For example, service and management charges",,,,,,,This is 'Yes' if the mortgage includes borrowing beyond the purchase price of the property,Enter the total cash sum paid by the purchaser towards the property that was not funded by the mortgage. This excludes any grant or loan,"For example, service and management charges"
+Values,Numeric,"Email format if using new CORE.
+Alphanumeric, except for commas if using old CORE.",Jan-31,01-Dec,20 - 24,"Alphanumeric, max 9 characters",01-Mar,"2, 16, 18, 24, 28 or 30 - 32","8, 9, 14, 21, 22, 27 or 29",10 or 12,Alphanumeric,01-Feb,,,01-Mar,01-Sep,1 - 4 or 9,01-Feb,"Numeric, max 12 digits",Alphanumeric,,Text,,XX(XX),XXX,"ONS CODE: Alphanumeric, 9 characters beginning with 'E'",01-Mar,01-Feb,1,"16 - 110
+ or R","F, M, X or R",Jan-19,"12 - 13, 17 - 19",Text,0 - 8 or 10,01-Feb,"P, C, X or R",1 - 110 or R,"F, M, X or R",Jan-19,"12 - 13, 17 - 19",Text,0 - 10,01-Feb,0 - 5,"P, C, X or R",1 - 110 or R,"F, M, X or R",0 - 10,"P, C, X or R",1 - 110 or R,"F, M, X or R",0 - 10,"P, C, X or R",1 - 110 or R,"F, M, X or R",0 - 10,"P, C, X or R",1 - 110 or R,"F, M, X or R",0 - 10,"1 - 7, 9 or R",01-Feb,XX(XX),XXX,"ONS CODE: Alphanumeric, 9 characters beginning with 'E'",1 or empty,,,,01-Mar,"1 - 7, 9 or R","1, 3, 7 or 8",04-Jun,04-Jul,01-Mar,,0 - 99999 or R,01-Feb,0 - 99999 or R,01-Feb,01-Apr,0 - 999990 or R,01-Mar,,Integer <= 80,01-Mar,1 - 100,,01-Mar,01-Feb,Jan-31,01-Dec,22 - 24,Jan-31,01-Dec,22 - 24,01-Mar,01-Feb,01-Sep,1 - 4 or 9,1 - 3 or 9 - 10,0 - 999999,0 - 100,01-Feb,0 - 999999,Jan-40,Alphanumeric,Integer <= 60,01-Mar,0 - 999999,,0 - 9999.99,,Integer <= 80,0 - 999999,,0 - 100,01-Feb,0 - 999999,Jan-40,Alphanumeric,Integer <= 60,01-Mar,0 - 999999,0 - 999.99,0 - 999999,01-Feb,0 - 999999,Jan-40,Alphanumeric,Integer <= 60,01-Mar,0 - 999999,0 - 9999.99
+Can be empty?,No,Yes,No,,,Yes,No,"Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3)","Yes, if the purchase was not made through a discounted ownership scheme (if field 7 = 1 or 3)","Yes, if the purchase was not an outright sale (if field 7 = 1 or 2)","Yes, if 'Other' is not selected for type of outright sale (if field 10 is not 12)","Yes, if the purchase was not an outright sale (if field 7 = 1 or 2)",,No,"Yes, if the sale is not a joint purchase (if field 14 = 2)",No,,,"Yes, if property's full address is known (if fields 20, 22, and 24 - 25 are not empty)","Yes, if property's UPRN is known (if field 19 is not empty)",Yes,"Yes, if property's UPRN is known (if field 19 is not empty)",Yes,"Yes, if property's UPRN and local authority are known (if fields 19 and 26 are not empty",,No,,Yes,No,"Yes, if buyer is a company (if field 12 = 1)",,,,"Yes, if buyer is a company (if field 12 = 1) or if ""Any other country"" is not selected for buyer 1's nationality (if field 33 is not 12)","Yes, if buyer is a company (if field 12 = 1)",,"Yes, if buyer is a company (if field 12 = 1), or if sale is not a joint purchase (if field 14 = 2) and the other fields about this person (fields 38, 39 and 43) are also empty","Yes, if buyer is a company (if field 12 = 1), or if sale is not a joint purchase (if field 14 = 2) and the other fields about this person (fields 37, 39 and 43) are also empty","Yes, if buyer is a company (if field 12 = 1), or if sale is not a joint purchase (if field 14 = 2) and the other fields about this person (fields 37, 38 and 43) are also empty","Yes, if buyer is a company (if field 12 = 1) or if sale is not a joint purchase (if field 14 = 2)",,"Yes, if buyer is a company (if field 12 = 1) or if ""Any other country"" is not selected for buyer 2's nationality (if field 41 is not 12)","Yes, if buyer is a company (if field 12 = 1), or if sale is not a joint purchase (if field 14 = 2) and the other fields about this person (fields 37, 38 and 39) are also empty","Yes, if buyer is a company (if field 12 = 1) or if sale is not a joint purchase (if field 14 = 2)","Yes, if buyer is a company (if field 12 = 1)","Yes, if the other fields about this person (fields 47, 48 and 49) are also empty","Yes, if the other fields about this person (fields 46, 48 and 49) are also empty","Yes, if the other fields about this person (fields 46, 47 and 49) are also empty","Yes, if the other fields about this person (fields 46, 47 and 48) are also empty","Yes, if the other fields about this person (fields 51, 52 and 53) are also empty","Yes, if the other fields about this person (fields 50, 52 and 53) are also empty","Yes, if the other fields about this person (fields 50, 51 and 53) are also empty","Yes, if the other fields about this person (fields 50, 51 and 52) are also empty","Yes, if the other fields about this person (fields 55, 56 and 57) are also empty","Yes, if the other fields about this person (fields 54, 56 and 57) are also empty","Yes, if the other fields about this person (fields 54, 55 and 57) are also empty","Yes, if the other fields about this person (fields 54, 55 and 56) are also empty","Yes, if the other fields about this person (fields 59, 60 and 61) are also empty","Yes, if the other fields about this person (fields 58, 60 and 61) are also empty","Yes, if the other fields about this person (fields 58, 59 and 61) are also empty","Yes, if the other fields about this person (fields 58, 59 and 60) are also empty",No,,"Yes, if postcode of buyer 1's last settled accommodation is unknown (if field 63 = 2)",,Yes,Yes,,,,"Yes, if sale is not a joint purchase (if field 14 = 2)","Yes, if sale is not a joint purchase (if field 14 = 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 71 = 1 or 3)",No,"Yes, if none of the buyers are known to have served as a regular in the UK armed forces (if field 73 = 7, 3 or 8)",No,,,No,"Yes, if buyer 1's income is not known (if field 78 = R)","Yes, if sale is not a joint purchase (if field 14 = 2)","Yes, if sale is not a joint purchase (if field 14 = 2) or if buyer 2's income is not known (if field 80 = R)",No,,,"Yes, if the purchasers did not previously own a property or if it is not known (if field 84 = 2 or 3)","Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3)",,"Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3) or if this is not known to be a staircasing transaction (if field 87 = 2 or 3)",,,"Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3) or if this is a staircasing transaction (if field 87 = 1)","Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3), if this is a resale (if field 91 = 1) or if this is a staircasing transaction (if field 87 = 1)",,,,,,"Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3)",,"Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3) or if the buyer was not a private registered provider, housing association or local authority tenant immediately before sale (if field 99 = 2)",,,"Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3)",,,"Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3) or if a mortgage was not used (if field 105 = 2)",,"Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3), if a mortgage was not used (if field 105 = 2) or if 'Other' is not selected for mortgage lender name (if field 107 is not 40)","Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3) or if a mortgage was not used (if field 105 = 2)",,"Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3)","Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3) or the type of shared ownership sale is not Social Homebuy (if field 8 is not 18)","Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3)",,"Yes, if the purchase was not made through a discounted ownership scheme (if field 7 = 1 or 3)",,"Yes, if the purchase was not made through a discounted ownership scheme (if field 7 = 1 or 3) or if the type of discounted sale is PRTB, VRTB, RTB, or Rent to Buy (if field 9 is null, 9, 14, 27 or 29)","Yes, if the purchase was not made through a discounted ownership scheme (if field 7 = 1 or 3 or if the type of discounted sale is not PRTB, VRTB, or RTB (if field 9 is null, 8, 29, 21, 22)","Yes, if the purchase was not made through a discounted ownership scheme (if field 7 = 1 or 3)","Yes, if the purchase was not made through a discounted ownership scheme (if field 7 = 1 or 3) or if a mortgage was not used (if field 119 = 2)",,"Yes, if the purchase was not made through a discounted ownership scheme (if field 7 = 1 or 3), if a mortgage was not used (if field 119 = 2) or if 'Other' is not selected for mortgage lender name (if field 121 is not 40)","Yes, if the purchase was not made through a discounted ownership scheme (if field 7 = 1 or 3) or if a mortgage was not used (if field 119 = 2)",,"Yes, if the purchase was not made through a discounted ownership scheme (if field 7 = 1 or 3)",,"Yes, if the purchase was not an outright sale (if field 7 = 1 or 2)",,"Yes, if the purchase was not an outright sale (if field 7 = 1 or 2) or if a mortgage was not used (if field 128 = 2)",,"Yes, if the purchase was not an outright sale (if field 7 = 1 or 2), if a mortgage was not used (if field 128 = 2), or if 'Other' is not selected for mortgage lender name (if field 130 is not 40)","Yes, if the purchase was not an outright sale (if field 7 = 1 or 2) or if a mortgage was not used (if field 128 = 2)",,"Yes, if the purchase was not an outright sale (if field 7 = 1 or 2)",
+Types of sales the question applies to,,,,,,,,Shared ownership only,Discounted ownership only,Outright sale only,,,,,Joint purchase only,,,,,,,,,,,,,,,,,,,,,,,,,Joint purchase only,,,,Joint purchase only,,,,,,,,,,,,,,,,,,,,,,,,,,,Joint purchase only,,,,,,,,,Joint purchase only,,,,,Shared ownership only,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Discounted ownership only,,,,,,,,,,,,Outright sale only,,,,,,,,
+Duplicate check field?,Yes,,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
+,123,,2,10,23,23 test BU,1,2,,,,,M,1,2,1,1,1,,123,,123,,A1,1AA,E07000223,1,1,1,32,X,1,12,1,1,1,P,33,X,1,12,1,1,1,0,,,,,,,,,,,,,,,,,1,3,A1,1AA,1,1,250000,25,42500,3,21000,8,800,7,1,1,,1,,1,1,,1,1,,1,10,10,1,,123,,,3,,5,1,1,1,1,1,200000,25,1,200000,1,,2,1,50000,1,1,,1,1,1,1,0,10,10,1,1,,,,,,,,,,,,
diff --git a/spec/fixtures/files/2023_24_sales_bulk_upload_invalid.csv b/spec/fixtures/files/2023_24_sales_bulk_upload_invalid.csv
new file mode 100644
index 000000000..b8defe9e9
--- /dev/null
+++ b/spec/fixtures/files/2023_24_sales_bulk_upload_invalid.csv
@@ -0,0 +1,12 @@
+Section,Setting up this sales log,,,,,,,,,,,,,,,Property information,,,,,,,,,,,,,,Household characteristics,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Household situation,,,,,,,,,,,Other household information,,,,,"Income, benefits and outgoings",,,,,,,,Sale information - Shared ownership,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Sale information - Discounted ownership,,,,,,,,,,,,Sale information - Outright sale,,,,,,,,
+Question,Which organisation owned this property before the sale?,What is the CORE username of the account this sale log should be assigned to? ,What is the sale completion date? - day DD,What is the sale completion date? - month MM,What is the sale completion date? - year YY,What is the purchaser code?,Was this purchase made through an ownership scheme?,What is the type of shared ownership sale?,What is the type of discounted ownership sale?,What is the type of outright sale?,"If 'Other', what is the type of outright sale?",Is the buyer a company?,Will the buyer(s) live in the property?,Is this a joint purchase?,Are there more than 2 joint buyers of this property?,How many bedrooms does the property have?,What type of unit is the property?,Which type of building is the property?,"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?,Is the property built or adapted to wheelchair-user standards?,Did you interview the buyer to answer these questions?,Has the buyer seen the DLUHC privacy notice?,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?,"If ""Any other country"", what is buyer 1's nationality?",Which of these best describes buyer 1’s working situation? ,Will buyer 1 live in the property?,What is buyer 2 or person 2's relationship to 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?,"If ""Any other country"", 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?",What is person 3's relationship to 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? ,What is person 4's relationship to 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? ,What is person 5's relationship to 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? ,What is person 6's relationship to 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?,Was the buyer registered with their PRP (HA)?,Was the buyer registered with another PRP (HA)?,Was the buyer registered with the local authority?,Was the buyer registered with a Help to Buy agent?,"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?,How long did the buyer(s) live in the property before purchasing it?,Is this a staircasing transaction?,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 a resale?,What is the day of the exchange of contracts date? - DD,What is the month of the exchange of contracts date? - MM,What is the year of the exchange of contracts date? - YY,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,Was the household rehoused under a ‘local authority nominations agreement’?,"Was the buyer a private registered provider, housing association or local authority tenant immediately before the sale?",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 equity stake purchased?,Was a mortgage used to buy this property?,What is the mortgage amount?,What is the name of the mortgage lender?,"If 'Other', what is the name of the mortgage lender?",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?,How much cash discount was given through Social HomeBuy?,What is the basic monthly rent?,What are the total monthly leasehold charges for the property?,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 name of the mortgage lender?,"If 'Other', what is the name mortgage lender?",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 is the full purchase price?,Was a mortgage used to buy this property?,What is the mortgage amount?,What is the name of the mortgage lender?,"If 'Other', what is the name of the mortgage lender?",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?
+Additional info,Provide the owning organisation's CORE ID code,"If using new CORE then this will be the email address. If left empty, the sale log will be assigned to the account used to upload the log. ",,,,,A shared ownership sale is when the purchaser buys up to 75% of the property value and pays rent to the Private Registered Provider (PRP) on the remaining portion.,,,,,,,,,"For bedsits, enter ‘1’",,,"The UPRN is the Unique Property Reference Number. It's created by the Ordnance Survey so it's a unique number system used across all housing providers, all sectors (i.e. not just social housing) and all across the UK.",,,,,,,,,You should still try to answer all questions even if the buyer wasn't interviewed in person,You should show the privacy notice to the buyer before completing this log,,,,,,,,,,,,,,,,"You can provide details for a maximum of 4 other people if there are 2 buyers, or 5 other people if there is only one buyer",,,,,,,,,,,,,,,,,,This is also known as the household's 'last settled home',,,,,,,,,,"A regular is somebody who has served in the Royal Navy, the Royal Marines, the Royal Air Force or army full time and does not include reserve forces",,,This includes any long-term health condition that has an impact on the person's day-to-day life,This can be inside or outside the home,"Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments",,"Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments",,,Enter their total savings to the nearest £10,,For any buyer,"If the buyers haven't been living in the property, enter '0'","A staircasing transaction is when the household purchases more shares in their property, increasing the proportion they own and decreasing the proportion the housing association owns. Once the household purchases 100% of the shares, they own the property.",,,,"If the social landlord has previously sold the property to another buyer and is now reselling the property, enter 'yes'. If this is the first time the property has been sold, enter 'no'.","This is the date on which the sale becomes legally binding with an exchange of signed contracts between the vendor and the buyer. Where properties are sold 'off plan' (such as while the property is still being built), the exchange of contracts date may happen before the practical completion or handover date.",,,This is the date on which the building contractor hands over responsibility for the completed property to the private registered provider (PRP).,,,A local authority nominations agreement is a written agreement between a local authority and private registered provider (PRP) that some or all of its sales vacancies are offered to local authorities for rehousing.,,"For bedsits, enter ‘1’",,,"Enter the full purchase price of the property before any discounts are applied. For shared ownership, enter the full purchase price paid for 100% equity (this is equal to the value of the share owned by the PRP plus the value bought by the purchaser).","Enter the amount of initial equity held by the purchaser (for example, 25% or 50%)",,,,,,This is 'Yes' if the mortgage includes borrowing beyond the purchase price of the property,Enter the total cash sum paid by the buyer towards the property that was not funded by the mortgage,Enter the total cash discount given on the property being purchased through the Social HomeBuy scheme,Before any charges,"For example, service and management charges",,"For all schemes, including Right to Acquire (RTA), Right to Buy (RTB), Voluntary Right to Buy (VRTB) or Preserved Right to Buy (PRTB) sales, enter the full price of the property without any discount","For all schemes except Right to Buy (RTB), Preserved Right to Buy (PRTB), Voluntary Right to Buy (VRTB) and Rent to Buy","For Right to Buy (RTB), Preserved Right to Buy (PRTB) and Voluntary Right to Buy (VRTB). For capped discount, enter capped %. If property is sold to an existing tenant under RTB, PRTB or VRTB, enter % discount from full market value.",,,,,,This is 'Yes' if the mortgage includes borrowing beyond the purchase price of the property,Enter the total cash sum paid by the purchaser towards the property that was not funded by the mortgage. This excludes any grant or loan.,"For example, service and management charges",,,,,,,This is 'Yes' if the mortgage includes borrowing beyond the purchase price of the property,Enter the total cash sum paid by the purchaser towards the property that was not funded by the mortgage. This excludes any grant or loan,"For example, service and management charges"
+Values,Numeric,"Email format if using new CORE.
+Alphanumeric, except for commas if using old CORE.",1 - 31,1 - 12,20 - 24,"Alphanumeric, max 9 characters",1 - 3,"2, 16, 18, 24, 28 or 30 - 32","8, 9, 14, 21, 22, 27 or 29",10 or 12,Alphanumeric,1 - 2,,,1 - 3,1 - 9,1 - 4 or 9,1 - 2,"Numeric, max 12 digits",Alphanumeric,,Text,,XX(XX),XXX,"ONS CODE: Alphanumeric, 9 characters beginning with 'E'",1 - 3,1 - 2,1,"16 - 110
+ or R","F, M, X or R",1 - 19,"12 - 13, 17 - 19",Text,0 - 8 or 10,1 - 2,"P, C, X or R",1 - 110 or R,"F, M, X or R",1 - 19,"12 - 13, 17 - 19",Text,0 - 10,1 - 2,0 - 5,"P, C, X or R",1 - 110 or R,"F, M, X or R",0 - 10,"P, C, X or R",1 - 110 or R,"F, M, X or R",0 - 10,"P, C, X or R",1 - 110 or R,"F, M, X or R",0 - 10,"P, C, X or R",1 - 110 or R,"F, M, X or R",0 - 10,"1 - 7, 9 or R",1 - 2,XX(XX),XXX,"ONS CODE: Alphanumeric, 9 characters beginning with 'E'",1 or empty,,,,1 - 3,"1 - 7, 9 or R","1, 3, 7 or 8",4 - 6,4 - 7,1 - 3,,0 - 99999 or R,1 - 2,0 - 99999 or R,1 - 2,1 - 4,0 - 999990 or R,1 - 3,,Integer <= 80,1 - 3,1 - 100,,1 - 3,1 - 2,1 - 31,1 - 12,22 - 24,1 - 31,1 - 12,22 - 24,1 - 3,1 - 2,1 - 9,1 - 4 or 9,1 - 3 or 9 - 10,0 - 999999,0 - 100,1 - 2,0 - 999999,1 - 40,Alphanumeric,Integer <= 60,1 - 3,0 - 999999,,0 - 9999.99,,Integer <= 80,0 - 999999,,0 - 100,1 - 2,0 - 999999,1 - 40,Alphanumeric,Integer <= 60,1 - 3,0 - 999999,0 - 999.99,0 - 999999,1 - 2,0 - 999999,1 - 40,Alphanumeric,Integer <= 60,1 - 3,0 - 999999,0 - 9999.99
+Can be empty?,No,Yes,No,,,Yes,No,"Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3)","Yes, if the purchase was not made through a discounted ownership scheme (if field 7 = 1 or 3)","Yes, if the purchase was not an outright sale (if field 7 = 1 or 2)","Yes, if 'Other' is not selected for type of outright sale (if field 10 is not 12)","Yes, if the purchase was not an outright sale (if field 7 = 1 or 2)",,No,"Yes, if the sale is not a joint purchase (if field 14 = 2)",No,,,"Yes, if property's full address is known (if fields 20, 22, and 24 - 25 are not empty)","Yes, if property's UPRN is known (if field 19 is not empty)",Yes,"Yes, if property's UPRN is known (if field 19 is not empty)",Yes,"Yes, if property's UPRN and local authority are known (if fields 19 and 26 are not empty",,No,,Yes,No,"Yes, if buyer is a company (if field 12 = 1)",,,,"Yes, if buyer is a company (if field 12 = 1) or if ""Any other country"" is not selected for buyer 1's nationality (if field 33 is not 12)","Yes, if buyer is a company (if field 12 = 1)",,"Yes, if buyer is a company (if field 12 = 1), or if sale is not a joint purchase (if field 14 = 2) and the other fields about this person (fields 38, 39 and 43) are also empty","Yes, if buyer is a company (if field 12 = 1), or if sale is not a joint purchase (if field 14 = 2) and the other fields about this person (fields 37, 39 and 43) are also empty","Yes, if buyer is a company (if field 12 = 1), or if sale is not a joint purchase (if field 14 = 2) and the other fields about this person (fields 37, 38 and 43) are also empty","Yes, if buyer is a company (if field 12 = 1) or if sale is not a joint purchase (if field 14 = 2)",,"Yes, if buyer is a company (if field 12 = 1) or if ""Any other country"" is not selected for buyer 2's nationality (if field 41 is not 12)","Yes, if buyer is a company (if field 12 = 1), or if sale is not a joint purchase (if field 14 = 2) and the other fields about this person (fields 37, 38 and 39) are also empty","Yes, if buyer is a company (if field 12 = 1) or if sale is not a joint purchase (if field 14 = 2)","Yes, if buyer is a company (if field 12 = 1)","Yes, if the other fields about this person (fields 47, 48 and 49) are also empty","Yes, if the other fields about this person (fields 46, 48 and 49) are also empty","Yes, if the other fields about this person (fields 46, 47 and 49) are also empty","Yes, if the other fields about this person (fields 46, 47 and 48) are also empty","Yes, if the other fields about this person (fields 51, 52 and 53) are also empty","Yes, if the other fields about this person (fields 50, 52 and 53) are also empty","Yes, if the other fields about this person (fields 50, 51 and 53) are also empty","Yes, if the other fields about this person (fields 50, 51 and 52) are also empty","Yes, if the other fields about this person (fields 55, 56 and 57) are also empty","Yes, if the other fields about this person (fields 54, 56 and 57) are also empty","Yes, if the other fields about this person (fields 54, 55 and 57) are also empty","Yes, if the other fields about this person (fields 54, 55 and 56) are also empty","Yes, if the other fields about this person (fields 59, 60 and 61) are also empty","Yes, if the other fields about this person (fields 58, 60 and 61) are also empty","Yes, if the other fields about this person (fields 58, 59 and 61) are also empty","Yes, if the other fields about this person (fields 58, 59 and 60) are also empty",No,,"Yes, if postcode of buyer 1's last settled accommodation is unknown (if field 63 = 2)",,Yes,Yes,,,,"Yes, if sale is not a joint purchase (if field 14 = 2)","Yes, if sale is not a joint purchase (if field 14 = 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 71 = 1 or 3)",No,"Yes, if none of the buyers are known to have served as a regular in the UK armed forces (if field 73 = 7, 3 or 8)",No,,,No,"Yes, if buyer 1's income is not known (if field 78 = R)","Yes, if sale is not a joint purchase (if field 14 = 2)","Yes, if sale is not a joint purchase (if field 14 = 2) or if buyer 2's income is not known (if field 80 = R)",No,,,"Yes, if the purchasers did not previously own a property or if it is not known (if field 84 = 2 or 3)","Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3)",,"Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3) or if this is not known to be a staircasing transaction (if field 87 = 2 or 3)",,,"Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3) or if this is a staircasing transaction (if field 87 = 1)","Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3), if this is a resale (if field 91 = 1) or if this is a staircasing transaction (if field 87 = 1)",,,,,,"Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3)",,"Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3) or if the buyer was not a private registered provider, housing association or local authority tenant immediately before sale (if field 99 = 2)",,,"Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3)",,,"Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3) or if a mortgage was not used (if field 105 = 2)",,"Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3), if a mortgage was not used (if field 105 = 2) or if 'Other' is not selected for mortgage lender name (if field 107 is not 40)","Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3) or if a mortgage was not used (if field 105 = 2)",,"Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3)","Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3) or the type of shared ownership sale is not Social Homebuy (if field 8 is not 18)","Yes, if the purchase was not made through a shared ownership scheme (if field 7 = 2 or 3)",,"Yes, if the purchase was not made through a discounted ownership scheme (if field 7 = 1 or 3)",,"Yes, if the purchase was not made through a discounted ownership scheme (if field 7 = 1 or 3) or if the type of discounted sale is PRTB, VRTB, RTB, or Rent to Buy (if field 9 is null, 9, 14, 27 or 29)","Yes, if the purchase was not made through a discounted ownership scheme (if field 7 = 1 or 3 or if the type of discounted sale is not PRTB, VRTB, or RTB (if field 9 is null, 8, 29, 21, 22)","Yes, if the purchase was not made through a discounted ownership scheme (if field 7 = 1 or 3)","Yes, if the purchase was not made through a discounted ownership scheme (if field 7 = 1 or 3) or if a mortgage was not used (if field 119 = 2)",,"Yes, if the purchase was not made through a discounted ownership scheme (if field 7 = 1 or 3), if a mortgage was not used (if field 119 = 2) or if 'Other' is not selected for mortgage lender name (if field 121 is not 40)","Yes, if the purchase was not made through a discounted ownership scheme (if field 7 = 1 or 3) or if a mortgage was not used (if field 119 = 2)",,"Yes, if the purchase was not made through a discounted ownership scheme (if field 7 = 1 or 3)",,"Yes, if the purchase was not an outright sale (if field 7 = 1 or 2)",,"Yes, if the purchase was not an outright sale (if field 7 = 1 or 2) or if a mortgage was not used (if field 128 = 2)",,"Yes, if the purchase was not an outright sale (if field 7 = 1 or 2), if a mortgage was not used (if field 128 = 2), or if 'Other' is not selected for mortgage lender name (if field 130 is not 40)","Yes, if the purchase was not an outright sale (if field 7 = 1 or 2) or if a mortgage was not used (if field 128 = 2)",,"Yes, if the purchase was not an outright sale (if field 7 = 1 or 2)",
+Types of sales the question applies to,,,,,,,,Shared ownership only,Discounted ownership only,Outright sale only,,,,,Joint purchase only,,,,,,,,,,,,,,,,,,,,,,,,,Joint purchase only,,,,Joint purchase only,,,,,,,,,,,,,,,,,,,,,,,,,,,Joint purchase only,,,,,,,,,Joint purchase only,,,,,Shared ownership only,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Discounted ownership only,,,,,,,,,,,,Outright sale only,,,,,,,,
+Duplicate check field?,Yes,,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
+,,,2,10,23,23 test BU,1,2,,,,,M,1,2,1,1,1,,123,,123,,A1,1AA,E07000223,1,1,1,32,X,1,12,1,1,1,P,33,X,1,12,1,1,1,0,,,,,,,,,,,,,,,,,1,3,A1,1AA,1,1,250000,25,42500,3,21000,8,800,7,1,1,,1,,1,1,,1,1,,1,10,10,1,,123,,,3,,5,1,1,1,1,1,200000,25,1,200000,1,,2,1,50000,1,1,,1,1,1,1,0,10,10,1,1,,,,,,,,,,,,
diff --git a/spec/fixtures/files/completed_2022_23_sales_bulk_upload.csv b/spec/fixtures/files/completed_2022_23_sales_bulk_upload.csv
deleted file mode 100644
index 1eb4a01f9..000000000
--- a/spec/fixtures/files/completed_2022_23_sales_bulk_upload.csv
+++ /dev/null
@@ -1,119 +0,0 @@
-Question,What is the purchaser code?,What is the day of the sale completion date? - DD,What is the month of the sale completion date? - MM,What is the year of the sale completion date? - YY,[BLANK],Was the buyer interviewed for any of the answers you will provide on this log?,Age of Buyer 1,Age of Buyer 2 or Person 2,Age of Person 3,Age of Person 4,Age of Person 5,Age of Person 6,Gender identity of Buyer 1,Gender identity of Buyer 2 or Person 2,Gender identity of Person 3,Gender identity of Person 4,Gender identity of Person 5,Gender identity of Person 6,Person 2's relationship to lead tenant,Person 3's relationship to lead tenant,Person 4's relationship to lead tenant,Person 5's relationship to lead tenant,Person 6's relationship to lead tenant,Working situation of Buyer 1,Working situation of Buyer 2 or Person 2,Working situation of Person 3,Working situation of Person 4,Working situation of Person 5,Working situation of Person 6,What is the buyer 1's ethnic group?,What is buyer 1's nationality?,What is buyer 1's gross annual income?,What is buyer 2's gross annual income?,Was buyer 1's income used for a mortgage application?,Was buyer 2's income used for a mortgage application?,"What is the total amount the buyers had in savings before they paid any deposit for the property?
-
-To the nearest £10",Have any of the buyers previously owned a property?,[BLANK],What was buyer 1's previous tenure?,What is the local authority of buyer 1's last settled home,Part 1 of postcode of buyer 1's last settled home,Part 2 of postcode of buyer 1's last settled home,Do you know the postcode of buyer 1's last settled home?,Was the buyer registered with their PRP (HA)?,Was the buyer registered with the local authority?,Was the buyer registered with a Help to Buy agent?,Was the buyer registered with another PRP (HA)?,Does anyone in the household consider themselves to have a disability?,Does anyone in the household use a wheelchair?,How many bedrooms does the property have?,What type of unit is the property?,Which type of building is the property?,What is the local authority of the property?,Part 1 of postcode of property,Part 2 of postcode of property,Is the property built or adapted to wheelchair-user standards?,What is the type of shared ownership sale?,"Is this a resale?
-
-Shared ownership","What is the day of the practical completion or handover date? - DD
-
-Shared ownership","What is the month of the practical completion or handover date? - MM
-
-Shared ownership","What is the year of the practical completion or handover date? - YY
-
-Shared ownership","What is the day of the exchange of contracts date? - DD
-
-Shared ownership","What is the month of the exchange of contracts date? - MM
-
-Shared ownership","What is the year of the exchange of contracts date? - YY
-
-Shared ownership","Was the household re-housed under a local authority nominations agreement?
-
-Shared ownership","How many bedrooms did the buyer's previous property have?
-
-Shared ownership","What was the type of the buyer's previous property?
-
-Shared ownership","What was the full purchase price?
-
-Shared ownership","What was the initial percentage equity stake purchased?
-
-Shared ownership","What is the mortgage amount?
-
-Shared ownership","Does this include any extra borrowing?
-
-Shared ownership","How much was the cash deposit paid on the property?
-
-Shared ownership","How much cash discount was given through Social Homebuy?
-
-Shared ownership","What is the basic monthly rent?
-
-Shared ownership","What are the total monthly leasehold charges for the property?
-
-Shared ownership",What is the type of discounted ownership sale?,"What was the full purchase price?
-
-Discounted ownership","What was the amount of any loan, grant, discount or subsidy given?
-
-Discounted ownership","What was the percentage discount?
-
-Discounted ownership","What is the mortgage amount?
-
-Discounted ownership","Does this include any extra borrowing?
-
-Discounted ownership","How much was the cash deposit paid on the property?
-
-Discounted ownership","What are the total monthly leasehold charges for the property?
-
-Discounted ownership",What is the type of outright sale?,"What is the 'other' type of outright sale?
-
-Outright sale",[BLANK],"What is the full purchase price?
-
-Outright sale","What is the mortgage amount?
-
-Outright sale","Does this include any extra borrowing?
-
-Outright sale","How much was the cash deposit paid on the property?
-
-Outright sale","What are the total monthly leasehold charges for the property?
-
-Outright sale","Which organisation owned this property before the sale?
-
-Organisation's CORE ID",Username,BLANK,Has the buyer ever served in the UK Armed Forces and for how long?,[BLANK],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?,"What is the name of the mortgage lender?
-
-Shared ownership","What is the name of the 'other' mortgage lender?
-
-Shared ownership","What is the name of the mortgage lender?
-
-Discounted ownership","What is the name of the 'other' mortgage lender?
-
-Discounted ownership","What is the name of the mortgage lender?
-
-Outright sale","What is the name of the 'other' mortgage lender?
-
-Outright sale",Were the buyers receiving any of these housing-related benefits immediately before buying this property?,"What is the length of the mortgage in years?
-
-Shared ownership","What is the length of the mortgage in years?
-
-Discounted ownership","What is the length of the mortgage in years?
-
-Outright sale","How long have the buyers been living in the property before the purchase?
-
-Discounted ownership",Are there more than two joint purchasers of this property?,"How long have the buyers been living in the property before the purchase?
-
-Shared ownership",Is this a staircasing transaction?,Data Protection question,Was this purchase made through an ownership scheme?,"Is the buyer a company?
-
-Outright sale",Will the buyers live in the property?,Is this a joint purchase?,Will buyer 1 live in the property?,Will buyer 2 live in the property?,"Besides the buyers, how many people live in the property?","What percentage of the property has been bought in this staircasing transaction?
-
-Shared ownership","What percentage of the property does the buyer now own in total?
-
-Shared ownership","What was the rent type of the buyer's previous property?
-
-Shared ownership","Was a mortgage used for the purchase of this property?
-
-Shared ownership","Was a mortgage used for the purchase of this property?
-
-Discounted ownership","Was a mortgage used for the purchase of this property?
-
-Outright sale"
-Values,Max 9 digits,1 - 31,1 - 12,19 - 23,,1 or null,"15 - 110
-or R",1 - 110 or R,,,,,"M, F, X or R",,,,,,"P, C, X or R",,,,,0 - 10,,,,,,1 - 19,"12 -13, 17 -19",0 - 99999,,1 or 2,1 or 2,0 - 999990,1 - 3,,1 - 7 or 9,ONS CODE - E + 9 digits,XXX(X),XXX,1 or null,,,,,1 - 3,1 - 3,1 - 9,1 - 4 or 9,1 or 2,ONS CODE E + 9 digits,XXX(X),XXX,1 - 3,"2, 16, 18, 24, 28 or 30-31",1 or 2,1 - 31,1 - 12,19 - 23,1 - 31,1 - 12,19 - 23,1 - 3,1 - 9,1 - 4 or 9,0 - 999999,0 - 100,0 - 999999,1 - 3,0 - 999999,,0 - 999.99,,"8, 9, 14, 21, 22, 27 or 29",0 - 999999,,0 - 100,0 - 999999,1 - 3,0 - 999999,0 - 999.99,10 or 12,,,0 - 999999,,1-3,0 - 999999,0-999.99,Up to 7 digits,Username of CORE account this sales log should be assigned to,,3 - 8,,4 - 7,1 - 40,,1 - 40,,1 - 40,,1 - 4, Integer <=60, Integer <=60, Integer <=60, Integer <=80,1 - 3, Integer <=80,1 - 3,1,1 - 3,1 - 2,1 - 2,1 - 2,1 - 2,1 - 2,0 - 5,1 - 100,1 - 100,1-3 or 9-10,1 - 2,1 - 2,1 - 2
-Can be Null?,No,,,,,No,No,"If fields 14, 19 and 25 are all also null","If fields 15, 20 and 26 are all also null","If fields 16, 21 and 27 are all also null","If fields 17, 22 and 28 are all also null","If fields 18, 23 and 29 are all also null",No,"If fields 8, 19 and 25 are all also null","If fields 9, 20 and 26 are also null","If fields 10, 21 and 27 are all also null","If fields 11, 22 and 28 are all also null","If fields 12, 23 and 29 are all also null","If fields 8, 14 and 25 are all also null","If fields 9, 15 and 26 are all also null","If fields 10, 16 and 27 are all also null","If fields 11, 17 and 28 are all also null","If fields 12, 18 and 29 are all also null",If field 6 = 1,"If fields 8, 14 and 19 are all also null","If fields 9, 15 and 20 are all also null","If fields 10, 16 and 21 are all also null","If fields 11, 17 and 22 are all also null","If fields 12, 18 and 23 are all also null",If field 6 = 1,,,If field 116 = 2,If field 32 is null,If field 116 = 2,If field 6 = 1,,,If field 6 = 1,No,If field 43 = 1,,If fields 41 and 42 BOTH have valid entries,Yes,,,,If field 6 = 1,,No,,,,,,,If field 113 = 2 or 3,,,,,,,,,"If field 113 = 2 or 3
-OR
-field 39 = 3 - 7 or 9",,If field 113 = 2 or 3,,,,,"If field 57 is null, 2, 16, 24 or 28",If field 113 = 2 or 3,,If field 113 = 1 or 3,If field 76 is null,"If field 76 is null, 9 or 14","If field 76 is null, 8, 21 or 22",If field 113 = 1 or 3,,,,If field 113 = 1 or 2,If field 84 is null or 10,,If field 113 = 1 or 2,,,,,No,Yes,,No,,No,If field 113 = 2 or 3,"If field 113 = 2 or 3
-OR
-If field 98 is not 40",If field 113 = 1 or 3,"If field 113 = 1 or 3
-OR
-If field 100 is not 40",If field 113 = 1 or 2,"If field 113 = 1 or 2
-OR
-If field 102 is not 40",No,If field 113 = 2 or 3,If field 113 = 1 or 3,If field 113 = 1 or 2,If field 113 = 1 or 3,If field 116 = 2,If field 113 = 2 or 3,If field 113 = 2 or 3,No,No,If field 113 = 1 or 2,If field 113 = 1 or 2,No,No,If field 116 = 2,No,If field 113 = 2 or 3,If field 113 = 2 or 3,"If field 113 = 1 or 2
-OR
-If field 39 = 3 - 9",If field 113 = 2 or 3,If field 113 = 1 or 3,If field 113 = 1 or 2
-Bulk upload format and duplicate check,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
-,22 test BU,22,2,23,,1,32,32,,,,,M,F,,,,,R,,,,,1,2,,,,,12,18,30000,15000,1,1,20000,3,,1,E09000008,A1,1AA,1,,1,1,,3,3,2,1,1,E09000008,CR0,4BB,3,2,2,23,3,22,30,3,22,3,1,1,250000,25,42500,3,20000,,800,200,,,,,,,,,,,,,,,,,123,,,3,,5,1,,,,,,4,20,,,,2,5,1,1,1,,1,1,1,1,0,10,10,1,1,,
diff --git a/spec/mailers/bulk_upload_mailer_spec.rb b/spec/mailers/bulk_upload_mailer_spec.rb
index 39bc54866..f26da1efb 100644
--- a/spec/mailers/bulk_upload_mailer_spec.rb
+++ b/spec/mailers/bulk_upload_mailer_spec.rb
@@ -46,7 +46,7 @@ RSpec.describe BulkUploadMailer do
filename: bulk_upload.filename,
log_type: "lettings",
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
- success_description: "The lettings 2022/23 data you uploaded has been checked. The 0 logs you uploaded are now complete.",
+ success_description: "The lettings 2023/24 data you uploaded has been checked. The 0 logs you uploaded are now complete.",
logs_link: clear_filters_url(filter_type: "lettings_logs"),
},
)
@@ -113,7 +113,7 @@ RSpec.describe BulkUploadMailer do
title: "Check your file data",
filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
- description: "Some of your 2022/23 lettings data might not be right. Click the link below to review the potential errors, and check your file to see if the data is correct.",
+ description: "Some of your 2023/24 lettings data might not be right. Click the link below to review the potential errors, and check your file to see if the data is correct.",
cta_link: bulk_upload_lettings_soft_validations_check_url(bulk_upload, page: "confirm-soft-errors"),
},
)
diff --git a/spec/requests/bulk_upload_lettings_results_controller_spec.rb b/spec/requests/bulk_upload_lettings_results_controller_spec.rb
index 91d7a0742..70a6c1f0a 100644
--- a/spec/requests/bulk_upload_lettings_results_controller_spec.rb
+++ b/spec/requests/bulk_upload_lettings_results_controller_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe BulkUploadLettingsResultsController, type: :request do
get "/lettings-logs/bulk-upload-results/#{bulk_upload.id}/summary"
expect(response).to be_successful
- expect(response.body).to include("Bulk upload for lettings (2022/23)")
+ expect(response.body).to include("Bulk upload for lettings (2023/24)")
end
it "renders the bulk upload filename" do
@@ -68,7 +68,7 @@ RSpec.describe BulkUploadLettingsResultsController, type: :request do
get "/lettings-logs/bulk-upload-results/#{bulk_upload.id}"
expect(response).to be_successful
- expect(response.body).to include("Bulk upload for lettings (2022/23)")
+ expect(response.body).to include("Bulk upload for lettings (2023/24)")
end
it "renders correct number of errors" do
diff --git a/spec/requests/bulk_upload_lettings_resume_controller_spec.rb b/spec/requests/bulk_upload_lettings_resume_controller_spec.rb
index 76100dec0..ce2c3b7c6 100644
--- a/spec/requests/bulk_upload_lettings_resume_controller_spec.rb
+++ b/spec/requests/bulk_upload_lettings_resume_controller_spec.rb
@@ -41,7 +41,7 @@ RSpec.describe BulkUploadLettingsResumeController, type: :request do
expect(response).to be_successful
expect(response.body).to include("Bulk upload for lettings")
- expect(response.body).to include("2022/23")
+ expect(response.body).to include("2023/24")
expect(response.body).to include("View the error report")
expect(response.body).to include("How would you like to fix the errors?")
expect(response.body).to include(bulk_upload.filename)
@@ -180,7 +180,7 @@ RSpec.describe BulkUploadLettingsResumeController, type: :request do
expect(response).to be_successful
expect(response.body).to include("Bulk upload for lettings")
- expect(response.body).to include("2022/23")
+ expect(response.body).to include("2023/24")
expect(response.body).to include("These 2 answers will be deleted if you upload the log")
expect(response.body).to include(bulk_upload.filename)
expect(response.body).to include("Clear this data and upload the logs")
diff --git a/spec/requests/bulk_upload_lettings_soft_validations_check_controller_spec.rb b/spec/requests/bulk_upload_lettings_soft_validations_check_controller_spec.rb
index 904c18573..88640dcb4 100644
--- a/spec/requests/bulk_upload_lettings_soft_validations_check_controller_spec.rb
+++ b/spec/requests/bulk_upload_lettings_soft_validations_check_controller_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe BulkUploadLettingsSoftValidationsCheckController, type: :request
get "/lettings-logs/bulk-upload-soft-validations-check/#{bulk_upload.id}/confirm-soft-errors"
expect(response.body).to include("Bulk upload for lettings")
- expect(response.body).to include("2022/23")
+ expect(response.body).to include("2023/24")
expect(response.body).to include("Check these 2 answers")
expect(response.body).to include(bulk_upload.filename)
expect(response.body).to include("Are these fields correct?")
diff --git a/spec/requests/bulk_upload_sales_results_controller_spec.rb b/spec/requests/bulk_upload_sales_results_controller_spec.rb
index 9717396c5..d0b2ef37b 100644
--- a/spec/requests/bulk_upload_sales_results_controller_spec.rb
+++ b/spec/requests/bulk_upload_sales_results_controller_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe BulkUploadSalesResultsController, type: :request do
get "/sales-logs/bulk-upload-results/#{bulk_upload.id}"
expect(response).to be_successful
- expect(response.body).to include("Bulk Upload for sales (2022/23)")
+ expect(response.body).to include("Bulk Upload for sales (2023/24)")
end
it "renders correct number of errors" do
diff --git a/spec/requests/bulk_upload_sales_resume_controller_spec.rb b/spec/requests/bulk_upload_sales_resume_controller_spec.rb
index 1d964b92d..0e5a74973 100644
--- a/spec/requests/bulk_upload_sales_resume_controller_spec.rb
+++ b/spec/requests/bulk_upload_sales_resume_controller_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe BulkUploadSalesResumeController, type: :request do
expect(response).to be_successful
expect(response.body).to include("Bulk upload for sales")
- expect(response.body).to include("2022/23")
+ expect(response.body).to include("2023/24")
expect(response.body).to include("View the error report")
expect(response.body).to include("How would you like to fix the errors?")
expect(response.body).to include(bulk_upload.filename)
@@ -170,7 +170,7 @@ RSpec.describe BulkUploadSalesResumeController, type: :request do
expect(response).to be_successful
expect(response.body).to include("Bulk upload for sales")
- expect(response.body).to include("2022/23")
+ expect(response.body).to include("2023/24")
expect(response.body).to include("These 2 answers will be deleted if you upload the log")
expect(response.body).to include(bulk_upload.filename)
expect(response.body).to include("Clear this data and upload the logs")
diff --git a/spec/requests/bulk_upload_sales_soft_validations_check_controller_spec.rb b/spec/requests/bulk_upload_sales_soft_validations_check_controller_spec.rb
index 3225f0f3f..225c7c734 100644
--- a/spec/requests/bulk_upload_sales_soft_validations_check_controller_spec.rb
+++ b/spec/requests/bulk_upload_sales_soft_validations_check_controller_spec.rb
@@ -15,7 +15,7 @@ RSpec.describe BulkUploadSalesSoftValidationsCheckController, type: :request do
get "/sales-logs/bulk-upload-soft-validations-check/#{bulk_upload.id}/confirm-soft-errors"
expect(response.body).to include("Bulk upload for sales")
- expect(response.body).to include("2022/23")
+ expect(response.body).to include("2023/24")
expect(response.body).to include("Check these 2 answers")
expect(response.body).to include(bulk_upload.filename)
expect(response.body).to include("Are these fields correct?")
diff --git a/spec/services/bulk_upload/lettings/log_creator_spec.rb b/spec/services/bulk_upload/lettings/log_creator_spec.rb
index 6e69cb71c..997e8efd9 100644
--- a/spec/services/bulk_upload/lettings/log_creator_spec.rb
+++ b/spec/services/bulk_upload/lettings/log_creator_spec.rb
@@ -1,16 +1,27 @@
require "rails_helper"
RSpec.describe BulkUpload::Lettings::LogCreator do
- subject(:service) { described_class.new(bulk_upload:, path:) }
+ subject(:service) { described_class.new(bulk_upload:, path: "") }
let(:owning_org) { create(:organisation, old_visible_id: 123) }
let(:user) { create(:user, organisation: owning_org) }
let(:bulk_upload) { create(:bulk_upload, :lettings, user:) }
- let(:path) { file_fixture("2022_23_lettings_bulk_upload.csv") }
+ let(:csv_parser) { instance_double(BulkUpload::Lettings::Year2023::CsvParser) }
+ let(:row_parser) { instance_double(BulkUpload::Lettings::Year2023::RowParser) }
+ let(:log) { build(:lettings_log, :completed, created_by: user, owning_organisation: owning_org, managing_organisation: owning_org) }
+
+ before do
+ allow(BulkUpload::Lettings::Year2023::CsvParser).to receive(:new).and_return(csv_parser)
+ allow(csv_parser).to receive(:row_parsers).and_return([row_parser])
+ allow(row_parser).to receive(:log).and_return(log)
+ allow(row_parser).to receive(:bulk_upload=).and_return(true)
+ allow(row_parser).to receive(:valid?).and_return(true)
+ allow(row_parser).to receive(:blank_row?).and_return(false)
+ end
around do |example|
- Timecop.freeze(Time.zone.local(2023, 1, 1)) do
+ Timecop.freeze(Time.zone.local(2023, 4, 1)) do
Singleton.__init__(FormHandler)
example.run
end
@@ -52,15 +63,8 @@ RSpec.describe BulkUpload::Lettings::LogCreator do
end
context "when a valid csv with several blank rows" do
- let(:file) { Tempfile.new }
- let(:path) { file.path }
- let(:log) { LettingsLog.new }
-
before do
- file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
- file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
- file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
- file.rewind
+ allow(row_parser).to receive(:blank_row?).and_return(true)
end
it "ignores them and does not create the logs" do
@@ -69,16 +73,16 @@ RSpec.describe BulkUpload::Lettings::LogCreator do
end
context "when a valid csv with row with one invalid non setup field" do
- let(:file) { Tempfile.new }
- let(:path) { file.path }
let(:log) do
build(
:lettings_log,
:completed,
renttype: 3,
+ age1_known: 0,
age1: 5,
owning_organisation: owning_org,
managing_organisation: owning_org,
+ created_by: user,
national: 18,
waityear: 9,
joint: 2,
@@ -87,16 +91,12 @@ RSpec.describe BulkUpload::Lettings::LogCreator do
)
end
- before do
- file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
- file.rewind
- end
-
it "creates the log" do
expect { service.call }.to change(LettingsLog, :count).by(1)
end
it "blanks invalid field" do
+ allow(row_parser).to receive(:valid?).and_return(false)
service.call
record = LettingsLog.last
@@ -105,8 +105,6 @@ RSpec.describe BulkUpload::Lettings::LogCreator do
end
context "when a valid csv with row with compound errors on non setup field" do
- let(:file) { Tempfile.new }
- let(:path) { file.path }
let(:log) do
build(
:lettings_log,
@@ -116,16 +114,12 @@ RSpec.describe BulkUpload::Lettings::LogCreator do
)
end
- before do
- file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2023_csv_row)
- file.rewind
- end
-
it "creates the log" do
expect { service.call }.to change(LettingsLog, :count).by(1)
end
it "blanks invalid fields" do
+ allow(row_parser).to receive(:valid?).and_return(false)
service.call
record = LettingsLog.last
@@ -135,8 +129,6 @@ RSpec.describe BulkUpload::Lettings::LogCreator do
end
context "when pre-creating logs" do
- subject(:service) { described_class.new(bulk_upload:, path:) }
-
it "creates a new log" do
expect { service.call }.to change(LettingsLog, :count)
end
@@ -152,8 +144,6 @@ RSpec.describe BulkUpload::Lettings::LogCreator do
end
context "with a valid csv and soft validations" do
- let(:file) { Tempfile.new }
- let(:path) { file.path }
let(:log) do
build(
:lettings_log,
@@ -173,11 +163,6 @@ RSpec.describe BulkUpload::Lettings::LogCreator do
)
end
- before do
- file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
- file.rewind
- end
-
it "creates a new log" do
expect { service.call }.to change(LettingsLog, :count)
end
diff --git a/spec/services/bulk_upload/lettings/validator_spec.rb b/spec/services/bulk_upload/lettings/validator_spec.rb
index 8cbbae29a..a82b7d74c 100644
--- a/spec/services/bulk_upload/lettings/validator_spec.rb
+++ b/spec/services/bulk_upload/lettings/validator_spec.rb
@@ -10,134 +10,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
let(:path) { file.path }
let(:file) { Tempfile.new }
- around do |example|
- Timecop.freeze(Date.new(2023, 10, 1)) do
- example.run
- end
- Timecop.return
- end
-
describe "validations" do
- context "when 2022" do
- let(:bulk_upload) { create(:bulk_upload, user:, year: 2022) }
-
- context "when file has no headers" do
- context "and too many columns" do
- before do
- file.write(("a" * 136).chars.join(","))
- file.write("\n")
- file.rewind
- end
-
- it "is not valid" do
- expect(validator).not_to be_valid
- expect(validator.errors["base"]).to eql(["Too many columns, please ensure you have used the correct template"])
- end
- end
-
- context "and is empty" do
- it "is not valid" do
- expect(validator).not_to be_valid
- expect(validator.errors["base"]).to eql(["Template is blank - The template must be filled in for us to create the logs and check if data is correct."])
- end
- end
-
- context "and has a new line in it (empty)" do
- before do
- file.write("\n")
- file.rewind
- end
-
- it "is not valid" do
- expect(validator).not_to be_valid
- expect(validator.errors["base"]).to eql(["Template is blank - The template must be filled in for us to create the logs and check if data is correct."])
- end
- end
-
- context "and doesn't have too many columns" do
- before do
- file.write(("a" * 95).chars.join(","))
- file.write(",1,10,22,")
- file.write(("a" * 37).chars.join(","))
- file.write("\n")
- file.rewind
- end
-
- it "is valid" do
- expect(validator).to be_valid
- end
- end
- end
-
- context "when file has headers" do
- context "and file has extra invalid headers" do
- let(:seed) { rand }
- let(:log_to_csv) { BulkUpload::LettingsLogToCsv.new(log:) }
- let(:field_numbers) { log_to_csv.default_2022_field_numbers + %w[invalid_field_number] }
- let(:field_values) { log_to_csv.to_2022_row + %w[value_for_invalid_field_number] }
-
- before do
- file.write(log_to_csv.custom_field_numbers_row(seed:, field_numbers:))
- file.write(log_to_csv.to_custom_csv_row(seed:, field_values:))
- file.rewind
- end
-
- it "is valid" do
- expect(validator).to be_valid
- end
-
- it "returns correct total logs count" do
- expect(validator.total_logs_count).to be(1)
- end
- end
-
- context "and is empty" do
- it "is not valid" do
- expect(validator).not_to be_valid
- expect(validator.errors["base"]).to eql(["Template is blank - The template must be filled in for us to create the logs and check if data is correct."])
- end
- end
-
- context "and file has too few valid headers" do
- let(:seed) { rand }
- let(:log_to_csv) { BulkUpload::LettingsLogToCsv.new(log:) }
- let(:field_numbers) { log_to_csv.default_2022_field_numbers }
- let(:field_values) { log_to_csv.to_2022_row }
-
- before do
- field_numbers.delete_at(20)
- field_values.delete_at(20)
- file.write(log_to_csv.custom_field_numbers_row(seed:, field_numbers:))
- file.write(log_to_csv.to_custom_csv_row(seed:, field_values:))
- file.rewind
- end
-
- it "is not valid" do
- expect(validator).not_to be_valid
- expect(validator.errors["base"]).to eql(["Incorrect number of fields, please ensure you have used the correct template"])
- end
- end
-
- context "and file has too many valid headers" do
- let(:seed) { rand }
- let(:log_to_csv) { BulkUpload::LettingsLogToCsv.new(log:) }
- let(:field_numbers) { log_to_csv.default_2022_field_numbers + %w[23] }
- let(:field_values) { log_to_csv.to_2022_row + %w[value] }
-
- before do
- file.write(log_to_csv.custom_field_numbers_row(seed:, field_numbers:))
- file.write(log_to_csv.to_custom_csv_row(seed:, field_values:))
- file.rewind
- end
-
- it "is not valid" do
- expect(validator).not_to be_valid
- expect(validator.errors["base"]).to eql(["Incorrect number of fields, please ensure you have used the correct template"])
- end
- end
- end
- end
-
context "when 2023" do
let(:bulk_upload) { create(:bulk_upload, user:, year: 2023) }
@@ -245,7 +118,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
context "with no headers" do
before do
- file.write(BulkUpload::LettingsLogToCsv.new(log:, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
+ file.write(BulkUpload::LettingsLogToCsv.new(log:, line_ending: "\r\n", col_offset: 0).to_2023_csv_row)
file.close
end
@@ -276,7 +149,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
describe "#call" do
context "when a valid csv" do
- let(:path) { file_fixture("2022_23_lettings_bulk_upload.csv") }
+ let(:path) { file_fixture("2023_24_lettings_bulk_upload_invalid.csv") }
it "creates validation errors" do
expect { validator.call }.to change(BulkUploadError, :count)
@@ -285,15 +158,15 @@ RSpec.describe BulkUpload::Lettings::Validator do
it "create validation error with correct values" do
validator.call
- error = BulkUploadError.find_by(row: "7", field: "field_96", category: "setup")
+ error = BulkUploadError.find_by(row: "9", field: "field_7", category: "setup")
- expect(error.field).to eql("field_96")
+ expect(error.field).to eql("field_7")
expect(error.error).to eql("You must answer tenancy start date (day)")
expect(error.tenant_code).to eql("123")
expect(error.property_ref).to be_nil
- expect(error.row).to eql("7")
- expect(error.cell).to eql("CS7")
- expect(error.col).to eql("CS")
+ expect(error.row).to eql("9")
+ expect(error.cell).to eql("H9")
+ expect(error.col).to eql("H")
end
end
@@ -350,12 +223,12 @@ RSpec.describe BulkUpload::Lettings::Validator do
end
it "creates errors" do
- expect { validator.call }.to change(BulkUploadError.where(category: :setup, error: "This is a duplicate of a log in your file"), :count).by(20)
+ expect { validator.call }.to change(BulkUploadError.where(category: :setup, error: "This is a duplicate of a log in your file"), :count).by(22)
end
end
context "with unix line endings" do
- let(:fixture_path) { file_fixture("2022_23_lettings_bulk_upload.csv") }
+ let(:fixture_path) { file_fixture("2023_24_lettings_bulk_upload.csv") }
let(:file) { Tempfile.new }
let(:path) { file.path }
@@ -377,7 +250,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
let(:path) { file.path }
before do
- file.write(BulkUpload::LettingsLogToCsv.new(log:, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
+ file.write(BulkUpload::LettingsLogToCsv.new(log:, line_ending: "\r\n", col_offset: 0).to_2023_csv_row)
file.close
end
@@ -389,16 +262,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
describe "#create_logs?" do
context "when all logs are valid" do
- let(:target_path) { file_fixture("2022_23_lettings_bulk_upload.csv") }
-
- before do
- allow(FormHandler.instance).to receive(:lettings_in_crossover_period?).and_return(true)
- target_array = File.open(target_path).readlines
- target_array[0..71].each do |line|
- file.write line
- end
- file.rewind
- end
+ let(:target_path) { file_fixture("2023_24_lettings_bulk_upload.csv") }
it "returns truthy" do
validator.call
@@ -407,7 +271,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
end
context "when there is an invalid log" do
- let(:path) { file_fixture("2022_23_lettings_bulk_upload.csv") }
+ let(:path) { file_fixture("2023_24_lettings_bulk_upload_invalid.csv") }
it "returns falsey" do
validator.call
@@ -420,8 +284,8 @@ RSpec.describe BulkUpload::Lettings::Validator do
let(:log_2) { build(:lettings_log, :completed, created_by: user) }
before do
- file.write(BulkUpload::LettingsLogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
- file.write(BulkUpload::LettingsLogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0, overrides: { illness: 100 }).to_2022_csv_row)
+ file.write(BulkUpload::LettingsLogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_2023_csv_row)
+ file.write(BulkUpload::LettingsLogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0, overrides: { illness: 100 }).to_2023_csv_row)
file.close
end
@@ -435,16 +299,9 @@ RSpec.describe BulkUpload::Lettings::Validator do
let(:log_1) { build(:lettings_log, :completed, renttype: 1, created_by: user) }
let(:log_2) { build(:lettings_log, :completed, renttype: 1, created_by: user) }
- around do |example|
- Timecop.freeze(Time.zone.local(2023, 2, 22)) do
- example.run
- end
- Timecop.return
- end
-
before do
- file.write(BulkUpload::LettingsLogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
- file.write(BulkUpload::LettingsLogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
+ file.write(BulkUpload::LettingsLogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_2023_csv_row)
+ file.write(BulkUpload::LettingsLogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_2023_csv_row)
file.close
end
@@ -460,7 +317,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
let(:log_1) { build(:lettings_log, :completed, renttype: 1, created_by: user, owning_organisation: unaffiliated_org) }
before do
- file.write(BulkUpload::LettingsLogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
+ file.write(BulkUpload::LettingsLogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_2023_csv_row)
file.close
end
@@ -474,7 +331,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
let(:log) { build(:lettings_log, :in_progress, created_by: user, startdate: Time.zone.local(2022, 5, 1)) }
before do
- file.write(BulkUpload::LettingsLogToCsv.new(log:, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
+ file.write(BulkUpload::LettingsLogToCsv.new(log:, line_ending: "\r\n", col_offset: 0).to_2023_csv_row)
file.close
end
diff --git a/spec/services/bulk_upload/lettings/year2022/csv_parser_spec.rb b/spec/services/bulk_upload/lettings/year2022/csv_parser_spec.rb
deleted file mode 100644
index 2cc3e3c9b..000000000
--- a/spec/services/bulk_upload/lettings/year2022/csv_parser_spec.rb
+++ /dev/null
@@ -1,218 +0,0 @@
-require "rails_helper"
-
-RSpec.describe BulkUpload::Lettings::Year2022::CsvParser do
- subject(:service) { described_class.new(path:) }
-
- let(:file) { Tempfile.new }
- let(:path) { file.path }
- let(:log) { build(:lettings_log, :completed) }
-
- context "when parsing csv with headers" do
- before do
- file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2022_field_numbers_row)
- file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2022_csv_row)
- file.rewind
- end
-
- it "returns correct offsets" do
- expect(service.row_offset).to eq(1)
- expect(service.col_offset).to eq(1)
- end
-
- it "parses csv correctly" do
- expect(service.row_parsers[0].field_12.to_i).to eq(35)
- end
- end
-
- context "when parsing csv with headers with extra rows" do
- before do
- file.write("Extra row\n")
- file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2022_field_numbers_row)
- file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2022_csv_row)
- file.rewind
- end
-
- it "returns correct offsets" do
- expect(service.row_offset).to eq(2)
- expect(service.col_offset).to eq(1)
- end
-
- it "parses csv correctly" do
- expect(service.row_parsers[0].field_12.to_i).to eq(35)
- end
- end
-
- context "when parsing csv with headers in arbitrary order" do
- let(:seed) { rand }
-
- before do
- file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2022_field_numbers_row(seed:))
- file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2022_csv_row(seed:))
- file.rewind
- end
-
- it "returns correct offsets" do
- expect(service.row_offset).to eq(1)
- expect(service.col_offset).to eq(1)
- end
-
- it "parses csv correctly" do
- expect(service.row_parsers[0].field_12.to_i).to eq(35)
- end
- end
-
- context "when parsing csv with extra invalid headers" do
- let(:seed) { rand }
- let(:log_to_csv) { BulkUpload::LettingsLogToCsv.new(log:) }
- let(:field_numbers) { log_to_csv.default_2022_field_numbers + %w[invalid_field_number] }
- let(:field_values) { log_to_csv.to_2022_row + %w[value_for_invalid_field_number] }
-
- before do
- file.write(log_to_csv.custom_field_numbers_row(seed:, field_numbers:))
- file.write(log_to_csv.to_custom_csv_row(seed:, field_values:))
- file.rewind
- end
-
- it "parses csv correctly" do
- expect(service.row_parsers[0].field_12.to_i).to eq(35)
- end
-
- it "counts the number of valid field numbers correctly" do
- expect(service).to be_correct_field_count
- end
- end
-
- context "when parsing csv without headers" do
- before do
- file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
- file.rewind
- end
-
- it "returns correct offsets" do
- expect(service.row_offset).to eq(0)
- expect(service.col_offset).to eq(0)
- end
-
- it "parses csv correctly" do
- expect(service.row_parsers[0].field_12.to_i).to be(35)
- end
- end
-
- context "when parsing with BOM aka byte order mark" do
- let(:bom) { "\uFEFF" }
-
- before do
- file.write(bom)
- file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
- file.rewind
- end
-
- it "parses csv correctly" do
- expect(service.row_parsers[0].field_12.to_i).to be(35)
- end
- end
-
- context "when an invalid byte sequence" do
- let(:invalid_sequence) { "\x81" }
-
- before do
- file.write(invalid_sequence)
- file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
- file.rewind
- end
-
- it "parses csv correctly" do
- expect(service.row_parsers[0].field_12.to_i).to be(35)
- end
- end
-
- describe "#column_for_field", aggregate_failures: true do
- context "when with headers using default ordering" do
- before do
- file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2022_field_numbers_row)
- file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2022_csv_row)
- file.rewind
- end
-
- it "returns correct column" do
- expect(service.column_for_field("field_1")).to eql("B")
- expect(service.column_for_field("field_134")).to eql("EE")
- end
- end
-
- context "when without headers using default ordering" do
- before do
- file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2022_csv_row)
- file.rewind
- end
-
- it "returns correct column" do
- expect(service.column_for_field("field_1")).to eql("A")
- expect(service.column_for_field("field_134")).to eql("ED")
- end
- end
-
- context "when with headers using custom ordering" do
- let(:seed) { 123 }
-
- before do
- file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2022_field_numbers_row(seed:))
- file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2022_csv_row(seed:))
- file.rewind
- end
-
- it "returns correct column" do
- expect(service.column_for_field("field_1")).to eql("I")
- expect(service.column_for_field("field_45")).to eql("BE")
- expect(service.column_for_field("field_90")).to eql("AN")
- expect(service.column_for_field("field_134")).to eql("BA")
- end
- end
- end
-
- context "when parsing csv with carriage returns" do
- before do
- file.write("Question\r\n")
- file.write("Additional info\r")
- file.write("Values\r\n")
- file.write("Can be empty?\r")
- file.write("Type of letting the question applies to\r\n")
- file.write("Duplicate check field?\r")
- file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2022_field_numbers_row)
- file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2022_csv_row)
- file.rewind
- end
-
- it "parses csv correctly" do
- expect(service.row_parsers[0].field_12.to_i).to eq(35)
- end
- end
-
- describe "#wrong_template_for_year?" do
- context "when 23/24 file with 23/24 data" do
- let(:log) { build(:lettings_log, :completed, startdate: Date.new(2024, 1, 1)) }
-
- before do
- file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2023_csv_row)
- file.rewind
- end
-
- it "returns true" do
- expect(service).to be_wrong_template_for_year
- end
- end
-
- context "when 22/23 file with 22/23 data" do
- let(:log) { build(:lettings_log, :completed, startdate: Date.new(2022, 10, 1)) }
-
- before do
- file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
- file.rewind
- end
-
- it "returns false" do
- expect(service).not_to be_wrong_template_for_year
- end
- end
- end
-end
diff --git a/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb
deleted file mode 100644
index a34cedbed..000000000
--- a/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb
+++ /dev/null
@@ -1,2181 +0,0 @@
-require "rails_helper"
-
-RSpec.describe BulkUpload::Lettings::Year2022::RowParser do
- subject(:parser) { described_class.new(attributes) }
-
- let(:now) { Time.zone.parse("01/03/2023") }
-
- let(:attributes) { { bulk_upload: } }
- let(:bulk_upload) { create(:bulk_upload, :lettings, user:) }
- let(:user) { create(:user, organisation: owning_org) }
-
- let(:owning_org) { create(:organisation, :with_old_visible_id) }
- let(:managing_org) { create(:organisation, :with_old_visible_id) }
- let(:scheme) { create(:scheme, :with_old_visible_id, owning_organisation: owning_org) }
- let(:location) { create(:location, :with_old_visible_id, scheme:) }
- let(:mock_interruption_ids) {}
-
- let(:setup_section_params) do
- {
- bulk_upload:,
- field_1: "2",
- field_111: owning_org.old_visible_id,
- field_113: managing_org.old_visible_id,
- field_96: now.day.to_s,
- field_97: now.month.to_s,
- field_98: now.strftime("%g"),
- field_134: "2",
- field_132: "1",
- }
- end
-
- let(:valid_attributes) do
- {
- bulk_upload:,
- field_1: "1",
- field_4: scheme.old_visible_id,
- field_7: "123",
- field_96: now.day.to_s,
- field_97: now.month.to_s,
- field_98: now.strftime("%g"),
- field_108: "EC1N",
- field_109: "2TD",
- field_111: owning_org.old_visible_id,
- field_113: managing_org.old_visible_id,
- field_130: "1",
- field_134: "2",
- field_102: "2",
- field_103: "1",
- field_104: "1",
- field_101: "1",
- field_133: "2",
- field_8: "1",
- field_9: "2",
- field_132: "1",
-
- field_12: "42",
- field_13: "41",
- field_14: "20",
- field_15: "18",
- field_16: "16",
- field_17: "14",
- field_18: "12",
- field_19: "20",
-
- field_20: "F",
- field_21: "M",
- field_22: "F",
- field_23: "M",
- field_24: "F",
- field_25: "M",
- field_26: "F",
- field_27: "M",
-
- field_43: "17",
- field_44: "18",
-
- field_28: "P",
- field_29: "C",
- field_30: "X",
- field_31: "R",
- field_32: "C",
- field_33: "C",
- field_34: "X",
-
- field_35: "1",
- field_36: "2",
- field_37: "6",
- field_38: "7",
- field_39: "8",
- field_40: "9",
- field_41: "0",
- field_42: "10",
-
- field_45: "1",
- field_114: "4",
- field_46: "1",
-
- field_47: "1",
-
- field_118: "2",
-
- field_55: "1",
- field_56: "0",
- field_57: "0",
- field_58: "1",
- field_59: "0",
-
- field_66: "5",
- field_67: "2",
- field_52: "31",
- field_61: "3",
- field_68: "12",
-
- field_65: "1",
- field_63: "EC1N",
- field_64: "2TD",
-
- field_69: "1",
- field_70: "1",
- field_71: "",
- field_72: "1",
- field_73: "",
- field_74: "",
-
- field_75: "1",
- field_76: "2",
- field_77: "2",
-
- field_78: "2",
-
- field_51: "1",
- field_50: "2300",
- field_116: "2",
- field_48: "1",
- field_49: "1",
-
- field_79: "4",
- field_80: "1234.56",
- field_87: "1",
- field_88: "234.56",
-
- field_106: "15",
- field_99: "0",
- field_89: now.day.to_s,
- field_90: now.month.to_s,
- field_91: now.strftime("%g"),
- }
- end
-
- before do
- mock_interruption_ids
- create(:organisation_relationship, parent_organisation: owning_org, child_organisation: managing_org)
- end
-
- around do |example|
- Timecop.freeze(Date.new(2023, 3, 1)) do
- FormHandler.instance.use_real_forms!
-
- example.run
-
- FormHandler.instance.use_fake_forms!
- end
- Timecop.return
- end
-
- describe "#blank_row?" do
- context "when a new object" do
- it "returns true" do
- expect(parser).to be_blank_row
- end
- end
-
- context "when the only populated fields are whitespace" do
- before do
- parser.field_18 = " "
- end
-
- it "returns true" do
- expect(parser).to be_blank_row
- end
- end
-
- context "when any field is populated with something other than whitespace" do
- before do
- parser.field_1 = "1"
- end
-
- it "returns false" do
- expect(parser).not_to be_blank_row
- end
- end
- end
-
- describe "validations" do
- before do
- stub_request(:get, /api.postcodes.io/)
- .to_return(status: 200, body: "{\"status\":200,\"result\":{\"admin_district\":\"Manchester\", \"codes\":{\"admin_district\": \"E08000003\"}}}", headers: {})
-
- parser.valid?
- end
-
- describe "#valid?" do
- context "when the row is blank" do
- let(:attributes) { { bulk_upload: } }
-
- it "returns true" do
- expect(parser).to be_valid
- end
- end
-
- context "when calling the method multiple times" do
- let(:attributes) { { bulk_upload:, field_134: 2 } }
-
- it "does not add keep adding errors to the pile" do
- expect { parser.valid? }.not_to change(parser.errors, :count)
- end
- end
-
- context "when valid row" do
- before do
- allow(FeatureToggle).to receive(:bulk_upload_duplicate_log_check_enabled?).and_return(true)
- end
-
- let(:attributes) { valid_attributes }
-
- it "returns true" do
- expect(parser).to be_valid
- end
-
- it "instantiates a log with everything completed", aggregate_failures: true do
- questions = parser.send(:questions).reject do |q|
- parser.send(:log).optional_fields.include?(q.id) || q.completed?(parser.send(:log))
- end
-
- expect(questions.map(&:id).size).to eq(0)
- expect(questions.map(&:id)).to eql([])
- end
-
- context "when a general needs log already exists in the db" do
- before do
- parser.log.needstype = 1
- parser.log.save!
- parser.instance_variable_set(:@valid, nil)
- end
-
- it "is not a valid row" do
- expect(parser).not_to be_valid
- end
-
- it "adds an error to all (and only) the fields used to determine duplicates" do
- parser.valid?
-
- error_message = "This is a duplicate log"
-
- expected_errors = {
- field_7: [error_message], # tenancycode
- field_12: [error_message], # age1
- field_20: [error_message], # sex1
- field_35: [error_message], # ecstat1
- field_84: [error_message], # tcharge
- field_96: [error_message], # startdate
- field_97: [error_message], # startdate
- field_98: [error_message], # startdate
- field_108: [error_message], # postcode_full
- field_109: [error_message], # postcode_full
- field_111: [error_message], # owning_organisation
- }
- expect(parser.errors.as_json).to eq(expected_errors)
- end
- end
-
- context "when a supported housing log already exists in the db" do
- let(:scheme) { create(:scheme, :with_old_visible_id, owning_organisation: owning_org) }
- let(:location) { create(:location, :with_old_visible_id, scheme:) }
- let(:attributes) { valid_attributes.merge({ field_4: scheme.old_visible_id, field_5: location.old_visible_id, field_111: owning_org.old_visible_id, field_113: owning_org.old_visible_id }) }
-
- before do
- parser.bulk_upload.needstype = "2"
- parser.log.save!
- parser.instance_variable_set(:@valid, nil)
- end
-
- it "is not a valid row" do
- expect(parser).not_to be_valid
- end
-
- it "adds an error to all the fields used to determine duplicates" do
- parser.valid?
-
- [
- :field_5, # location
- :field_7, # tenancycode
- :field_12, # age1
- :field_20, # sex1
- :field_35, # ecstat1
- :field_84, # tcharge
- :field_96, # startdate
- :field_97, # startdate
- :field_98, # startdate
- :field_111, # owning_organisation
- ].each do |field|
- expect(parser.errors[field]).to include("This is a duplicate log")
- end
-
- expect(parser.errors[:field_109]).not_to include("This is a duplicate log")
- expect(parser.errors[:field_108]).not_to include("This is a duplicate log")
- end
- end
-
- context "when a supported housing log with chcharges already exists in the db" do
- let(:scheme) { create(:scheme, :with_old_visible_id, owning_organisation: owning_org) }
- let(:location) { create(:location, :with_old_visible_id, scheme:) }
- let(:bulk_upload) { create(:bulk_upload, :lettings, user:, needstype: 2) }
- let(:attributes) do
- valid_attributes.merge({ field_4: scheme.old_visible_id,
- field_1: "2",
- field_5: location.old_visible_id,
- field_111: owning_org.old_visible_id,
- field_86: 0,
- field_85: "88" })
- end
-
- before do
- parser.log.save!
- parser.instance_variable_set(:@valid, nil)
- end
-
- it "is not a valid row" do
- expect(parser).not_to be_valid
- end
-
- it "adds an error to all the fields used to determine duplicates" do
- parser.valid?
-
- [
- :field_5, # location
- :field_7, # tenancycode
- :field_12, # age1
- :field_20, # sex1
- :field_35, # ecstat1
- :field_85, # chcharge
- :field_86, # household_charge
- :field_96, # startdate
- :field_97, # startdate
- :field_98, # startdate
- :field_111, # owning_organisation
- ].each do |field|
- expect(parser.errors[field]).to include("This is a duplicate log")
- end
-
- expect(parser.errors[:field_109]).not_to include("This is a duplicate log")
- expect(parser.errors[:field_108]).not_to include("This is a duplicate log")
- end
- end
-
- context "when a supported housing log with different chcharges already exists in the db" do
- let(:scheme) { create(:scheme, :with_old_visible_id, owning_organisation: owning_org) }
- let(:location) { create(:location, :with_old_visible_id, scheme:) }
- let(:bulk_upload) { create(:bulk_upload, :lettings, user:, needstype: 2) }
- let(:attributes) do
- valid_attributes.merge({ field_4: scheme.old_visible_id,
- field_1: "2",
- field_5: location.old_visible_id,
- field_111: owning_org.old_visible_id,
- field_86: 0,
- field_117: 4,
- field_85: 88 })
- end
-
- let(:attributes_too) do
- valid_attributes.merge({ field_4: scheme.old_visible_id,
- field_1: "2",
- field_5: location.old_visible_id,
- field_111: owning_org.old_visible_id,
- field_86: 0,
- field_117: 4,
- field_85: 87 })
- end
- let(:parser_too) { described_class.new(attributes_too) }
-
- before do
- parser.log.save!
- parser.instance_variable_set(:@valid, nil)
- end
-
- it "is a valid row" do
- expect(parser_too).to be_valid
- end
-
- it "does not add an error to all the fields used to determine duplicates" do
- parser_too.valid?
-
- [
- :field_5, # location
- :field_7, # tenancycode
- :field_12, # age1
- :field_20, # sex1
- :field_35, # ecstat1
- :field_85, # chcharge
- :field_86, # household_charge
- :field_96, # startdate
- :field_97, # startdate
- :field_98, # startdate
- :field_111, # owning_organisation
- ].each do |field|
- expect(parser_too.errors[field]).not_to include("This is a duplicate log")
- end
- end
- end
-
- context "when a supported housing log with different household_charge already exists in the db" do
- let(:scheme) { create(:scheme, :with_old_visible_id, owning_organisation: owning_org) }
- let(:location) { create(:location, :with_old_visible_id, scheme:) }
- let(:bulk_upload) { create(:bulk_upload, :lettings, user:, needstype: 2) }
- let(:attributes) do
- valid_attributes.merge({ field_4: scheme.old_visible_id,
- field_1: "2",
- field_5: location.old_visible_id,
- field_111: owning_org.old_visible_id,
- field_86: 1,
- field_117: 4 })
- end
-
- let(:attributes_too) do
- valid_attributes.merge({ field_4: scheme.old_visible_id,
- field_1: "2",
- field_5: location.old_visible_id,
- field_111: owning_org.old_visible_id,
- field_86: 0,
- field_117: 4 })
- end
- let(:parser_too) { described_class.new(attributes_too) }
-
- before do
- parser.log.save!
- parser.instance_variable_set(:@valid, nil)
- end
-
- it "is a valid row" do
- expect(parser_too).to be_valid
- end
-
- it "does not add an error to all the fields used to determine duplicates" do
- parser_too.valid?
-
- [
- :field_5, # location
- :field_7, # tenancycode
- :field_12, # age1
- :field_20, # sex1
- :field_35, # ecstat1
- :field_85, # chcharge
- :field_86, # household_charge
- :field_96, # startdate
- :field_97, # startdate
- :field_98, # startdate
- :field_111, # owning_organisation
- ].each do |field|
- expect(parser_too.errors[field]).not_to include("This is a duplicate log")
- end
- end
- end
-
- context "when a hidden log already exists in db" do
- before do
- parser.log.status = "pending"
- parser.log.skip_update_status = true
- parser.log.save!
- end
-
- it "is a valid row" do
- expect(parser).to be_valid
- end
-
- it "does not add duplicate errors" do
- parser.valid?
-
- [
- :field_5, # location
- :field_12, # age1
- :field_20, # sex1
- :field_35, # ecstat1
- :field_84, # tcharge
- :field_96, # startdate
- :field_97, # startdate
- :field_98, # startdate
- :field_108, # postcode_full
- :field_109, # postcode_full
- :field_111, # owning_organisation
- ].each do |field|
- expect(parser.errors[field]).to be_blank
- end
- end
- end
- end
-
- context "when valid row with valid decimal (integer) field_1" do
- before do
- allow(FeatureToggle).to receive(:bulk_upload_duplicate_log_check_enabled?).and_return(true)
- end
-
- let(:attributes) { valid_attributes.merge(field_1: "1.00") }
-
- it "returns true" do
- expect(parser).to be_valid
- end
- end
-
- context "when valid row with invalid decimal (non-integer) field_1" do
- before do
- allow(FeatureToggle).to receive(:bulk_upload_duplicate_log_check_enabled?).and_return(true)
- end
-
- let(:attributes) { valid_attributes.merge(field_1: "1.56") }
-
- it "returns false" do
- expect(parser).not_to be_valid
- end
- end
- end
-
- context "when setup section not complete" do
- let(:attributes) { { bulk_upload:, field_7: "123" } }
-
- it "has errors on setup fields" do
- errors = parser.errors.select { |e| e.options[:category] == :setup }.map(&:attribute).sort
-
- expect(errors).to eql(%i[field_1 field_111 field_113 field_132 field_96 field_97 field_98])
- end
- end
-
- context "when lettype is intermediate rent and intermediate rent type is not selected" do
- let(:attributes) { valid_attributes.merge(field_1: "11", field_130: nil) }
-
- it "has errors on setup field" do
- errors = parser.errors.select { |e| e.options[:category] == :setup }.map(&:attribute)
-
- expect(errors).to eql(%i[field_130])
- end
- end
-
- context "when lettype is affordable rent and affordable rent type is not selected" do
- let(:attributes) { valid_attributes.merge(field_1: "5", field_130: nil) }
-
- it "has errors on setup field" do
- errors = parser.errors.select { |e| e.options[:category] == :setup }.map(&:attribute)
-
- expect(errors).to eql(%i[field_129])
- end
- end
-
- describe "#field_1" do
- context "when null" do
- let(:attributes) { { bulk_upload:, field_1: nil, field_4: "1" } }
-
- it "returns an error" do
- expect(parser.errors[:field_1]).to be_present
- end
- end
-
- context "when incorrect data type" do
- let(:attributes) { { bulk_upload:, field_1: "foo" } }
-
- it "returns an error" do
- expect(parser.errors[:field_1]).to be_present
- end
- end
-
- context "when unpermitted value" do
- let(:attributes) { { bulk_upload:, field_1: "101" } }
-
- it "returns an error" do
- expect(parser.errors[:field_1]).to be_present
- end
- end
-
- context "when valid" do
- let(:attributes) { { bulk_upload:, field_1: "1" } }
-
- it "does not return any errors" do
- expect(parser.errors[:field_1]).to be_blank
- end
- end
-
- context "when bulk upload is for general needs" do
- let(:bulk_upload) { create(:bulk_upload, :lettings, user:, needstype: "1") }
-
- context "when general needs option selected" do
- let(:attributes) { { bulk_upload:, field_1: "1" } }
-
- it "is permitted" do
- expect(parser.errors[:field_1]).to be_blank
- end
- end
-
- context "when supported housing option selected" do
- let(:attributes) { { bulk_upload:, field_1: "2" } }
-
- it "is not permitted" do
- expect(parser.errors[:field_1]).to include("Lettings type must be a general needs type because you selected general needs when uploading the file")
- end
- end
- end
-
- context "when bulk upload is for supported housing" do
- let(:bulk_upload) { create(:bulk_upload, :lettings, user:, needstype: "2") }
-
- context "when general needs option selected" do
- let(:attributes) { { bulk_upload:, field_1: "1" } }
-
- it "is not permitted" do
- expect(parser.errors[:field_1]).to include("Lettings type must be a supported housing type because you selected supported housing when uploading the file")
- end
- end
-
- context "when supported housing option selected" do
- let(:attributes) { { bulk_upload:, field_1: "2" } }
-
- it "is permitted" do
- expect(parser.errors[:field_1]).to be_blank
- end
- end
- end
- end
-
- describe "#field_4" do # mangement group code
- context "when nullable not permitted" do
- let(:bulk_upload) { create(:bulk_upload, :lettings, user:, needstype: 2) }
- let(:attributes) { { bulk_upload:, field_1: "2", field_4: nil } }
-
- it "cannot be nulled" do
- expect(parser.errors.where(:field_4, category: :setup).map(&:message)).to eql(["You must answer management group code"])
- end
- end
-
- context "when nullable permitted" do
- let(:attributes) { { bulk_upload:, field_1: "1", field_4: nil } }
-
- it "can be nulled" do
- expect(parser.errors[:field_4]).to be_blank
- end
- end
-
- context "when matching scheme cannot be found" do
- let(:attributes) { { bulk_upload:, field_1: "1", field_4: "123", field_111: owning_org.old_visible_id, field_113: owning_org.old_visible_id } }
-
- it "returns an error" do
- expect(parser.errors.where(:field_4, category: :setup)).to be_present
- end
- end
-
- context "when scheme belongs to someone else" do
- let(:other_scheme) { create(:scheme, :with_old_visible_id) }
- let(:attributes) { { bulk_upload:, field_1: "1", field_4: other_scheme.old_visible_id, field_111: owning_org.old_visible_id, field_113: owning_org.old_visible_id } }
-
- it "returns an error" do
- expect(parser.errors.where(:field_4, category: :setup).map(&:message)).to include("This management group code does not belong to the owning organisation or managing organisation")
- end
- end
-
- context "when scheme belongs to owning org" do
- let(:scheme) { create(:scheme, :with_old_visible_id, owning_organisation: owning_org) }
- let(:attributes) { { bulk_upload:, field_1: "1", field_4: scheme.old_visible_id, field_111: owning_org.old_visible_id, field_113: owning_org.old_visible_id } }
-
- it "does not return an error" do
- expect(parser.errors[:field_4]).to be_blank
- end
- end
-
- context "when scheme belongs to managing org" do
- let(:scheme) { create(:scheme, :with_old_visible_id, owning_organisation: managing_org) }
- let(:attributes) { { bulk_upload:, field_1: "1", field_4: scheme.old_visible_id, field_113: managing_org.old_visible_id } }
-
- it "does not return an error" do
- expect(parser.errors[:field_4]).to be_blank
- end
- end
- end
-
- describe "#field_5" do # scheme code
- context "when not nullable" do
- let(:bulk_upload) { create(:bulk_upload, :lettings, user:, needstype: 2) }
- let(:attributes) { { bulk_upload:, field_1: "2", field_5: nil } }
-
- it "cannot be nulled" do
- expect(parser.errors.where(:field_5, category: :setup).map(&:message)).to eql(["You must answer scheme code"])
- end
- end
-
- context "when location does not exist" do
- let(:scheme) { create(:scheme, :with_old_visible_id, owning_organisation: owning_org) }
- let(:attributes) do
- {
- bulk_upload:,
- field_1: "1",
- field_4: scheme.old_visible_id,
- field_5: "dontexist",
- field_111: owning_org.old_visible_id,
- field_113: owning_org.old_visible_id,
- }
- end
-
- it "returns an error" do
- expect(parser.errors[:field_5]).to be_present
- end
- end
-
- context "when location exists" do
- let(:scheme) { create(:scheme, :with_old_visible_id, owning_organisation: owning_org) }
- let(:attributes) do
- {
- bulk_upload:,
- field_1: "1",
- field_4: scheme.old_visible_id,
- field_5: location.old_visible_id,
- field_111: owning_org.old_visible_id,
- field_113: owning_org.old_visible_id,
- }
- end
-
- it "does not return an error" do
- expect(parser.errors[:field_5]).to be_blank
- end
- end
-
- context "when location exists but not related" do
- let(:location) { create(:location, :with_old_visible_id) }
- let(:attributes) do
- {
- bulk_upload:,
- field_1: "1",
- field_4: scheme.old_visible_id,
- field_5: location.old_visible_id,
- field_111: owning_org.old_visible_id,
- field_113: owning_org.old_visible_id,
- }
- end
-
- it "returns as setup error" do
- expect(parser.errors.where(:field_5, category: :setup).map(&:message)).to eql(["Scheme code must relate to a scheme that is owned by the owning organisation or managing organisation"])
- end
- end
- end
-
- describe "#field_7" do
- context "when null" do
- let(:attributes) { { bulk_upload:, field_7: nil } }
-
- xit "returns an error" do
- expect(parser.errors[:field_7]).to be_present
- end
- end
- end
-
- describe "#field_10" do
- context "when field_9 is 3 aka other" do
- let(:attributes) { { bulk_upload:, field_9: "3" } }
-
- xit "returns an error" do
- expect(parser.errors[:field_10]).to be_present
- end
- end
- end
-
- describe "#field_12" do
- context "when set to a non-sensical value" do
- let(:attributes) { valid_attributes.merge(field_12: "A", field_35: "1") }
-
- it "returns only one error" do
- expect(parser.errors[:field_12].size).to be(1)
- end
- end
- end
-
- describe "#field_14" do # age3
- context "when blank but gender given" do
- let(:attributes) { valid_attributes.merge(field_14: "", field_22: "F") }
-
- it "returns an error" do
- expect(parser.errors[:field_14]).to be_present
- end
- end
- end
-
- describe "#field_52" do # leaving reason
- context "when field_134 is 1 meaning it is a renewal" do
- context "when field_52 is 40" do
- let(:attributes) { { bulk_upload:, field_52: "40", field_134: "1" } }
-
- it "is permitted" do
- expect(parser.errors[:field_52]).to be_blank
- end
- end
-
- context "when field_52 is 42" do
- let(:attributes) { { bulk_upload:, field_52: "42", field_134: "1" } }
-
- it "is permitted" do
- expect(parser.errors[:field_52]).to be_blank
- end
- end
-
- context "when field_52 is not 40 or 42" do
- let(:attributes) { { bulk_upload:, field_52: "1", field_134: "1" } }
-
- it "is not permitted" do
- expect(parser.errors[:field_52]).to be_present
- end
- end
-
- context "when not a valid option" do
- let(:attributes) { setup_section_params.merge({ bulk_upload:, field_52: "99" }) }
-
- it "has error for invalid option" do
- expect(parser.errors[:field_52]).to include("Enter a valid value for What is the tenant's main reason for the household leaving their last settled home?")
- end
- end
- end
- end
-
- describe "#field_55, #field_56, #field_57" do
- context "when one item selected" do
- let(:attributes) { { bulk_upload:, field_55: "1" } }
-
- it "is permitted" do
- expect(parser.errors[:field_55]).to be_blank
- expect(parser.errors[:field_56]).to be_blank
- expect(parser.errors[:field_57]).to be_blank
- end
- end
-
- context "when more than one item selected" do
- let(:attributes) { { bulk_upload:, field_55: "1", field_56: "1" } }
-
- it "is not permitted" do
- expect(parser.errors[:field_55]).to be_present
- expect(parser.errors[:field_56]).to be_present
- end
- end
- end
-
- describe "#field_59" do
- context "when 1 and another disability field selected" do
- let(:attributes) { { bulk_upload:, field_59: "1", field_58: "1" } }
-
- it "is not permitted" do
- expect(parser.errors[:field_59]).to be_present
- end
- end
- end
-
- describe "#field_60" do
- context "when 1 and another disability field selected" do
- let(:attributes) { { bulk_upload:, field_60: "1", field_58: "1" } }
-
- it "is not permitted" do
- expect(parser.errors[:field_60]).to be_present
- end
- end
- end
-
- describe "#field_59, #field_60" do
- context "when both 1" do
- let(:attributes) { { bulk_upload:, field_59: "1", field_60: "1" } }
-
- it "is not permitted" do
- expect(parser.errors[:field_59]).to be_present
- expect(parser.errors[:field_60]).to be_present
- end
- end
- end
-
- describe "#field_68 - 74" do
- context "when there is a reasonable preference but none is given" do
- let(:attributes) { { bulk_upload:, field_69: "1", field_70: nil, field_71: nil, field_72: nil, field_73: nil, field_74: nil } }
-
- it "is not permitted" do
- expect(parser.errors[:field_70]).to be_present
- expect(parser.errors[:field_71]).to be_present
- expect(parser.errors[:field_72]).to be_present
- expect(parser.errors[:field_73]).to be_present
- expect(parser.errors[:field_74]).to be_present
- end
- end
- end
-
- describe "#field_78" do # referral
- context "when 3 ie PRP nominated by LA and owning org is LA" do
- let(:attributes) { { bulk_upload:, field_78: "3", field_111: owning_org.old_visible_id, field_113: owning_org.old_visible_id } }
-
- it "is not permitted" do
- expect(parser.errors[:field_78]).to be_present
- end
- end
-
- context "when 4 ie referred by LA and is general needs and owning org is LA" do
- let(:attributes) { { bulk_upload:, field_78: "4", field_111: owning_org.old_visible_id, field_113: owning_org.old_visible_id } }
-
- it "is not permitted" do
- expect(parser.errors[:field_78]).to be_present
- end
- end
-
- context "when 4 ie referred by LA and is general needs and owning org is PRP" do
- let(:owning_org) { create(:organisation, :prp, :with_old_visible_id) }
-
- let(:attributes) { { bulk_upload:, field_78: "4", field_111: owning_org.old_visible_id, field_113: owning_org.old_visible_id } }
-
- it "is permitted" do
- expect(parser.errors[:field_78]).to be_blank
- end
- end
-
- context "when 4 ie referred by LA and is not general needs" do
- let(:bulk_upload) { create(:bulk_upload, :lettings, user:, needstype: 2) }
- let(:attributes) { { bulk_upload:, field_78: "4" } }
-
- it "is permitted" do
- expect(parser.errors[:field_78]).to be_blank
- end
- end
- end
-
- describe "#field_85" do
- let(:bulk_upload) { create(:bulk_upload, :lettings, user:, needstype: "2") }
-
- context "when care home charge is given" do
- let(:attributes) { valid_attributes.merge(field_85: "100") }
-
- it "sets is carehome to yes and saves the charge" do
- expect(parser.log.is_carehome).to eq(1)
- expect(parser.log.chcharge).to eq(100)
- end
- end
-
- context "when care home charge is not given" do
- let(:attributes) { valid_attributes.merge(field_85: nil) }
-
- it "sets is carehome to no and does not save the charge" do
- expect(parser.log.is_carehome).to eq(0)
- expect(parser.log.chcharge).to be_nil
- end
- end
- end
-
- describe "fields 96, 97, 98 => startdate" do
- context "when all of these fields are blank" do
- let(:attributes) { { bulk_upload:, field_1: "1", field_96: nil, field_97: nil, field_98: nil } }
-
- it "returns them as setup errors" do
- expect(parser.errors.where(:field_96, category: :setup)).to be_present
- expect(parser.errors.where(:field_97, category: :setup)).to be_present
- expect(parser.errors.where(:field_98, category: :setup)).to be_present
- end
- end
-
- context "when one of these fields is blank", aggregate_failures: true do
- let(:attributes) { { bulk_upload:, field_1: "1", field_96: "1", field_97: "1", field_98: nil } }
-
- it "returns an error only on blank field" do
- expect(parser.errors.where(:field_96, category: :setup)).to be_blank
- expect(parser.errors.where(:field_97, category: :setup)).to be_blank
-
- expect(parser.errors.where(:field_98).map(&:message)).to eql(["You must answer tenancy start date (year)"])
- expect(parser.errors.where(:field_98, category: :setup).map(&:message)).to eql(["You must answer tenancy start date (year)"])
- end
- end
-
- context "when field 98 is 4 digits instead of 2" do
- let(:attributes) { { bulk_upload:, field_98: "2022" } }
-
- it "returns an error" do
- expect(parser.errors.where(:field_98, category: :setup).map(&:message)).to include("Tenancy start year must be 2 digits")
- end
- end
-
- context "when invalid date given" do
- let(:attributes) { { bulk_upload:, field_1: "1", field_96: "a", field_97: "12", field_98: "2022" } }
-
- it "does not raise an error" do
- expect { parser.valid? }.not_to raise_error
- end
- end
-
- context "when inside of collection year" do
- let(:attributes) { { bulk_upload:, field_96: "1", field_97: "10", field_98: "22" } }
-
- let(:bulk_upload) { create(:bulk_upload, :lettings, user:, year: 2022) }
-
- it "does not return errors" do
- expect(parser.errors[:field_96]).not_to be_present
- expect(parser.errors[:field_97]).not_to be_present
- expect(parser.errors[:field_98]).not_to be_present
- end
- end
-
- context "when outside of collection year" do
- around do |example|
- Timecop.freeze(Date.new(2022, 4, 2)) do
- example.run
- end
- Timecop.return
- end
-
- let(:attributes) { { bulk_upload:, field_96: "1", field_97: "1", field_98: "22" } }
-
- let(:bulk_upload) { create(:bulk_upload, :lettings, user:, year: 2022) }
-
- it "returns errors" do
- expect(parser.errors.where(:field_96, category: :setup)).to be_present
- expect(parser.errors.where(:field_97, category: :setup)).to be_present
- expect(parser.errors.where(:field_98, category: :setup)).to be_present
- end
- end
- end
-
- describe "#field_103" do
- context "when null" do
- let(:attributes) { setup_section_params.merge({ field_103: nil }) }
-
- it "returns an error" do
- expect(parser.errors[:field_103]).to be_present
- end
-
- it "populates with correct error message" do
- expect(parser.errors[:field_103]).to eql(["You must answer type of building"])
- end
- end
- end
-
- describe "#field_111" do # owning org
- context "when no data given" do
- let(:attributes) { { bulk_upload:, field_1: "1", field_111: "" } }
-
- it "is not permitted as setup error" do
- setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
-
- expect(setup_errors.find { |e| e.attribute == :field_111 }.message).to eql("You must answer owning organisation")
- end
-
- it "blocks log creation" do
- expect(parser).to be_block_log_creation
- end
- end
-
- context "when cannot find owning org" do
- let(:attributes) { { bulk_upload:, field_111: "donotexist" } }
-
- it "is not permitted as a setup error" do
- setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
-
- expect(setup_errors.find { |e| e.attribute == :field_111 }.message).to eql("The owning organisation code is incorrect")
- end
-
- it "blocks log creation" do
- expect(parser).to be_block_log_creation
- end
- end
-
- context "when org is not stock owning" do
- let(:owning_org) { create(:organisation, :with_old_visible_id, :does_not_own_stock) }
-
- let(:attributes) { { bulk_upload:, field_111: owning_org.old_visible_id } }
-
- it "is not permitted as a setup error" do
- setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
-
- expect(setup_errors.find { |e| e.attribute == :field_111 }.message).to eql("The owning organisation code provided is for an organisation that does not own stock")
- end
-
- it "blocks log creation" do
- expect(parser).to be_block_log_creation
- end
- end
-
- context "when not affiliated with owning org" do
- let(:unaffiliated_org) { create(:organisation, :with_old_visible_id) }
-
- let(:attributes) { { bulk_upload:, field_111: unaffiliated_org.old_visible_id } }
-
- it "is not permitted as setup error" do
- setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
-
- expect(setup_errors.find { |e| e.attribute == :field_111 }.message).to eql("You do not have permission to add logs for this owning organisation")
- end
-
- it "blocks log creation" do
- expect(parser).to be_block_log_creation
- end
- end
- end
-
- describe "#field_112" do # username for created_by
- context "when blank" do
- let(:attributes) { { bulk_upload:, field_112: "", field_4: 1 } }
-
- it "is permitted" do
- expect(parser.errors[:field_112]).to be_blank
- end
- end
-
- context "when user could not be found" do
- let(:attributes) { { bulk_upload:, field_112: "idonotexist@example.com" } }
-
- it "is not permitted" do
- expect(parser.errors[:field_112]).to be_present
- end
- end
-
- context "when an unaffiliated user" do
- let(:other_user) { create(:user) }
-
- let(:attributes) { { bulk_upload:, field_111: owning_org.old_visible_id, field_112: other_user.email, field_113: managing_org.old_visible_id } }
-
- it "is not permitted" do
- expect(parser.errors[:field_112]).to be_present
- end
-
- it "blocks log creation" do
- expect(parser).to be_block_log_creation
- end
- end
-
- context "when an user part of owning org" do
- let(:other_user) { create(:user, organisation: owning_org) }
-
- let(:attributes) { { bulk_upload:, field_111: owning_org.old_visible_id, field_112: other_user.email, field_113: managing_org.old_visible_id } }
-
- it "is permitted" do
- expect(parser.errors[:field_112]).to be_blank
- end
- end
-
- context "when email matches other than casing" do
- let(:other_user) { create(:user, organisation: owning_org) }
-
- let(:attributes) { { bulk_upload:, field_111: owning_org.old_visible_id, field_112: other_user.email.upcase!, field_113: managing_org.old_visible_id } }
-
- it "is permitted" do
- expect(parser.errors[:field_112]).to be_blank
- end
- end
-
- context "when an user part of managing org" do
- let(:other_user) { create(:user, organisation: managing_org) }
-
- let(:attributes) { { bulk_upload:, field_111: owning_org.old_visible_id, field_112: other_user.email, field_113: managing_org.old_visible_id } }
-
- it "is permitted" do
- expect(parser.errors[:field_112]).to be_blank
- end
- end
- end
-
- describe "#field_113" do # managing org
- context "when blank" do
- let(:attributes) { setup_section_params.merge(field_113: nil) }
-
- it "is not permitted as setup error" do
- expect(parser.errors.where(:field_113, category: :setup).map(&:message)).to eql(["You must answer managing organisation"])
- end
-
- it "blocks log creation" do
- expect(parser).to be_block_log_creation
- end
- end
-
- context "when cannot find managing org" do
- let(:attributes) { { bulk_upload:, field_113: "donotexist" } }
-
- it "is not permitted as setup error" do
- setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
-
- expect(setup_errors.find { |e| e.attribute == :field_113 }.message).to eql("The managing organisation code is incorrect")
- end
-
- it "blocks log creation" do
- expect(parser).to be_block_log_creation
- end
- end
-
- context "when not affiliated with managing org" do
- let(:unaffiliated_org) { create(:organisation, :with_old_visible_id) }
-
- let(:attributes) { { bulk_upload:, field_111: owning_org.old_visible_id, field_113: unaffiliated_org.old_visible_id } }
-
- it "is not permitted as setup error" do
- setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
-
- expect(setup_errors.find { |e| e.attribute == :field_113 }.message).to eql("This managing organisation does not have a relationship with the owning organisation")
- end
-
- it "blocks log creation" do
- expect(parser).to be_block_log_creation
- end
- end
- end
-
- describe "#field_118, field_119 - 128" do
- context "when no illness but illnesses answered" do
- let(:attributes) { { bulk_upload:, field_118: "2", field_119: "1", field_120: "1", field_121: "1" } }
-
- it "errors added to correct fields" do
- expect(parser.errors[:field_119]).to be_present
- expect(parser.errors[:field_120]).to be_present
- expect(parser.errors[:field_121]).to be_present
- expect(parser.errors[:field_122]).not_to be_present
- expect(parser.errors[:field_123]).not_to be_present
- expect(parser.errors[:field_124]).not_to be_present
- expect(parser.errors[:field_125]).not_to be_present
- expect(parser.errors[:field_126]).not_to be_present
- expect(parser.errors[:field_127]).not_to be_present
- expect(parser.errors[:field_128]).not_to be_present
- end
- end
-
- context "when illness but no illnesses answered" do
- let(:attributes) { { bulk_upload:, field_118: "1", field_119: nil, field_120: nil, field_121: nil, field_122: nil, field_123: nil, field_124: nil, field_125: nil, field_126: nil, field_127: nil, field_128: nil } }
-
- it "errors added to correct fields" do
- expect(parser.errors[:field_119]).to be_present
- expect(parser.errors[:field_120]).to be_present
- expect(parser.errors[:field_121]).to be_present
- expect(parser.errors[:field_122]).to be_present
- expect(parser.errors[:field_123]).to be_present
- expect(parser.errors[:field_124]).to be_present
- expect(parser.errors[:field_125]).to be_present
- expect(parser.errors[:field_126]).to be_present
- expect(parser.errors[:field_127]).to be_present
- expect(parser.errors[:field_128]).to be_present
- end
- end
- end
-
- describe "#field_134" do # renewal
- context "when none possible option selected" do
- let(:attributes) { setup_section_params.merge({ field_134: "101" }) }
-
- it "adds a setup error" do
- expect(parser.errors.where(:field_134, category: :setup).map(&:message)).to include("Enter a valid value for Is this letting a renewal?")
- end
- end
- end
-
- describe "#field_55 - #field_60" do
- context "when all blank" do
- let(:attributes) { setup_section_params.merge({ field_55: nil, field_56: nil, field_57: nil, field_58: nil, field_59: nil, field_60: nil }) }
-
- it "adds errors to correct fields" do
- expect(parser.errors[:field_55]).to be_present
- expect(parser.errors[:field_56]).to be_present
- expect(parser.errors[:field_57]).to be_present
- expect(parser.errors[:field_58]).to be_present
- expect(parser.errors[:field_59]).to be_present
- end
- end
- end
-
- describe "soft validations" do
- context "when soft validation is triggered" do
- let(:attributes) { setup_section_params.merge({ field_12: 22, field_35: 5 }) }
-
- it "adds an error to the relevant fields" do
- expect(parser.errors.where(:field_12, category: :soft_validation)).to be_present
- expect(parser.errors.where(:field_35, category: :soft_validation)).to be_present
- end
-
- it "populates with correct error message" do
- expect(parser.errors.where(:field_12, category: :soft_validation).first.message).to eql("You told us this person is aged 22 years and retired.")
- expect(parser.errors.where(:field_35, category: :soft_validation).first.message).to eql("You told us this person is aged 22 years and retired.")
- end
- end
-
- context "when a soft validation is triggered that relates both to fields that are and are not routed to" do
- let(:attributes) { setup_section_params.merge({ field_47: "1", field_20: "M", field_21: "M" }) }
-
- it "adds errors to fields that are routed to" do
- expect(parser.errors.where(:field_20, category: :soft_validation)).to be_present
- expect(parser.errors.where(:field_21, category: :soft_validation)).to be_present
- end
-
- it "does not add errors to fields that are not routed to" do
- expect(parser.errors.where(:field_22, category: :soft_validation)).not_to be_present
- expect(parser.errors.where(:field_23, category: :soft_validation)).not_to be_present
- end
- end
-
- context "when soft validation is triggered and the mappings for errors are not defined" do
- let(:attributes) { setup_section_params.merge({ field_12: 22, field_35: 5 }) }
- let(:mock_interruption_ids) { allow_any_instance_of(Form::Page).to receive(:interruption_screen_question_ids).and_return(%w[fake_question_id]) } # rubocop:disable RSpec/AnyInstance
-
- it "does not crash" do
- expect(parser.errors).to be_present
- end
- end
- end
- end
-
- describe "#log" do
- describe "#created_by" do
- context "when blank" do
- let(:attributes) { setup_section_params }
-
- it "takes the user that is uploading" do
- expect(parser.log.created_by).to eql(bulk_upload.user)
- end
- end
-
- context "when email specified" do
- let(:other_user) { create(:user, organisation: owning_org) }
-
- let(:attributes) { setup_section_params.merge(field_112: other_user.email) }
-
- it "sets to user with specified email" do
- expect(parser.log.created_by).to eql(other_user)
- end
- end
- end
-
- [
- %w[age1_known age1 field_12],
- %w[age2_known age2 field_13],
- %w[age3_known age3 field_14],
- %w[age4_known age4 field_15],
- %w[age5_known age5 field_16],
- %w[age6_known age6 field_17],
- %w[age7_known age7 field_18],
- %w[age8_known age8 field_19],
- ].each do |known, age, field|
- describe "##{known} and ##{age}" do
- context "when #{field} is blank" do
- let(:attributes) { { bulk_upload:, field.to_s => nil, field_4: 1 } }
-
- it "sets ##{known} 1" do
- expect(parser.log.public_send(known)).to be(1)
- end
-
- it "sets ##{age} to nil" do
- expect(parser.log.public_send(age)).to be_nil
- end
- end
-
- context "when #{field} is R" do
- let(:attributes) { { bulk_upload:, field.to_s => "R" } }
-
- it "sets ##{known} 1" do
- expect(parser.log.public_send(known)).to be(1)
- end
-
- it "sets ##{age} to nil" do
- expect(parser.log.public_send(age)).to be_nil
- end
- end
-
- context "when #{field} is a number" do
- let(:attributes) { { bulk_upload:, field.to_s => "50" } }
-
- it "sets ##{known} to 0" do
- expect(parser.log.public_send(known)).to be(0)
- end
-
- it "sets ##{age} to given age" do
- expect(parser.log.public_send(age)).to be(50)
- end
- end
-
- context "when #{field} is a non-sensical value" do
- let(:attributes) { { bulk_upload:, field.to_s => "A" } }
-
- it "sets ##{known} to 0" do
- expect(parser.log.public_send(known)).to be(0)
- end
-
- it "sets ##{age} to nil" do
- expect(parser.log.public_send(age)).to be_nil
- end
- end
- end
- end
-
- describe "#location" do
- context "when lookup is via new core id" do
- let(:attributes) { { bulk_upload:, field_4: "S#{scheme.id}", field_5: location.id, field_111: "ORG#{owning_org.id}", field_113: "ORG#{owning_org.id}" } }
-
- it "assigns the correct location" do
- expect(parser.log.location).to eql(location)
- end
- end
-
- context "when lookup is via old core id" do
- let(:attributes) { { bulk_upload:, field_4: scheme.old_visible_id, field_5: location.old_visible_id, field_111: owning_org.old_visible_id, field_113: owning_org.old_visible_id } }
-
- it "assigns the correct location" do
- expect(parser.log.location).to eql(location)
- end
-
- context "when location had leading zeroes in its id in Old CORE" do
- let(:attributes) { { bulk_upload:, field_4: scheme.old_visible_id, field_5: "123", field_111: owning_org.old_visible_id, field_113: owning_org.old_visible_id } }
-
- before do
- location.old_visible_id = "00123"
- location.save!
- end
-
- it "assigns the correct location" do
- expect(parser.log.location).to eql(location)
- end
- end
-
- context "when the user provides an id with leading zeroes" do
- let(:attributes) { { bulk_upload:, field_4: scheme.old_visible_id, field_5: "00123", field_111: owning_org.old_visible_id, field_113: owning_org.old_visible_id } }
-
- before do
- location.old_visible_id = "123"
- location.save!
- end
-
- it "assigns the correct location" do
- expect(parser.log.location).to eql(location)
- end
- end
- end
- end
-
- describe "#scheme" do
- context "when lookup is via new core id" do
- let(:attributes) { { bulk_upload:, field_4: "S#{scheme.id}", field_5: location.id, field_113: "ORG#{owning_org.id}", field_111: "ORG#{owning_org.id}" } }
-
- it "assigns the correct scheme" do
- expect(parser.log.scheme).to eql(scheme)
- end
- end
-
- context "when lookup is via old core id" do
- let(:attributes) { { bulk_upload:, field_4: scheme.old_visible_id, field_5: location.old_visible_id, field_111: owning_org.old_visible_id, field_113: owning_org.old_visible_id } }
-
- it "assigns the correct scheme" do
- expect(parser.log.scheme).to eql(scheme)
- end
-
- context "when scheme had leading zeroes in its id in Old CORE" do
- let(:attributes) { { bulk_upload:, field_4: "10", field_5: location.old_visible_id, field_111: owning_org.old_visible_id, field_113: owning_org.old_visible_id } }
-
- before do
- scheme.old_visible_id = "010"
- scheme.save!
- end
-
- it "assigns the correct scheme" do
- expect(parser.log.scheme).to eql(scheme)
- end
- end
-
- context "when the user provides an id with leading zeroes" do
- let(:attributes) { { bulk_upload:, field_4: "010", field_5: location.old_visible_id, field_111: owning_org.old_visible_id, field_113: owning_org.old_visible_id } }
-
- before do
- scheme.old_visible_id = "10"
- scheme.save!
- end
-
- it "assigns the correct scheme" do
- expect(parser.log.scheme).to eql(scheme)
- end
- end
- end
- end
-
- describe "#owning_organisation" do
- context "when lookup is via id prefixed with ORG" do
- let(:attributes) { { bulk_upload:, field_111: "ORG#{owning_org.id}" } }
-
- it "assigns the correct org" do
- expect(parser.log.owning_organisation).to eql(owning_org)
- end
- end
- end
-
- describe "#managing_organisation" do
- context "when lookup is via id prefixed with ORG" do
- let(:attributes) { { bulk_upload:, field_113: "ORG#{managing_org.id}" } }
-
- it "assigns the correct org" do
- expect(parser.log.managing_organisation).to eql(managing_org)
- end
- end
- end
-
- describe "#cbl" do
- context "when field_75 is yes ie 1" do
- let(:attributes) { { bulk_upload:, field_75: 1 } }
-
- it "sets value to 1" do
- expect(parser.log.cbl).to be(1)
- end
- end
-
- context "when field_75 is no ie 2" do
- let(:attributes) { { bulk_upload:, field_75: 2 } }
-
- it "sets value to 0" do
- expect(parser.log.cbl).to be(0)
- end
- end
- end
-
- describe "#chr" do
- context "when field_76 is yes ie 1" do
- let(:attributes) { { bulk_upload:, field_76: 1 } }
-
- it "sets value to 1" do
- expect(parser.log.chr).to be(1)
- end
- end
-
- context "when field_76 is no ie 2" do
- let(:attributes) { { bulk_upload:, field_76: 2 } }
-
- it "sets value to 0" do
- expect(parser.log.chr).to be(0)
- end
- end
- end
-
- describe "#cap" do
- context "when field_77 is yes ie 1" do
- let(:attributes) { { bulk_upload:, field_77: 1 } }
-
- it "sets value to 1" do
- expect(parser.log.cap).to be(1)
- end
- end
-
- context "when field_77 is no ie 2" do
- let(:attributes) { { bulk_upload:, field_77: 2 } }
-
- it "sets value to 0" do
- expect(parser.log.cap).to be(0)
- end
- end
- end
-
- describe "#letting_allocation_unknown" do
- context "when field_75, 76, 77 are no ie 2" do
- let(:attributes) { { bulk_upload:, field_75: 2, field_76: 2, field_77: 2 } }
-
- it "sets value to 1" do
- expect(parser.log.letting_allocation_unknown).to be(1)
- end
- end
-
- context "when any one of field_75, 76, 77 is yes ie 1" do
- let(:attributes) { { bulk_upload:, field_75: 1 } }
-
- it "sets value to 0" do
- expect(parser.log.letting_allocation_unknown).to be(0)
- end
- end
- end
-
- describe "#renewal" do
- context "when field_134 is no ie 2" do
- let(:attributes) { { bulk_upload:, field_134: 2 } }
-
- it "sets value to 0" do
- expect(parser.log.renewal).to eq(0)
- end
- end
-
- context "when field_134 is null but rsnvac/field_106 is 14" do
- let(:attributes) { { bulk_upload:, field_134: "", field_106: "14" } }
-
- it "sets renewal to 1" do
- expect(parser.log.renewal).to eq(1)
- end
- end
- end
-
- describe "#sexN fields" do
- let(:attributes) do
- {
- bulk_upload:,
- field_20: "F",
- field_21: "M",
- field_22: "X",
- field_23: "R",
- field_24: "F",
- field_25: "M",
- field_26: "X",
- field_27: "R",
- }
- end
-
- it "sets value from correct mapping" do
- expect(parser.log.sex1).to eql("F")
- expect(parser.log.sex2).to eql("M")
- expect(parser.log.sex3).to eql("X")
- expect(parser.log.sex4).to eql("R")
- expect(parser.log.sex5).to eql("F")
- expect(parser.log.sex6).to eql("M")
- expect(parser.log.sex7).to eql("X")
- expect(parser.log.sex8).to eql("R")
- end
- end
-
- describe "#ecstatN fields" do
- let(:attributes) do
- {
- bulk_upload:,
- field_35: "1",
- field_36: "2",
- field_37: "6",
- field_38: "7",
- field_39: "8",
- field_40: "9",
- field_41: "0",
- field_42: "10",
- }
- end
-
- it "sets value from correct mapping", aggregate_failures: true do
- expect(parser.log.ecstat1).to eq(1)
- expect(parser.log.ecstat2).to eq(2)
- expect(parser.log.ecstat3).to eq(6)
- expect(parser.log.ecstat4).to eq(7)
- expect(parser.log.ecstat5).to eq(8)
- expect(parser.log.ecstat6).to eq(9)
- expect(parser.log.ecstat7).to eq(0)
- expect(parser.log.ecstat8).to eq(10)
- end
- end
-
- describe "#relatN fields" do
- let(:attributes) do
- {
- bulk_upload:,
- field_28: "P",
- field_29: "C",
- field_30: "X",
- field_31: "R",
- field_32: "P",
- field_33: "C",
- field_34: "X",
- }
- end
-
- it "sets value from correct mapping", aggregate_failures: true do
- expect(parser.log.relat2).to eq("P")
- expect(parser.log.relat3).to eq("C")
- expect(parser.log.relat4).to eq("X")
- expect(parser.log.relat5).to eq("R")
- expect(parser.log.relat6).to eq("P")
- expect(parser.log.relat7).to eq("C")
- expect(parser.log.relat8).to eq("X")
- end
- end
-
- describe "#net_income_known" do
- context "when 1" do
- let(:attributes) { { bulk_upload:, field_51: "1" } }
-
- it "sets value from correct mapping" do
- expect(parser.log.net_income_known).to eq(0)
- end
- end
-
- context "when 2" do
- let(:attributes) { { bulk_upload:, field_51: "2" } }
-
- it "sets value from correct mapping" do
- expect(parser.log.net_income_known).to eq(1)
- end
- end
-
- context "when 3" do
- let(:attributes) { { bulk_upload:, field_51: "3" } }
-
- it "sets value from correct mapping" do
- expect(parser.log.net_income_known).to eq(1)
- end
- end
-
- context "when 4" do
- let(:attributes) { { bulk_upload:, field_51: "4" } }
-
- it "sets value from correct mapping" do
- expect(parser.log.net_income_known).to eq(2)
- end
- end
- end
-
- describe "#unitletas" do
- let(:attributes) { { bulk_upload:, field_105: "1" } }
-
- it "sets value from correct mapping" do
- expect(parser.log.unitletas).to eq(1)
- end
- end
-
- describe "#rsnvac" do
- let(:attributes) { { bulk_upload:, field_106: "5" } }
-
- it "sets value from correct mapping" do
- expect(parser.log.rsnvac).to eq(5)
- end
- end
-
- describe "#sheltered" do
- let(:attributes) { { bulk_upload:, field_117: "1" } }
-
- it "sets value from correct mapping" do
- expect(parser.log.sheltered).to eq(1)
- end
- end
-
- describe "illness fields" do
- mapping = [
- { attribute: :illness_type_1, field: :field_119 },
- { attribute: :illness_type_2, field: :field_120 },
- { attribute: :illness_type_3, field: :field_121 },
- { attribute: :illness_type_4, field: :field_122 },
- { attribute: :illness_type_5, field: :field_123 },
- { attribute: :illness_type_6, field: :field_124 },
- { attribute: :illness_type_7, field: :field_125 },
- { attribute: :illness_type_8, field: :field_126 },
- { attribute: :illness_type_9, field: :field_127 },
- { attribute: :illness_type_10, field: :field_128 },
- ]
-
- mapping.each do |hash|
- describe "##{hash[:attribute]}" do
- context "when yes" do
- let(:attributes) { { bulk_upload:, hash[:field] => "1" } }
-
- it "sets value from correct mapping" do
- expect(parser.log.public_send(hash[:attribute])).to eq(1)
- end
- end
-
- context "when no" do
- let(:attributes) { { bulk_upload:, hash[:field] => "", field_4: 1 } }
-
- it "sets value from correct mapping" do
- expect(parser.log.public_send(hash[:attribute])).to be_nil
- end
- end
- end
- end
- end
-
- describe "#irproduct_other" do
- let(:attributes) { { bulk_upload:, field_131: "some other product" } }
-
- it "sets value to given free text string" do
- expect(parser.log.irproduct_other).to eql("some other product")
- end
- end
-
- describe "#tenancyother" do
- let(:attributes) { { bulk_upload:, field_10: "some other tenancy" } }
-
- it "sets value to given free text string" do
- expect(parser.log.tenancyother).to eql("some other tenancy")
- end
- end
-
- describe "#tenancylength" do
- let(:attributes) { { bulk_upload:, field_11: "2" } }
-
- it "sets value to given free text string" do
- expect(parser.log.tenancylength).to eq(2)
- end
- end
-
- describe "#earnings" do
- let(:attributes) { { bulk_upload:, field_50: "104.50" } }
-
- it "rounds to the nearest whole pound" do
- expect(parser.log.earnings).to eq(105)
- end
- end
-
- describe "#reasonother" do
- let(:attributes) { { bulk_upload:, field_53: "some other reason" } }
-
- it "sets value to given free text string" do
- expect(parser.log.reasonother).to eql("some other reason")
- end
- end
-
- describe "#ppcodenk" do
- let(:attributes) { { bulk_upload:, field_65: "2" } }
-
- it "sets correct value from mapping" do
- expect(parser.log.ppcodenk).to eq(1)
- end
- end
-
- describe "#household_charge" do
- let(:attributes) { { bulk_upload:, field_86: "1" } }
-
- it "sets correct value from mapping" do
- expect(parser.log.household_charge).to eq(1)
- end
- end
-
- describe "#chcharge" do
- let(:attributes) { { bulk_upload:, field_85: "123.45" } }
-
- it "sets value given" do
- expect(parser.log.chcharge).to eq(123.45)
- end
- end
-
- describe "#tcharge" do
- let(:attributes) { { bulk_upload:, field_84: "123.45" } }
-
- it "sets value given" do
- expect(parser.log.tcharge).to eq(123.45)
- end
- end
-
- describe "#supcharg" do
- let(:attributes) { { bulk_upload:, field_83: "123.45" } }
-
- it "sets value given" do
- expect(parser.log.supcharg).to eq(123.45)
- end
- end
-
- describe "#pscharge" do
- let(:attributes) { { bulk_upload:, field_82: "123.45" } }
-
- it "sets value given" do
- expect(parser.log.pscharge).to eq(123.45)
- end
- end
-
- describe "#scharge" do
- let(:attributes) { { bulk_upload:, field_81: "123.45" } }
-
- it "sets value given" do
- expect(parser.log.scharge).to eq(123.45)
- end
- end
-
- describe "#offered" do
- let(:attributes) { { bulk_upload:, field_99: "3" } }
-
- it "sets value given" do
- expect(parser.log.offered).to eq(3)
- end
- end
-
- describe "#propcode" do
- let(:attributes) { { bulk_upload:, field_100: "abc123" } }
-
- it "sets value given" do
- expect(parser.log.propcode).to eq("abc123")
- end
- end
-
- describe "#mrcdate" do
- context "when valid" do
- let(:attributes) { { bulk_upload:, field_92: "13", field_93: "12", field_94: "22" } }
-
- it "sets value given" do
- expect(parser.log.mrcdate).to eq(Date.new(2022, 12, 13))
- end
- end
-
- context "when invalid" do
- let(:attributes) { { bulk_upload:, field_92: "13", field_93: "13", field_94: "22" } }
-
- it "does not raise an error" do
- expect { parser.log.mrcdate }.not_to raise_error
- end
- end
- end
-
- describe "#majorrepairs" do
- context "when mrcdate given" do
- let(:attributes) { { bulk_upload:, field_92: "13", field_93: "12", field_94: "22" } }
-
- it "sets #majorrepairs to 1" do
- expect(parser.log.majorrepairs).to eq(1)
- end
- end
-
- context "when mrcdate not given" do
- let(:attributes) { { bulk_upload:, field_92: "", field_93: "", field_94: "", field_4: 1 } }
-
- it "sets #majorrepairs to 0" do
- expect(parser.log.majorrepairs).to eq(0)
- end
- end
- end
-
- describe "#voiddate" do
- context "when valid" do
- let(:attributes) { { bulk_upload:, field_89: "13", field_90: "12", field_91: "22" } }
-
- it "sets value given" do
- expect(parser.log.voiddate).to eq(Date.new(2022, 12, 13))
- end
- end
-
- context "when invalid" do
- let(:attributes) { { bulk_upload:, field_89: "13", field_90: "13", field_91: "22" } }
-
- it "does not raise an error" do
- expect { parser.log.voiddate }.not_to raise_error
- end
- end
- end
-
- describe "#startdate" do
- let(:attributes) { { bulk_upload:, field_96: now.day.to_s, field_97: now.month.to_s, field_98: now.strftime("%g") } }
-
- it "sets value given" do
- expect(parser.log.startdate).to eq(now)
- end
- end
-
- describe "#postcode_full" do
- let(:attributes) { { bulk_upload:, field_108: " EC1N ", field_109: " 2TD " } }
-
- it "strips whitespace" do
- expect(parser.log.postcode_full).to eql("EC1N 2TD")
- end
- end
-
- describe "#la" do
- let(:attributes) { { bulk_upload:, field_107: "E07000223" } }
-
- it "sets to given value" do
- expect(parser.log.la).to eql("E07000223")
- end
- end
-
- describe "#prevloc" do
- let(:attributes) { { bulk_upload:, field_62: "E07000223" } }
-
- it "sets to given value" do
- expect(parser.log.prevloc).to eql("E07000223")
- end
- end
-
- describe "#previous_la_known" do
- context "when known" do
- let(:attributes) { { bulk_upload:, field_62: "E07000223" } }
-
- it "sets to 1" do
- expect(parser.log.previous_la_known).to eq(1)
- end
- end
-
- context "when not known" do
- let(:attributes) { { bulk_upload:, field_62: "", field_4: 1 } }
-
- it "sets to 0" do
- expect(parser.log.previous_la_known).to eq(0)
- end
- end
- end
-
- describe "#first_time_property_let_as_social_housing" do
- context "when field_106 is 15, 16, or 17" do
- let(:attributes) { { bulk_upload:, field_106: %w[15 16 17].sample } }
-
- it "sets to 1" do
- expect(parser.log.first_time_property_let_as_social_housing).to eq(1)
- end
- end
-
- context "when field_106 is not 15, 16, or 17" do
- let(:attributes) { { bulk_upload:, field_106: "1" } }
-
- it "sets to 0" do
- expect(parser.log.first_time_property_let_as_social_housing).to eq(0)
- end
- end
- end
-
- describe "#housingneeds" do
- context "when no disabled needs" do
- let(:attributes) { { bulk_upload:, field_59: "1" } }
-
- it "sets to 2" do
- expect(parser.log.housingneeds).to eq(2)
- end
- end
-
- context "when dont know about disabled needs" do
- let(:attributes) { { bulk_upload:, field_60: "1" } }
-
- it "sets to 3" do
- expect(parser.log.housingneeds).to eq(3)
- end
- end
-
- context "when housingneeds are given" do
- let(:attributes) { { bulk_upload:, field_59: "0", field_57: "1", field_58: "1" } }
-
- it "sets correct housingneeds" do
- expect(parser.log.housingneeds).to eq(1)
- expect(parser.log.housingneeds_type).to eq(2)
- expect(parser.log.housingneeds_other).to eq(1)
- end
- end
-
- context "when housingneeds are given and field_59 is nil" do
- let(:attributes) { { bulk_upload:, field_57: "1", field_58: "1", field_59: nil } }
-
- it "sets correct housingneeds" do
- expect(parser.log.housingneeds).to eq(1)
- expect(parser.log.housingneeds_type).to eq(2)
- expect(parser.log.housingneeds_other).to eq(1)
- end
- end
-
- context "when housingneeds are not given" do
- let(:attributes) { { bulk_upload:, field_55: nil, field_56: nil, field_57: nil, field_59: nil } }
-
- it "sets correct housingneeds" do
- expect(parser.log.housingneeds).to eq(1)
- expect(parser.log.housingneeds_type).to eq(3)
- end
- end
-
- context "when housingneeds a and b are selected" do
- let(:attributes) { { bulk_upload:, field_55: "1", field_56: "1" } }
-
- it "sets error on housingneeds a and b" do
- parser.valid?
- expect(parser.errors[:field_55]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected")
- expect(parser.errors[:field_56]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected")
- expect(parser.errors[:field_57]).to be_blank
- end
- end
-
- context "when housingneeds a and c are selected" do
- let(:attributes) { { bulk_upload:, field_55: "1", field_57: "1" } }
-
- it "sets error on housingneeds a and c" do
- parser.valid?
- expect(parser.errors[:field_55]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected")
- expect(parser.errors[:field_57]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected")
- expect(parser.errors[:field_56]).to be_blank
- end
- end
-
- context "when housingneeds b and c are selected" do
- let(:attributes) { { bulk_upload:, field_56: "1", field_57: "1" } }
-
- it "sets error on housingneeds b and c" do
- parser.valid?
- expect(parser.errors[:field_56]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected")
- expect(parser.errors[:field_57]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected")
- expect(parser.errors[:field_55]).to be_blank
- end
- end
-
- context "when housingneeds a and g are selected" do
- let(:attributes) { { bulk_upload:, field_55: "1", field_59: "1" } }
-
- it "sets error on housingneeds a and g" do
- parser.valid?
- expect(parser.errors[:field_59]).to include("No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs")
- expect(parser.errors[:field_55]).to include("No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs")
- expect(parser.errors[:field_56]).to be_blank
- expect(parser.errors[:field_57]).to be_blank
- end
- end
-
- context "when only housingneeds g is selected" do
- let(:attributes) { { bulk_upload:, field_55: "0", field_59: "1" } }
-
- it "does not add any housingneeds errors" do
- parser.valid?
- expect(parser.errors[:field_59]).to be_blank
- expect(parser.errors[:field_55]).to be_blank
- expect(parser.errors[:field_56]).to be_blank
- expect(parser.errors[:field_57]).to be_blank
- end
- end
-
- context "when housingneeds a and h are selected" do
- let(:attributes) { { bulk_upload:, field_55: "1", field_60: "1" } }
-
- it "sets error on housingneeds a and h" do
- parser.valid?
- expect(parser.errors[:field_60]).to include("Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs")
- expect(parser.errors[:field_55]).to include("Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs")
- expect(parser.errors[:field_56]).to be_blank
- expect(parser.errors[:field_57]).to be_blank
- end
- end
-
- context "when only housingneeds h is selected" do
- let(:attributes) { { bulk_upload:, field_55: "0", field_60: "1" } }
-
- it "does not add any housingneeds errors" do
- parser.valid?
- expect(parser.errors[:field_60]).to be_blank
- expect(parser.errors[:field_55]).to be_blank
- expect(parser.errors[:field_56]).to be_blank
- expect(parser.errors[:field_57]).to be_blank
- end
- end
- end
-
- describe "#housingneeds_type" do
- context "when field_55 is 1" do
- let(:attributes) { { bulk_upload:, field_55: "1" } }
-
- it "set to 0" do
- expect(parser.log.housingneeds_type).to eq(0)
- end
- end
-
- context "when field_56 is 1" do
- let(:attributes) { { bulk_upload:, field_56: "1" } }
-
- it "set to 1" do
- expect(parser.log.housingneeds_type).to eq(1)
- end
- end
-
- context "when field_57 is 1" do
- let(:attributes) { { bulk_upload:, field_57: "1" } }
-
- it "set to 2" do
- expect(parser.log.housingneeds_type).to eq(2)
- end
- end
- end
-
- describe "#housingneeds_other" do
- context "when field_58 is 1" do
- let(:attributes) { { bulk_upload:, field_58: "1" } }
-
- it "sets to 1" do
- expect(parser.log.housingneeds_other).to eq(1)
- end
- end
-
- context "when field_58 is nil and one housingneeds option is selected" do
- let(:attributes) { { bulk_upload:, field_58: nil, field_55: "1" } }
-
- it "sets to 0" do
- expect(parser.errors[:field_58]).to be_blank
- expect(parser.errors[:field_55]).to be_blank
- expect(parser.log.housingneeds_other).to eq(0)
- end
- end
- end
- end
-
- describe "#start_date" do
- context "when year of 9 is passed to represent 2009" do
- let(:attributes) { { bulk_upload:, field_96: "1", field_97: "1", field_98: "9" } }
-
- it "uses the year 2009" do
- expect(parser.send(:start_date)).to eql(Date.new(2009, 1, 1))
- end
- end
- end
-
- describe "#spreadsheet_duplicate_hash" do
- it "returns a hash" do
- expect(parser.spreadsheet_duplicate_hash).to be_a(Hash)
- end
- end
-
- describe "#add_duplicate_found_in_spreadsheet_errors" do
- it "adds errors" do
- expect { parser.add_duplicate_found_in_spreadsheet_errors }.to change(parser.errors, :size)
- end
- end
-end
diff --git a/spec/services/bulk_upload/processor_spec.rb b/spec/services/bulk_upload/processor_spec.rb
index 5081dc17d..d71a4c007 100644
--- a/spec/services/bulk_upload/processor_spec.rb
+++ b/spec/services/bulk_upload/processor_spec.rb
@@ -6,12 +6,15 @@ RSpec.describe BulkUpload::Processor do
let(:bulk_upload) { create(:bulk_upload, :lettings, user:) }
let(:user) { create(:user, organisation: owning_org) }
let(:owning_org) { create(:organisation, old_visible_id: 123) }
-
- around do |example|
- Timecop.freeze(Time.utc(2023, 1, 1)) do
- Singleton.__init__(FormHandler)
- example.run
- end
+ let(:mock_validator) do
+ instance_double(
+ BulkUpload::Lettings::Validator,
+ invalid?: false,
+ call: nil,
+ total_logs_count: 1,
+ any_setup_errors?: false,
+ create_logs?: false,
+ )
end
describe "#call" do
@@ -39,7 +42,7 @@ RSpec.describe BulkUpload::Processor do
instance_double(
BulkUpload::Downloader,
call: nil,
- path: file_fixture("2022_23_lettings_bulk_upload.csv"),
+ path: file_fixture("2023_24_lettings_bulk_upload.csv"),
delete_local_file!: nil,
)
end
@@ -89,7 +92,7 @@ RSpec.describe BulkUpload::Processor do
instance_double(
BulkUpload::Downloader,
call: nil,
- path: file_fixture("2022_23_lettings_bulk_upload.csv"),
+ path: file_fixture("2023_24_lettings_bulk_upload.csv"),
delete_local_file!: nil,
)
end
@@ -134,7 +137,7 @@ RSpec.describe BulkUpload::Processor do
instance_double(
BulkUpload::Downloader,
call: nil,
- path: file_fixture("2022_23_lettings_bulk_upload.csv"),
+ path: file_fixture("2023_24_lettings_bulk_upload.csv"),
delete_local_file!: nil,
)
end
@@ -183,25 +186,16 @@ RSpec.describe BulkUpload::Processor do
build(
:lettings_log,
:completed,
- renttype: 3,
- age1: 20,
owning_organisation: owning_org,
managing_organisation: owning_org,
- created_by: nil,
- national: 18,
- waityear: 9,
- joint: 2,
- tenancy: 9,
- ppcodenk: 1,
- voiddate: nil,
- mrcdate: nil,
- startdate: Date.new(2022, 10, 1),
- tenancylength: nil,
+ created_by: user,
+ renttype: 1,
+ leftreg: 3,
)
end
before do
- file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
+ file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2023_csv_row)
file.rewind
allow(BulkUpload::Downloader).to receive(:new).with(bulk_upload:).and_return(mock_downloader)
@@ -255,37 +249,6 @@ RSpec.describe BulkUpload::Processor do
end
end
- context "when processing an empty file with headers" do
- context "when 2022-23" do
- let(:mock_downloader) do
- instance_double(
- BulkUpload::Downloader,
- call: nil,
- path: file_fixture("2022_23_lettings_bulk_upload_empty_with_headers.csv"),
- delete_local_file!: nil,
- )
- end
-
- before do
- allow(BulkUpload::Downloader).to receive(:new).with(bulk_upload:).and_return(mock_downloader)
- end
-
- it "sends failure email" do
- mail_double = instance_double("ActionMailer::MessageDelivery", deliver_later: nil)
-
- allow(BulkUploadMailer).to receive(:send_bulk_upload_failed_service_error_mail).and_return(mail_double)
-
- processor.call
-
- expect(BulkUploadMailer).to have_received(:send_bulk_upload_failed_service_error_mail).with(
- bulk_upload:,
- errors: ["Template is blank - The template must be filled in for us to create the logs and check if data is correct."],
- )
- expect(mail_double).to have_received(:deliver_later)
- end
- end
- end
-
context "when 2023-24" do
let(:mock_downloader) do
instance_double(
@@ -334,17 +297,20 @@ RSpec.describe BulkUpload::Processor do
renttype: 3,
owning_organisation: owning_org,
managing_organisation: owning_org,
- startdate: Time.zone.local(2022, 10, 1),
+ startdate: Time.zone.local(2023, 10, 1),
renewal: 2,
declaration: 1,
)
end
before do
- file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
+ file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2023_csv_row)
file.rewind
allow(BulkUpload::Downloader).to receive(:new).with(bulk_upload:).and_return(mock_downloader)
+ allow(BulkUpload::Lettings::Validator).to receive(:new).and_return(mock_validator)
+ allow(mock_validator).to receive(:create_logs?).and_return(true)
+ allow(mock_validator).to receive(:soft_validation_errors_only?).and_return(false)
allow(FeatureToggle).to receive(:bulk_upload_duplicate_log_check_enabled?).and_return(true)
end
@@ -406,17 +372,20 @@ RSpec.describe BulkUpload::Processor do
reason: 40,
leftreg: 3,
mrcdate: nil,
- startdate: Date.new(2022, 10, 1),
+ startdate: Date.new(2023, 10, 1),
tenancylength: nil,
)
end
before do
FormHandler.instance.use_real_forms!
- file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
+ file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2023_csv_row)
file.rewind
allow(BulkUpload::Downloader).to receive(:new).with(bulk_upload:).and_return(mock_downloader)
+ allow(BulkUpload::Lettings::Validator).to receive(:new).and_return(mock_validator)
+ allow(mock_validator).to receive(:create_logs?).and_return(true)
+ allow(mock_validator).to receive(:soft_validation_errors_only?).and_return(true)
allow(FeatureToggle).to receive(:bulk_upload_duplicate_log_check_enabled?).and_return(true)
end
@@ -473,7 +442,7 @@ RSpec.describe BulkUpload::Processor do
renttype: 3,
owning_organisation: owning_org,
managing_organisation: owning_org,
- startdate: Time.zone.local(2022, 10, 1),
+ startdate: Time.zone.local(2023, 10, 1),
renewal: 2,
created_by: other_user, # unaffiliated user
declaration: 1,
@@ -481,7 +450,8 @@ RSpec.describe BulkUpload::Processor do
end
before do
- file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
+ allow(BulkUpload::Lettings::Validator).to receive(:new).and_return(mock_validator)
+ file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2023_csv_row)
file.rewind
allow(BulkUpload::Downloader).to receive(:new).with(bulk_upload:).and_return(mock_downloader)
diff --git a/spec/services/bulk_upload/sales/log_creator_spec.rb b/spec/services/bulk_upload/sales/log_creator_spec.rb
index 91bf4565a..26ced4944 100644
--- a/spec/services/bulk_upload/sales/log_creator_spec.rb
+++ b/spec/services/bulk_upload/sales/log_creator_spec.rb
@@ -1,17 +1,28 @@
require "rails_helper"
RSpec.describe BulkUpload::Sales::LogCreator do
- subject(:service) { described_class.new(bulk_upload:, path:) }
+ subject(:service) { described_class.new(bulk_upload:, path: "") }
let(:owning_org) { create(:organisation, old_visible_id: 123) }
let(:user) { create(:user, organisation: owning_org) }
let(:bulk_upload) { create(:bulk_upload, :sales, user:) }
- let(:path) { file_fixture("completed_2022_23_sales_bulk_upload.csv") }
+ let(:csv_parser) { instance_double(BulkUpload::Sales::Year2023::CsvParser) }
+ let(:row_parser) { instance_double(BulkUpload::Sales::Year2023::RowParser) }
+ let(:log) { build(:sales_log, :completed, created_by: user, owning_organisation: owning_org, managing_organisation: owning_org) }
+
+ before do
+ allow(BulkUpload::Sales::Year2023::CsvParser).to receive(:new).and_return(csv_parser)
+ allow(csv_parser).to receive(:row_parsers).and_return([row_parser])
+ allow(row_parser).to receive(:log).and_return(log)
+ allow(row_parser).to receive(:bulk_upload=).and_return(true)
+ allow(row_parser).to receive(:valid?).and_return(true)
+ allow(row_parser).to receive(:blank_row?).and_return(false)
+ end
describe "#call" do
around do |example|
- Timecop.freeze(Time.zone.local(2023, 2, 22)) do
+ Timecop.freeze(Time.zone.local(2023, 4, 22)) do
Singleton.__init__(FormHandler)
example.run
end
@@ -43,15 +54,8 @@ RSpec.describe BulkUpload::Sales::LogCreator do
end
context "when a valid csv with several blank rows" do
- let(:file) { Tempfile.new }
- let(:path) { file.path }
- let(:log) { SalesLog.new }
-
before do
- file.write(BulkUpload::SalesLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
- file.write(BulkUpload::SalesLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
- file.write(BulkUpload::SalesLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
- file.rewind
+ allow(row_parser).to receive(:blank_row?).and_return(true)
end
it "ignores them and does not create the logs" do
@@ -60,22 +64,17 @@ RSpec.describe BulkUpload::Sales::LogCreator do
end
context "when a valid csv with row with one invalid non setup field" do
- let(:file) { Tempfile.new }
- let(:path) { file.path }
let(:log) do
build(
:sales_log,
:completed,
age1: 5,
owning_organisation: owning_org,
+ created_by: user,
+ managing_organisation: owning_org,
)
end
- before do
- file.write(BulkUpload::SalesLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
- file.rewind
- end
-
it "creates the log" do
expect { service.call }.to change(SalesLog, :count).by(1)
end
@@ -89,8 +88,6 @@ RSpec.describe BulkUpload::Sales::LogCreator do
end
context "when a valid csv with row with compound errors on non setup field" do
- let(:file) { Tempfile.new }
- let(:path) { file.path }
let(:log) do
build(
:sales_log,
@@ -100,14 +97,12 @@ RSpec.describe BulkUpload::Sales::LogCreator do
ppcodenk: 0,
postcode_full: "AA11AA",
ppostcode_full: "BB22BB",
+ owning_organisation: owning_org,
+ created_by: user,
+ managing_organisation: owning_org,
)
end
- before do
- file.write(BulkUpload::SalesLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
- file.rewind
- end
-
it "creates the log" do
expect { service.call }.to change(SalesLog, :count).by(1)
end
@@ -124,8 +119,6 @@ RSpec.describe BulkUpload::Sales::LogCreator do
end
context "when pre-creating logs" do
- subject(:service) { described_class.new(bulk_upload:, path:) }
-
it "creates a new log" do
expect { service.call }.to change(SalesLog, :count)
end
@@ -145,8 +138,6 @@ RSpec.describe BulkUpload::Sales::LogCreator do
end
context "with a valid csv and soft validations" do
- let(:file) { Tempfile.new }
- let(:path) { file.path }
let(:log) do
build(
:sales_log,
@@ -156,14 +147,10 @@ RSpec.describe BulkUpload::Sales::LogCreator do
ecstat1: 5,
owning_organisation: owning_org,
created_by: user,
+ managing_organisation: owning_org,
)
end
- before do
- file.write(BulkUpload::SalesLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
- file.rewind
- end
-
it "creates a new log" do
expect { service.call }.to change(SalesLog, :count)
end
diff --git a/spec/services/bulk_upload/sales/validator_spec.rb b/spec/services/bulk_upload/sales/validator_spec.rb
index 730a2b643..dec7e5cef 100644
--- a/spec/services/bulk_upload/sales/validator_spec.rb
+++ b/spec/services/bulk_upload/sales/validator_spec.rb
@@ -31,7 +31,7 @@ RSpec.describe BulkUpload::Sales::Validator do
context "when file has too many columns" do
before do
- file.write((%w[a] * 127).join(","))
+ file.write((%w[a] * (BulkUpload::Sales::Year2023::CsvParser::MAX_COLUMNS + 1)).join(","))
file.rewind
end
@@ -74,7 +74,7 @@ RSpec.describe BulkUpload::Sales::Validator do
let(:path) { file.path }
before do
- Timecop.freeze(Time.utc(2022, 6, 3))
+ Timecop.freeze(Time.utc(2022, 10, 3))
file.write(BulkUpload::SalesLogToCsv.new(log:, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.close
end
@@ -92,7 +92,7 @@ RSpec.describe BulkUpload::Sales::Validator do
describe "#call" do
context "when a valid csv" do
- let(:path) { file_fixture("2022_23_sales_bulk_upload.csv") }
+ let(:path) { file_fixture("2023_24_sales_bulk_upload_invalid.csv") }
it "creates validation errors" do
expect { validator.call }.to change(BulkUploadError, :count)
@@ -101,19 +101,19 @@ RSpec.describe BulkUpload::Sales::Validator do
it "create validation error with correct values" do
validator.call
- error = BulkUploadError.find_by(row: "6", field: "field_92", category: "setup")
+ error = BulkUploadError.find_by(row: "9", field: "field_1", category: "setup")
- expect(error.field).to eql("field_92")
+ expect(error.field).to eql("field_1")
expect(error.error).to eql("You must answer owning organisation")
- expect(error.purchaser_code).to eql("22 test BU")
- expect(error.row).to eql("6")
- expect(error.cell).to eql("CO6")
- expect(error.col).to eql("CO")
+ expect(error.purchaser_code).to eql("23 test BU")
+ expect(error.row).to eql("9")
+ expect(error.cell).to eql("B9")
+ expect(error.col).to eql("B")
end
end
context "with unix line endings" do
- let(:fixture_path) { file_fixture("2022_23_sales_bulk_upload.csv") }
+ let(:fixture_path) { file_fixture("2023_24_sales_bulk_upload.csv") }
let(:file) { Tempfile.new }
let(:path) { file.path }
@@ -135,7 +135,7 @@ RSpec.describe BulkUpload::Sales::Validator do
let(:path) { file.path }
before do
- file.write(BulkUpload::SalesLogToCsv.new(log:, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
+ file.write(BulkUpload::SalesLogToCsv.new(log:, line_ending: "\r\n", col_offset: 0).to_2023_csv_row)
file.close
end
@@ -150,8 +150,8 @@ RSpec.describe BulkUpload::Sales::Validator do
let(:log) { build(:sales_log, :completed) }
before do
- file.write(BulkUpload::SalesLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
- file.write(BulkUpload::SalesLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
+ file.write(BulkUpload::SalesLogToCsv.new(log:, col_offset: 0).to_2023_csv_row)
+ file.write(BulkUpload::SalesLogToCsv.new(log:, col_offset: 0).to_2023_csv_row)
file.close
end
@@ -163,7 +163,7 @@ RSpec.describe BulkUpload::Sales::Validator do
describe "#create_logs?" do
around do |example|
- Timecop.freeze(Time.zone.local(2023, 2, 22)) do
+ Timecop.freeze(Time.zone.local(2023, 10, 22)) do
Singleton.__init__(FormHandler)
example.run
end
@@ -172,15 +172,7 @@ RSpec.describe BulkUpload::Sales::Validator do
end
context "when all logs are valid" do
- let(:target_path) { file_fixture("completed_2022_23_sales_bulk_upload.csv") }
-
- before do
- target_array = File.open(target_path).readlines
- target_array[0..118].each do |line|
- file.write line
- end
- file.rewind
- end
+ let(:target_path) { file_fixture("2023_24_sales_bulk_upload.csv") }
it "returns truthy" do
validator.call
@@ -189,7 +181,7 @@ RSpec.describe BulkUpload::Sales::Validator do
end
context "when there is an invalid log" do
- let(:path) { file_fixture("2022_23_sales_bulk_upload.csv") }
+ let(:path) { file_fixture("2023_24_sales_bulk_upload_invalid.csv") }
it "returns falsey" do
validator.call
@@ -202,8 +194,8 @@ RSpec.describe BulkUpload::Sales::Validator do
let(:log_2) { build(:sales_log, :completed, created_by: user) }
before do
- file.write(BulkUpload::SalesLogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
- file.write(BulkUpload::SalesLogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0, overrides: { organisation_id: "random" }).to_2022_csv_row)
+ file.write(BulkUpload::SalesLogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_2023_csv_row)
+ file.write(BulkUpload::SalesLogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0, overrides: { organisation_id: "random" }).to_2023_csv_row)
file.close
end
@@ -214,14 +206,7 @@ RSpec.describe BulkUpload::Sales::Validator do
end
context "when all logs valid?" do
- let(:log_1) { build(:sales_log, :completed, created_by: user) }
- let(:log_2) { build(:sales_log, :completed, created_by: user) }
-
- before do
- file.write(BulkUpload::SalesLogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
- file.write(BulkUpload::SalesLogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
- file.close
- end
+ let(:path) { file_fixture("2023_24_sales_bulk_upload.csv") }
it "returns true" do
validator.call
@@ -235,7 +220,7 @@ RSpec.describe BulkUpload::Sales::Validator do
let(:log_1) { build(:sales_log, :completed, created_by: user, owning_organisation: unaffiliated_org) }
before do
- file.write(BulkUpload::SalesLogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
+ file.write(BulkUpload::SalesLogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_2023_csv_row)
file.close
end
@@ -249,7 +234,7 @@ RSpec.describe BulkUpload::Sales::Validator do
let(:log) { build(:sales_log, created_by: user, saledate: Time.zone.local(2022, 5, 1)) }
before do
- file.write(BulkUpload::SalesLogToCsv.new(log:, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
+ file.write(BulkUpload::SalesLogToCsv.new(log:, line_ending: "\r\n", col_offset: 0).to_2023_csv_row)
file.close
end
@@ -262,7 +247,7 @@ RSpec.describe BulkUpload::Sales::Validator do
describe "#total_logs_count?" do
around do |example|
- Timecop.freeze(Time.zone.local(2023, 2, 22)) do
+ Timecop.freeze(Time.zone.local(2023, 10, 22)) do
Singleton.__init__(FormHandler)
example.run
end
@@ -271,7 +256,7 @@ RSpec.describe BulkUpload::Sales::Validator do
end
context "when all logs are valid" do
- let(:target_path) { file_fixture("completed_2022_23_sales_bulk_upload.csv") }
+ let(:target_path) { file_fixture("2023_24_sales_bulk_upload.csv") }
before do
target_array = File.open(target_path).readlines
diff --git a/spec/services/bulk_upload/sales/year2022/csv_parser_spec.rb b/spec/services/bulk_upload/sales/year2022/csv_parser_spec.rb
deleted file mode 100644
index 92fa563d8..000000000
--- a/spec/services/bulk_upload/sales/year2022/csv_parser_spec.rb
+++ /dev/null
@@ -1,150 +0,0 @@
-require "rails_helper"
-
-RSpec.describe BulkUpload::Sales::Year2022::CsvParser do
- subject(:service) { described_class.new(path:) }
-
- let(:path) { file_fixture("completed_2022_23_sales_bulk_upload.csv") }
-
- context "when parsing csv with headers" do
- it "returns correct offsets" do
- expect(service.row_offset).to eq(5)
- expect(service.col_offset).to eq(1)
- end
-
- it "parses csv correctly" do
- expect(service.row_parsers[0].field_7.to_i).to eq(32)
- end
- end
-
- context "when parsing csv without headers" do
- let(:file) { Tempfile.new }
- let(:path) { file.path }
- let(:log) { build(:sales_log, :completed) }
-
- before do
- file.write(BulkUpload::SalesLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
- file.rewind
- end
-
- it "returns correct offsets" do
- expect(service.row_offset).to eq(0)
- expect(service.col_offset).to eq(0)
- end
-
- it "parses csv correctly" do
- expect(service.row_parsers[0].field_7.to_i).to eql(log.age1)
- end
- end
-
- context "when parsing with BOM aka byte order mark" do
- let(:file) { Tempfile.new }
- let(:path) { file.path }
- let(:log) { build(:sales_log, :completed) }
- let(:bom) { "\uFEFF" }
-
- before do
- file.write(bom)
- file.write(BulkUpload::SalesLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
- file.close
- end
-
- it "parses csv correctly" do
- expect(service.row_parsers[0].field_7.to_i).to eql(log.age1)
- end
- end
-
- context "when an invalid byte sequence" do
- let(:file) { Tempfile.new }
- let(:path) { file.path }
- let(:log) { build(:sales_log, :completed) }
- let(:invalid_sequence) { "\x81" }
-
- before do
- file.write(invalid_sequence)
- file.write(BulkUpload::SalesLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
- file.close
- end
-
- it "parses csv correctly" do
- expect(service.row_parsers[0].field_7.to_i).to eql(log.age1)
- end
- end
-
- describe "#column_for_field", aggregate_failures: true do
- context "when headers present" do
- it "returns correct column" do
- expect(service.column_for_field("field_1")).to eql("B")
- expect(service.column_for_field("field_125")).to eql("DV")
- end
- end
-
- context "when no headers" do
- let(:file) { Tempfile.new }
- let(:path) { file.path }
- let(:log) { build(:sales_log, :completed) }
-
- before do
- file.write(BulkUpload::SalesLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
- file.rewind
- end
-
- it "returns correct column" do
- expect(service.column_for_field("field_1")).to eql("A")
- expect(service.column_for_field("field_125")).to eql("DU")
- end
- end
- end
-
- context "when parsing csv with carriage returns" do
- let(:file) { Tempfile.new }
- let(:path) { file.path }
- let(:log) { build(:sales_log, :completed) }
-
- before do
- file.write("Question\r\n")
- file.write("Additional info\r")
- file.write("Values\r\n")
- file.write("Can be empty?\r")
- file.write("Type of letting the question applies to\r\n")
- file.write("Duplicate check field?\r")
- file.write(BulkUpload::SalesLogToCsv.new(log:).to_2022_csv_row)
- file.rewind
- end
-
- it "parses csv correctly" do
- expect(service.column_for_field("field_1")).to eql("A")
- expect(service.column_for_field("field_125")).to eql("DU")
- end
- end
-
- describe "#wrong_template_for_year?" do
- let(:file) { Tempfile.new }
- let(:path) { file.path }
-
- context "when 23/24 file with 23/24 data" do
- let(:log) { build(:sales_log, :completed, saledate: Date.new(2024, 1, 1)) }
-
- before do
- file.write(BulkUpload::SalesLogToCsv.new(log:, col_offset: 0).to_2023_csv_row)
- file.rewind
- end
-
- it "returns true" do
- expect(service).to be_wrong_template_for_year
- end
- end
-
- context "when 22/23 file with 22/23 data" do
- let(:log) { build(:sales_log, :completed, saledate: Date.new(2022, 10, 1)) }
-
- before do
- file.write(BulkUpload::SalesLogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
- file.rewind
- end
-
- it "returns false" do
- expect(service).not_to be_wrong_template_for_year
- end
- end
- end
-end
diff --git a/spec/services/bulk_upload/sales/year2022/row_parser_spec.rb b/spec/services/bulk_upload/sales/year2022/row_parser_spec.rb
deleted file mode 100644
index c305c67ed..000000000
--- a/spec/services/bulk_upload/sales/year2022/row_parser_spec.rb
+++ /dev/null
@@ -1,921 +0,0 @@
-require "rails_helper"
-
-RSpec.describe BulkUpload::Sales::Year2022::RowParser do
- subject(:parser) { described_class.new(attributes) }
-
- let(:now) { Time.zone.parse("01/03/2023") }
-
- let(:attributes) { { bulk_upload: } }
- let(:bulk_upload) { create(:bulk_upload, :sales, user:) }
- let(:user) { create(:user, organisation: owning_org) }
-
- let(:owning_org) { create(:organisation, :with_old_visible_id) }
-
- let(:setup_section_params) do
- {
- bulk_upload:,
- field_1: "test id", # purchase id
- field_92: owning_org.old_visible_id, # organisation
- field_93: user.email, # user
- field_2: now.day.to_s, # sale day
- field_3: now.month.to_s, # sale month
- field_4: now.strftime("%g"), # sale year
- field_113: "1", # owhershipsch
- field_57: "2", # shared ownership sale type
- field_116: "2", # joint purchase
- field_115: "1", # will the buyers live in the property
- }
- end
-
- let(:valid_attributes) do
- {
- bulk_upload:,
- field_1: "test id",
- field_2: "22",
- field_3: "2",
- field_4: "23",
- field_6: "1",
- field_7: "32",
- field_8: "32",
- field_13: "M",
- field_14: "F",
- field_19: "R",
- field_24: "1",
- field_25: "2",
- field_30: "12",
- field_31: "18",
- field_32: "30000",
- field_33: "15000",
- field_34: "1",
- field_35: "1",
- field_36: "20000",
- field_37: "3",
- field_39: "1",
- field_40: "E09000008",
- field_41: "A1",
- field_42: "1AA",
- field_43: "1",
- field_45: "1",
- field_46: "1",
- field_48: "3",
- field_49: "3",
- field_50: "2",
- field_51: "1",
- field_52: "1",
- field_53: "E09000008",
- field_54: "CR0",
- field_55: "4BB",
- field_56: "3",
- field_57: "2",
- field_58: "2",
- field_59: "23",
- field_60: "3",
- field_61: "22",
- field_62: "30",
- field_63: "3",
- field_64: "22",
- field_65: "3",
- field_66: "1",
- field_67: "1",
- field_68: "250000",
- field_69: "25",
- field_70: "42500",
- field_71: "3",
- field_72: "20000",
- field_74: "800",
- field_75: "200",
- field_92: owning_org.old_visible_id,
- field_95: "3",
- field_97: "5",
- field_98: "1",
- field_104: "4",
- field_105: "20",
- field_109: "2",
- field_110: "5",
- field_111: "1",
- field_112: "1",
- field_113: "1",
- field_115: "1",
- field_116: "1",
- field_117: "1",
- field_118: "1",
- field_119: "0",
- field_120: "10",
- field_121: "10",
- field_122: "1",
- field_123: "1",
- }
- end
-
- around do |example|
- Timecop.freeze(Time.zone.local(2023, 2, 22)) do
- Singleton.__init__(FormHandler)
- example.run
- end
- end
-
- describe "#blank_row?" do
- context "when a new object" do
- it "returns true" do
- expect(parser).to be_blank_row
- end
- end
-
- context "when any field is populated" do
- before do
- parser.field_1 = "1"
- end
-
- it "returns false" do
- expect(parser).not_to be_blank_row
- end
- end
- end
-
- describe "purchaser_code" do
- before do
- def purch_id_field
- described_class::QUESTIONS.key("What is the purchaser code?").to_s
- end
- end
-
- let(:attributes) do
- {
- bulk_upload:,
- purch_id_field => "some purchaser code",
- }
- end
-
- it "is linked to the correct field" do
- expect(parser.purchaser_code).to eq("some purchaser code")
- end
- end
-
- describe "previous postcode known" do
- context "when field_43 is 1" do
- let(:attributes) do
- {
- bulk_upload:,
- field_43: 1,
- }
- end
-
- it "sets previous postcode known to yes" do
- expect(parser.log.ppcodenk).to eq(0)
- end
- end
-
- context "when field_43 is nil" do
- let(:attributes) do
- {
- bulk_upload:,
- field_43: nil,
- }
- end
-
- it "sets previous postcode known to no" do
- expect(parser.log.ppcodenk).to eq(1)
- end
- end
- end
-
- describe "validations" do
- before do
- stub_request(:get, /api.postcodes.io/)
- .to_return(status: 200, body: "{\"status\":200,\"result\":{\"admin_district\":\"Manchester\", \"codes\":{\"admin_district\": \"E08000003\"}}}", headers: {})
-
- parser.valid?
- end
-
- describe "#valid?" do
- context "when the row is blank" do
- let(:attributes) { { bulk_upload: } }
-
- it "returns true" do
- expect(parser).to be_valid
- end
- end
-
- context "when calling the method multiple times" do
- let(:attributes) { { bulk_upload:, field_7: 2 } }
-
- it "does not add keep adding errors to the pile" do
- expect { parser.valid? }.not_to change(parser.errors, :count)
- end
- end
-
- context "when valid row" do
- let(:attributes) { valid_attributes }
-
- around do |example|
- Timecop.freeze(Date.new(2023, 2, 22)) do
- example.run
- end
- Timecop.return
- end
-
- it "returns true" do
- expect(parser).to be_valid
- end
-
- it "instantiates a log with everything completed", aggregate_failures: true do
- questions = parser.send(:questions).reject do |q|
- parser.send(:log).optional_fields.include?(q.id) || q.completed?(parser.send(:log))
- end
-
- expect(questions.map(&:id).size).to eq(0)
- expect(questions.map(&:id)).to eql([])
- end
- end
- end
-
- context "when setup section not complete and type is not given" do
- let(:attributes) do
- {
- bulk_upload:,
- field_1: "test id",
- }
- end
-
- it "has errors on correct setup fields" do
- errors = parser.errors.select { |e| e.options[:category] == :setup }.map(&:attribute)
-
- expect(errors).to eql(%i[field_2 field_3 field_4 field_113 field_92 field_112])
- end
- end
-
- context "when setup section not complete and type is shared ownership" do
- let(:attributes) do
- {
- bulk_upload:,
- field_1: "test id",
- field_113: "1",
- }
- end
-
- it "has errors on correct setup fields" do
- errors = parser.errors.select { |e| e.options[:category] == :setup }.map(&:attribute).sort
-
- expect(errors).to eql(%i[field_112 field_116 field_2 field_3 field_4 field_57 field_92])
- end
- end
-
- context "when setup section not complete it's shared ownership joint purchase" do
- let(:attributes) do
- {
- bulk_upload:,
- field_1: "test id",
- field_113: "1",
- field_57: "2",
- field_116: "1",
- }
- end
-
- it "has errors on correct setup fields" do
- errors = parser.errors.select { |e| e.options[:category] == :setup }.map(&:attribute)
-
- expect(errors).to eql(%i[field_2 field_3 field_4 field_109 field_92 field_112])
- end
- end
-
- context "when setup section not complete and type is discounted ownership" do
- let(:attributes) do
- {
- bulk_upload:,
- field_1: "test id",
- field_113: "2",
- }
- end
-
- it "has errors on correct setup fields" do
- errors = parser.errors.select { |e| e.options[:category] == :setup }.map(&:attribute).sort
-
- expect(errors).to eql(%i[field_112 field_116 field_2 field_3 field_4 field_76 field_92])
- end
- end
-
- context "when setup section not complete it's discounted ownership joint purchase" do
- let(:attributes) do
- {
- bulk_upload:,
- field_1: "test id",
- field_113: "2",
- field_76: "8",
- field_116: "1",
- }
- end
-
- it "has errors on correct setup fields" do
- errors = parser.errors.select { |e| e.options[:category] == :setup }.map(&:attribute)
-
- expect(errors).to eql(%i[field_2 field_3 field_4 field_109 field_92 field_112])
- end
- end
-
- context "when setup section not complete and type is outright sale" do
- let(:attributes) do
- {
- bulk_upload:,
- field_1: "test id",
- field_113: "3",
- }
- end
-
- it "has errors on correct setup fields" do
- errors = parser.errors.select { |e| e.options[:category] == :setup }.map(&:attribute).sort
-
- expect(errors).to eql(%i[field_112 field_114 field_115 field_2 field_3 field_4 field_84 field_92])
- end
- end
-
- context "when setup section not complete outright sale buyer is not company" do
- let(:attributes) do
- {
- bulk_upload:,
- field_1: "test id",
- field_84: "12",
- field_85: "other sale type",
- field_113: "3",
- field_114: "2",
- }
- end
-
- it "has errors on correct setup fields" do
- errors = parser.errors.select { |e| e.options[:category] == :setup }.map(&:attribute).sort
-
- expect(errors).to eql(%i[field_2 field_3 field_4 field_115 field_116 field_92 field_112].sort)
- end
- end
-
- describe "#field_44 - 7" do # buyers organisations
- context "when all nil" do
- let(:attributes) { setup_section_params.merge(field_44: nil, field_45: nil, field_46: nil, field_47: nil) }
-
- it "returns correct errors" do
- expect(parser.errors[:field_44]).to be_present
- expect(parser.errors[:field_45]).to be_present
- expect(parser.errors[:field_46]).to be_present
- expect(parser.errors[:field_47]).to be_present
- end
- end
- end
-
- describe "#field_57" do # type of shared ownership scheme
- context "when an invalid option" do
- let(:attributes) { setup_section_params.merge(field_57: "100", field_113: "1") }
-
- it "returns setup error" do
- expect(parser.errors.where(:field_57, category: :setup).map(&:message)).to include("Enter a valid value for what is the type of shared ownership sale?")
- end
- end
- end
-
- describe "#field_76" do # type of discounted ownership scheme
- context "when an invalid option" do
- let(:attributes) { setup_section_params.merge({ field_76: "100", field_113: "2" }) }
-
- it "returns setup error" do
- expect(parser.errors.where(:field_76, category: :setup).map(&:message)).to eql(["Enter a valid value for what is the type of discounted ownership sale?"])
- end
- end
- end
-
- describe "#field_84" do # type of outright sale
- context "when an invalid option" do
- let(:attributes) { setup_section_params.merge({ field_84: "100", field_113: "3" }) }
-
- it "returns setup error" do
- expect(parser.errors.where(:field_84, category: :setup).map(&:message)).to eql(["Enter a valid value for what is the type of outright sale?"])
- end
- end
- end
-
- describe "#field_85" do # type of other outright sale
- context "when cant be blank" do
- let(:attributes) { setup_section_params.merge({ field_85: nil, field_84: "12" }) }
-
- it "returns setup error" do
- expect(parser.errors.where(:field_85, category: :setup).map(&:message)).to eql(["You must answer type of outright sale"])
- end
- end
- end
-
- describe "#field_92" do # owning org
- context "when no data given" do
- let(:attributes) { { bulk_upload:, field_92: "" } }
-
- it "is not permitted as setup error" do
- setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
-
- expect(setup_errors.find { |e| e.attribute == :field_92 }.message).to eql("You must answer owning organisation")
- end
-
- it "blocks log creation" do
- expect(parser).to be_block_log_creation
- end
- end
-
- context "when cannot find owning org" do
- let(:attributes) { { bulk_upload:, field_92: "donotexist" } }
-
- it "is not permitted as a setup error" do
- setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
-
- expect(setup_errors.find { |e| e.attribute == :field_92 }.message).to eql("You must answer owning organisation")
- end
-
- it "blocks log creation" do
- expect(parser).to be_block_log_creation
- end
- end
-
- context "when not affiliated with owning org" do
- let(:unaffiliated_org) { create(:organisation, :with_old_visible_id) }
-
- let(:attributes) { { bulk_upload:, field_92: unaffiliated_org.old_visible_id } }
-
- it "is not permitted as setup error" do
- setup_errors = parser.errors.select { |e| e.options[:category] == :setup }
-
- expect(setup_errors.find { |e| e.attribute == :field_92 }.message).to eql("You do not have permission to add logs for this owning organisation")
- end
-
- it "blocks log creation" do
- expect(parser).to be_block_log_creation
- end
- end
- end
-
- context "when type is shared ownership" do
- let(:attributes) { valid_attributes.merge({ field_113: 1, field_68: nil }) }
-
- it "has error on correct fields" do
- expect(parser.errors).to include(:field_68)
- end
- end
-
- context "when type is discounted ownership" do
- let(:attributes) { valid_attributes.merge({ field_113: 2, field_77: nil }) }
-
- it "has error on correct fields" do
- expect(parser.errors).to include(:field_77)
- end
- end
-
- context "when type is outright sale" do
- let(:attributes) { valid_attributes.merge({ field_113: 3, field_87: nil }) }
-
- it "has error on correct fields" do
- expect(parser.errors).to include(:field_87)
- end
- end
-
- describe "#field_93" do # username for created_by
- context "when blank" do
- let(:attributes) { { bulk_upload:, field_93: "" } }
-
- it "is permitted" do
- expect(parser.errors[:field_93]).to be_blank
- end
- end
-
- context "when user could not be found" do
- let(:attributes) { { bulk_upload:, field_93: "idonotexist@example.com" } }
-
- it "is not permitted" do
- expect(parser.errors[:field_93]).to be_present
- end
- end
-
- context "when an unaffiliated user" do
- let(:other_user) { create(:user) }
-
- let(:attributes) { { bulk_upload:, field_92: owning_org.old_visible_id, field_93: other_user.email } }
-
- it "is not permitted" do
- expect(parser.errors[:field_93]).to be_present
- end
-
- it "blocks log creation" do
- expect(parser).to be_block_log_creation
- end
- end
-
- context "when an user part of owning org" do
- let(:other_user) { create(:user, organisation: owning_org) }
-
- let(:attributes) { { bulk_upload:, field_92: owning_org.old_visible_id, field_93: other_user.email } }
-
- it "is permitted" do
- expect(parser.errors[:field_93]).to be_blank
- end
- end
-
- context "when email matches other than casing" do
- let(:other_user) { create(:user, organisation: owning_org) }
-
- let(:attributes) { { bulk_upload:, field_92: owning_org.old_visible_id, field_93: other_user.email.upcase! } }
-
- it "is permitted" do
- expect(parser.errors[:field_93]).to be_blank
- end
- end
- end
-
- [
- %w[age1_known age1 field_7],
- %w[age2_known age2 field_8],
- %w[age3_known age3 field_9],
- %w[age4_known age4 field_10],
- %w[age5_known age5 field_11],
- %w[age6_known age6 field_12],
- ].each do |known, age, field|
- describe "##{known} and ##{age}" do
- context "when #{field} is blank" do
- let(:attributes) { { bulk_upload:, field.to_s => nil } }
-
- it "sets ##{known} 1" do
- expect(parser.log.public_send(known)).to be(1)
- end
-
- it "sets ##{age} to nil" do
- expect(parser.log.public_send(age)).to be_nil
- end
- end
-
- context "when #{field} is R" do
- let(:attributes) { setup_section_params.merge({ field.to_s => "R", field_6: "1", field_119: "5", field_112: "1" }) }
-
- it "sets ##{known} 1" do
- expect(parser.log.public_send(known)).to be(1)
- end
-
- it "sets ##{age} to nil" do
- expect(parser.log.public_send(age)).to be_nil
- end
- end
-
- context "when #{field} is a number" do
- let(:attributes) { setup_section_params.merge({ field.to_s => "50", field_6: "1", field_119: "5", field_112: "1" }) }
-
- it "sets ##{known} to 0" do
- expect(parser.log.public_send(known)).to be(0)
- end
-
- it "sets ##{age} to given age" do
- expect(parser.log.public_send(age)).to be(50)
- end
- end
-
- context "when #{field} is a non-sensical value" do
- let(:attributes) { setup_section_params.merge({ field.to_s => "A", field_6: "1", field_119: "5", field_112: "1" }) }
-
- it "sets ##{known} to 0" do
- expect(parser.log.public_send(known)).to be(0)
- end
-
- it "sets ##{age} to nil" do
- expect(parser.log.public_send(age)).to be_nil
- end
- end
- end
- end
-
- describe "#field_109" do # more that 2 joint purchasers?
- context "when an invalid option" do
- let(:attributes) { setup_section_params.merge({ field_109: "100", field_116: "1" }) }
-
- it "returns setup error" do
- expect(parser.errors.where(:field_109, category: :setup).map(&:message)).to include("Enter a valid value for are there more than two joint purchasers of this property?")
- end
- end
- end
-
- describe "#field_112" do # data protection
- context "when not accepted" do
- let(:attributes) { setup_section_params.merge(field_112: nil) }
-
- it "returns setup error" do
- expect(parser.errors.where(:field_112, category: :setup).map(&:message)).to eql(["You must answer data protection question"])
- end
- end
- end
-
- describe "#field_113" do # purchase made thru ownership scheme?
- context "when an invalid option" do
- let(:attributes) { setup_section_params.merge({ field_113: "100" }) }
-
- it "returns setup error" do
- expect(parser.errors.where(:field_113, category: :setup).map(&:message)).to include("Enter a valid value for was this purchase made through an ownership scheme?")
- end
- end
- end
-
- describe "#field_114" do # is buyer a company?
- context "when an invalid option" do
- let(:attributes) { setup_section_params.merge({ field_114: "100", field_113: "3" }) }
-
- it "returns setup error" do
- expect(parser.errors.where(:field_114, category: :setup).map(&:message)).to include("Enter a valid value for is the buyer a company?")
- end
- end
- end
-
- describe "#field_115" do # will buyers live in property?
- context "when an invalid option" do
- let(:attributes) { setup_section_params.merge({ field_115: "100", field_113: "3", field_114: "2" }) }
-
- it "returns setup error" do
- expect(parser.errors.where(:field_115, category: :setup).map(&:message)).to include("Enter a valid value for will the buyers live in the property?")
- end
- end
- end
-
- describe "#field_116" do # joint purchase?
- context "when an invalid option" do
- let(:attributes) { setup_section_params.merge({ field_116: "100" }) }
-
- it "returns setup error" do
- expect(parser.errors.where(:field_116, category: :setup).map(&:message)).to include("Enter a valid value for is this a joint purchase?")
- end
- end
- end
-
- describe "#field_117" do
- context "when not a possible value" do
- let(:attributes) { valid_attributes.merge({ field_117: "3" }) }
-
- it "is not valid" do
- expect(parser.errors).to include(:field_117)
- end
- end
- end
-
- describe "field_24" do # ecstat1
- context "when buyer 1 is marked as a child" do
- let(:attributes) { valid_attributes.merge({ field_24: 9 }) }
-
- it "a custom validation is applied" do
- validation_message = "Buyer 1 cannot be a child under 16"
- expect(parser.errors[:field_24]).to include validation_message
- end
- end
- end
-
- describe "fields 2, 3, 4 => saledate" do
- context "when all of these fields are blank" do
- let(:attributes) { setup_section_params.merge({ field_2: nil, field_3: nil, field_4: nil }) }
-
- it "returns them as setup errors" do
- expect(parser.errors.where(:field_2, category: :setup)).to be_present
- expect(parser.errors.where(:field_3, category: :setup)).to be_present
- expect(parser.errors.where(:field_4, category: :setup)).to be_present
- end
- end
-
- context "when one of these fields is blank" do
- let(:attributes) { setup_section_params.merge({ field_2: "1", field_3: "1", field_4: nil }) }
-
- it "returns an error only on blank field" do
- expect(parser.errors[:field_2]).to be_blank
- expect(parser.errors[:field_3]).to be_blank
- expect(parser.errors[:field_4]).to be_present
- end
- end
-
- context "when field 4 is 4 digits instead of 2" do
- let(:attributes) { setup_section_params.merge({ bulk_upload:, field_4: "2022" }) }
-
- it "returns an error" do
- expect(parser.errors[:field_4]).to include("Sale completion year must be 2 digits")
- end
- end
-
- context "when invalid date given" do
- let(:attributes) { setup_section_params.merge({ field_2: "a", field_3: "12", field_4: "2022" }) }
-
- it "does not raise an error" do
- expect { parser.valid? }.not_to raise_error
- end
- end
-
- context "when inside of collection year" do
- let(:attributes) { setup_section_params.merge({ field_2: "1", field_3: "10", field_4: "22" }) }
-
- let(:bulk_upload) { create(:bulk_upload, :sales, user:, year: 2022) }
-
- it "does not return errors" do
- expect(parser.errors[:field_2]).not_to be_present
- expect(parser.errors[:field_3]).not_to be_present
- expect(parser.errors[:field_4]).not_to be_present
- end
- end
-
- context "when outside of collection year" do
- around do |example|
- Timecop.freeze(Date.new(2022, 4, 2)) do
- example.run
- end
- end
-
- let(:attributes) { setup_section_params.merge({ field_2: "1", field_3: "1", field_4: "22" }) }
-
- let(:bulk_upload) { create(:bulk_upload, :sales, user:, year: 2022) }
-
- it "returns setup errors" do
- expect(parser.errors.where(:field_2, category: :setup)).to be_present
- expect(parser.errors.where(:field_3, category: :setup)).to be_present
- expect(parser.errors.where(:field_4, category: :setup)).to be_present
- end
- end
- end
-
- describe "soft validations" do
- context "when soft validation is triggered" do
- let(:attributes) { valid_attributes.merge({ field_7: 22, field_24: 5 }) }
-
- it "adds an error to the relevant fields" do
- expect(parser.errors.where(:field_7, category: :soft_validation)).to be_present
- expect(parser.errors.where(:field_24, category: :soft_validation)).to be_present
- end
-
- it "populates with correct error message" do
- expect(parser.errors.where(:field_7, category: :soft_validation).first.message).to eql("You told us this person is aged 22 years and retired.")
- expect(parser.errors.where(:field_24, category: :soft_validation).first.message).to eql("You told us this person is aged 22 years and retired.")
- end
- end
-
- context "when a soft validation is triggered that relates both to fields that are and are not routed to" do
- let(:attributes) { valid_attributes.merge({ field_123: "2" }) }
-
- it "adds errors to fields that are routed to" do
- expect(parser.errors.where(:field_123, category: :soft_validation)).to be_present
- end
-
- it "does not add errors to fields that are not routed to" do
- expect(parser.errors.where(:field_73, category: :soft_validation)).not_to be_present
- expect(parser.errors.where(:field_70, category: :soft_validation)).not_to be_present
- end
- end
- end
-
- context "when the log already exists in the db" do
- let(:attributes) { valid_attributes }
-
- before do
- parser.log.save!
- parser.instance_variable_set(:@valid, nil)
- end
-
- it "is not a valid row" do
- expect(parser).not_to be_valid
- end
-
- it "adds an error to all (and only) the fields used to determine duplicates" do
- parser.valid?
-
- error_message = "This is a duplicate log"
-
- [
- :field_92, # Owning org
- :field_2, # Sale completion date
- :field_3, # Sale completion date
- :field_4, # Sale completion date
- :field_41, # Postcode
- :field_42, # Postcode
- :field_7, # Buyer 1 age
- :field_13, # Buyer 1 gender
- :field_24, # Buyer 1 working situation
- :field_1, # Purchaser code
- ].each do |field|
- expect(parser.errors[field]).to include(error_message)
- end
- end
- end
-
- context "when a hidden log already exists in db" do
- before do
- parser.log.status = "pending"
- parser.log.skip_update_status = true
- parser.log.save!
- end
-
- it "is a valid row" do
- expect(parser).to be_valid
- end
-
- it "does not add duplicate errors" do
- parser.valid?
-
- [
- :field_92, # Owning org
- :field_2, # Sale completion date
- :field_3, # Sale completion date
- :field_4, # Sale completion date
- :field_41, # Postcode
- :field_42, # Postcode
- :field_7, # Buyer 1 age
- :field_13, # Buyer 1 gender
- :field_24, # Buyer 1 working situation
- :field_1, # Purchaser code
- ].each do |field|
- expect(parser.errors[field]).to be_blank
- end
- end
- end
-
- describe "shared ownership sale type" do
- context "when 32 is selected for shared ownership type" do
- let(:attributes) { valid_attributes.merge(field_113: 1, field_57: "32") }
-
- it "is not permitted as a setup error" do
- expect(parser.errors.where(:field_57, category: :setup)).to be_present
- end
- end
- end
- end
-
- describe "inferences" do
- context "when buyer not interviewed and optional values nil" do
- let(:attributes) { valid_attributes.merge(field_6: "1", field_24: nil, field_30: nil, field_31: nil, field_32: nil, field_34: nil, field_36: nil, field_37: nil, field_39: nil, field_48: nil, field_49: nil) }
-
- it "infers correctly" do
- log = parser.log
- expect(log["noint"]).to eq(1)
- expect(log["ecstat1"]).to eq(10)
- expect(log["ethnic"]).to eq(nil)
- expect(log["ethnic_group"]).to eq(17)
- expect(log["national"]).to eq(13)
- expect(log["income1nk"]).to eq(1)
- expect(log["inc1mort"]).to eq(2)
- expect(log["savingsnk"]).to eq(1)
- expect(log["prevown"]).to eq(3)
- expect(log["prevten"]).to eq(0)
- expect(log["disabled"]).to eq(3)
- expect(log["wheel"]).to eq(3)
- end
- end
-
- context "when buyer not interviewed and optional values present" do
- let(:attributes) { valid_attributes.merge(field_6: "1", field_24: "1", field_30: "1", field_31: "1", field_32: "1", field_34: "1", field_36: "1", field_37: "1", field_39: "1", field_48: "1", field_49: "1") }
-
- it "does not override variables correctly" do
- log = parser.log
- expect(log["noint"]).to eq(1)
- expect(log["ecstat1"]).to eq(1)
- expect(log["ethnic"]).to eq(1)
- expect(log["ethnic_group"]).to eq(0)
- expect(log["national"]).to eq(1)
- expect(log["income1nk"]).to eq(0)
- expect(log["inc1mort"]).to eq(1)
- expect(log["savingsnk"]).to eq(0)
- expect(log["prevown"]).to eq(1)
- expect(log["prevten"]).to eq(1)
- expect(log["disabled"]).to eq(1)
- expect(log["wheel"]).to eq(1)
- end
- end
-
- context "when mscharge is given, but is set to 0 for shared ownership" do
- let(:attributes) { valid_attributes.merge(field_75: "0") }
-
- it "does not override variables correctly" do
- log = parser.log
- expect(log["has_mscharge"]).to eq(0) # no
- expect(log["mscharge"]).to be_nil
- end
- end
-
- context "when mscharge is given, but is set to 0 for discounted ownership" do
- let(:attributes) { valid_attributes.merge(field_113: "2", field_76: "8", field_83: "0") }
-
- it "does not override variables correctly" do
- log = parser.log
- expect(log["has_mscharge"]).to eq(0) # no
- expect(log["mscharge"]).to be_nil
- end
- end
-
- context "when mscharge is given, but is set to 0 for outright sale" do
- let(:attributes) { valid_attributes.merge(field_113: "3", field_91: "0") }
-
- it "does not override variables correctly" do
- log = parser.log
- expect(log["has_mscharge"]).to eq(0) # no
- expect(log["mscharge"]).to be_nil
- end
- end
- end
-
- describe "#spreadsheet_duplicate_hash" do
- it "returns a hash" do
- expect(parser.spreadsheet_duplicate_hash).to be_a(Hash)
- end
- end
-
- describe "#add_duplicate_found_in_spreadsheet_errors" do
- it "adds errors" do
- expect { parser.add_duplicate_found_in_spreadsheet_errors }.to change(parser.errors, :size)
- end
- end
-end