Browse Source

CLDC-2067 Bulk upload for lettings 23/24 (#1417)

* refactor questions from validator to row parser

* able to switch between bulk upload parsers

- depending on what year we are processing

* spec tooling to support bulk upload multi year

* row parser now has year in namespacing

* add static data for 2023 row parser

* add placeholder to log to csv for specs

* bulk upload aribtrary 23/34 column ordering works

* bulk upload supports 23/24 without headers

* bulk upload 23/24 supports BOM + invalid chars

* dupe tests

* port 23/24 attributes_for_log

* port 23/24 bulk upload validations

* force crossover period

* tweak max permitted columns

* able to return column for given field

* work out column for field for errors

* add field_4 as 23/24 setup field

* remove duplicate method

* map schemes and locations correctly

* handle arbitrary number of header rows

* add missing fields to bulk upload support
pull/1419/head
Phil Lee 2 years ago committed by GitHub
parent
commit
35de06e324
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      app/components/bulk_upload_error_row_component.rb
  2. 2
      app/components/bulk_upload_error_summary_table_component.html.erb
  3. 6
      app/components/bulk_upload_error_summary_table_component.rb
  4. 2
      app/controllers/bulk_upload_lettings_logs_controller.rb
  5. 8
      app/mailers/bulk_upload_mailer.rb
  6. 22
      app/models/bulk_upload.rb
  7. 2
      app/models/forms/bulk_upload_lettings/prepare_your_file.rb
  8. 5
      app/models/forms/bulk_upload_lettings/year.rb
  9. 9
      app/services/bulk_upload/lettings/log_creator.rb
  10. 152
      app/services/bulk_upload/lettings/validator.rb
  11. 14
      app/services/bulk_upload/lettings/year2022/csv_parser.rb
  12. 150
      app/services/bulk_upload/lettings/year2022/row_parser.rb
  13. 80
      app/services/bulk_upload/lettings/year2023/csv_parser.rb
  14. 1233
      app/services/bulk_upload/lettings/year2023/row_parser.rb
  15. 176
      app/services/bulk_upload/sales/row_parser.rb
  16. 134
      app/services/bulk_upload/sales/validator.rb
  17. 308
      app/services/bulk_upload/sales/year2022/row_parser.rb
  18. 6
      config/initializers/feature_toggle.rb
  19. 8
      spec/components/bulk_upload_error_summary_table_component_spec.rb
  20. 4
      spec/models/forms/bulk_upload_lettings/year_spec.rb
  21. 8
      spec/services/bulk_upload/lettings/log_creator_spec.rb
  22. 54
      spec/services/bulk_upload/lettings/validator_spec.rb
  23. 33
      spec/services/bulk_upload/lettings/year2022/csv_parser_spec.rb
  24. 2
      spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb
  25. 181
      spec/services/bulk_upload/lettings/year2023/csv_parser_spec.rb
  26. 1282
      spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb
  27. 2
      spec/services/bulk_upload/sales/year2022/row_parser_spec.rb
  28. 49
      spec/support/bulk_upload/log_to_csv.rb

9
app/components/bulk_upload_error_row_component.rb

@ -24,14 +24,7 @@ class BulkUploadErrorRowComponent < ViewComponent::Base
end
def question_for_field(field)
case bulk_upload.log_type
when "lettings"
BulkUpload::Lettings::Validator.question_for_field(field.to_sym)
when "sales"
BulkUpload::Sales::Validator.question_for_field(field.to_sym)
else
"Unknown question"
end
bulk_upload.prefix_namespace::RowParser.question_for_field(field.to_sym)
end
def bulk_upload

2
app/components/bulk_upload_error_summary_table_component.html.erb

@ -16,7 +16,7 @@
<% body.row do |row| %>
<% row.cell(text: error[0][0]) %>
<% row.cell(text: error[1]) %>
<% row.cell(text: BulkUpload::Lettings::Validator.question_for_field(error[0][1].to_sym)) %>
<% row.cell(text: question_for_field(error[0][1].to_sym)) %>
<% row.cell(text: error[0][2]) %>
<% row.cell(text: error[0][1]) %>
<% end %>

6
app/components/bulk_upload_error_summary_table_component.rb

@ -3,6 +3,8 @@ class BulkUploadErrorSummaryTableComponent < ViewComponent::Base
attr_reader :bulk_upload
delegate :question_for_field, to: :row_parser_class
def initialize(bulk_upload:)
@bulk_upload = bulk_upload
@ -27,4 +29,8 @@ private
def display_threshold
DISPLAY_THRESHOLD
end
def row_parser_class
bulk_upload.prefix_namespace::RowParser
end
end

2
app/controllers/bulk_upload_lettings_logs_controller.rb

@ -33,6 +33,8 @@ private
end
def in_crossover_period?
return true if FeatureToggle.force_crossover?
FormHandler.instance.lettings_in_crossover_period?
end

8
app/mailers/bulk_upload_mailer.rb

@ -73,11 +73,7 @@ class BulkUploadMailer < NotifyMailer
start_bulk_upload_sales_logs_url
end
validator_class = if bulk_upload.lettings?
BulkUpload::Lettings::Validator
else
BulkUpload::Sales::Validator
end
row_parser_class = bulk_upload.prefix_namespace::RowParser
errors = bulk_upload
.bulk_upload_errors
@ -87,7 +83,7 @@ class BulkUploadMailer < NotifyMailer
.keys
.sort_by { |_col, field| field }
.map do |col, field|
"- Column #{col} (#{validator_class.question_for_field(field.to_sym)})"
"- Column #{col} (#{row_parser_class.question_for_field(field.to_sym)})"
end
send_email(

22
app/models/bulk_upload.rb

@ -46,6 +46,28 @@ class BulkUpload < ApplicationRecord
needstype == 2
end
def prefix_namespace
type_class = case log_type
when "lettings"
"Lettings"
when "sales"
"Sales"
else
raise "unknown log type"
end
year_class = case year
when 2022
"Year2022"
when 2023
"Year2023"
else
raise "unknown year"
end
"BulkUpload::#{type_class}::#{year_class}".constantize
end
private
def generate_identifier

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

@ -44,6 +44,8 @@ module Forms
private
def in_crossover_period?
return true if FeatureToggle.force_crossover?
FormHandler.instance.lettings_in_crossover_period?
end
end

5
app/models/forms/bulk_upload_lettings/year.rb

@ -34,7 +34,10 @@ module Forms
private
def possible_years
[FormHandler.instance.lettings_forms["current_lettings"].start_date.year, FormHandler.instance.lettings_forms["previous_lettings"].start_date.year]
[
FormHandler.instance.lettings_forms["next_lettings"].start_date.year,
FormHandler.instance.lettings_forms["current_lettings"].start_date.year,
]
end
end
end

9
app/services/bulk_upload/lettings/log_creator.rb

@ -26,7 +26,14 @@ class BulkUpload::Lettings::LogCreator
private
def csv_parser
@csv_parser ||= BulkUpload::Lettings::CsvParser.new(path:)
@csv_parser ||= case bulk_upload.year
when 2022
BulkUpload::Lettings::Year2022::CsvParser.new(path:)
when 2023
BulkUpload::Lettings::Year2023::CsvParser.new(path:)
else
raise "csv parser not found"
end
end
def row_offset

152
app/services/bulk_upload/lettings/validator.rb

@ -6,143 +6,6 @@ class BulkUpload::Lettings::Validator
include ActiveModel::Validations
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 or renewal date?",
field_90: "What is the void or renewal date?",
field_91: "What is the void or renewal 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 includes 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
attr_reader :bulk_upload, :path
validate :validate_file_not_empty
@ -167,7 +30,7 @@ class BulkUpload::Lettings::Validator
property_ref: row_parser.field_100,
row:,
cell: "#{cols[field_number_for_attribute(error.attribute) - col_offset + 1]}#{row}",
col: cols[field_number_for_attribute(error.attribute) - col_offset + 1],
col: csv_parser.column_for_field(error.attribute.to_s),
category: error.options[:category],
)
end
@ -210,7 +73,14 @@ private
end
def csv_parser
@csv_parser ||= BulkUpload::Lettings::CsvParser.new(path:)
@csv_parser ||= case bulk_upload.year
when 2022
BulkUpload::Lettings::Year2022::CsvParser.new(path:)
when 2023
BulkUpload::Lettings::Year2023::CsvParser.new(path:)
else
raise "csv parser not found"
end
end
def row_offset
@ -260,9 +130,9 @@ private
def validate_max_columns
return if halt_validations?
max_row_size = rows.map(&:size).max
column_count = rows.map(&:size).max
errors.add(:file, :max_row_size) if max_row_size > 136
errors.add(:file, :column_count) if column_count > csv_parser.class::MAX_COLUMNS
end
def halt_validations!

14
app/services/bulk_upload/lettings/csv_parser.rb → app/services/bulk_upload/lettings/year2022/csv_parser.rb

@ -1,6 +1,8 @@
require "csv"
class BulkUpload::Lettings::CsvParser
class BulkUpload::Lettings::Year2022::CsvParser
MAX_COLUMNS = 136
attr_reader :path
def initialize(path:)
@ -25,7 +27,7 @@ class BulkUpload::Lettings::CsvParser
headers = ("field_1".."field_134").to_a
hash = Hash[headers.zip(stripped_row)]
BulkUpload::Lettings::RowParser.new(hash)
BulkUpload::Lettings::Year2022::RowParser.new(hash)
end
end
@ -37,8 +39,16 @@ class BulkUpload::Lettings::CsvParser
@rows ||= CSV.parse(normalised_string, row_sep:)
end
def column_for_field(field)
cols[headers.find_index(field) + col_offset]
end
private
def headers
@headers ||= ("field_1".."field_134").to_a
end
def with_headers?
rows[0][0]&.match?(/\D+/)
end

150
app/services/bulk_upload/lettings/row_parser.rb → app/services/bulk_upload/lettings/year2022/row_parser.rb

@ -1,7 +1,144 @@
class BulkUpload::Lettings::RowParser
class BulkUpload::Lettings::Year2022::RowParser
include ActiveModel::Model
include ActiveModel::Attributes
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 or renewal date?",
field_90: "What is the void or renewal date?",
field_91: "What is the void or renewal 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 includes 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 }
@ -188,6 +325,10 @@ class BulkUpload::Lettings::RowParser
validate :validate_location_exists
validate :validate_location_data_given
def self.question_for_field(field)
QUESTIONS[field]
end
def valid?
errors.clear
@ -211,7 +352,12 @@ class BulkUpload::Lettings::RowParser
end
def blank_row?
attribute_set.to_hash.reject { |k, _| %w[bulk_upload block_log_creation].include?(k) }.values.compact.empty?
attribute_set
.to_hash
.reject { |k, _| %w[bulk_upload block_log_creation].include?(k) }
.values
.compact
.empty?
end
def log

80
app/services/bulk_upload/lettings/year2023/csv_parser.rb

@ -0,0 +1,80 @@
require "csv"
class BulkUpload::Lettings::Year2023::CsvParser
MAX_COLUMNS = 143
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".."EL").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::Year2023::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
private
def default_field_numbers
[5, nil, nil, 15, 16, nil, 13, 40, 41, 42, 43, 46, 52, 56, 60, 64, 68, 72, 76, 47, 53, 57, 61, 65, 69, 73, 77, 51, 55, 59, 63, 67, 71, 75, 50, 54, 58, 62, 66, 70, 74, 78, 48, 49, 79, 81, 82, 123, 124, 122, 120, 102, 103, nil, 83, 84, 85, 86, 87, 88, 104, 109, 107, 108, 106, 100, 101, 105, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 126, 128, 129, 130, 131, 132, 127, 125, 133, 134, 33, 34, 35, 36, 37, 38, nil, 7, 8, 9, 28, 14, 32, 29, 30, 31, 26, 27, 25, 23, 24, nil, 1, 3, 2, 80, nil, 121, 44, 89, 98, 92, 95, 90, 91, 93, 94, 97, 96, 99, 10, 11, 12, 45, 39, 6, 4, 17, 18, 19, 20, 21, 22].map { |h| h.present? ? "field_#{h}" : "field_blank" }
end
def field_numbers
@field_numbers ||= if with_headers?
rows[row_offset - 1][col_offset..].map { |h| h.present? ? "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
end
end

1233
app/services/bulk_upload/lettings/year2023/row_parser.rb

File diff suppressed because it is too large Load Diff

176
app/services/bulk_upload/sales/row_parser.rb

@ -1,176 +0,0 @@
class BulkUpload::Sales::RowParser
include ActiveModel::Model
include ActiveModel::Attributes
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, :integer
attribute :field_8, :integer
attribute :field_9, :integer
attribute :field_10, :integer
attribute :field_11, :integer
attribute :field_12, :integer
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, :integer
attribute :field_21, :integer
attribute :field_22, :integer
attribute :field_23, :integer
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, :integer
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_1, presence: true, numericality: { in: (1..12) }
# validates :field_4, numericality: { in: (1..999), allow_blank: true }
# validates :field_4, presence: true, if: :field_4_presence_check
validate :validate_possible_answers
# delegate :valid?, to: :native_object
# delegate :errors, to: :native_object
private
def native_object
@native_object ||= SalesLog.new(attributes_for_log)
end
def field_mapping
{
field_117: :buy1livein,
}
end
def validate_possible_answers
field_mapping.each do |field, attribute|
possible_answers = FormHandler.instance.current_sales_form.questions.find { |q| q.id == attribute.to_s }.answer_options.keys
unless possible_answers.include?(public_send(field))
errors.add(field, "Value supplied is not one of the permitted values")
end
end
end
def attributes_for_log
hash = field_mapping.invert
attributes = {}
hash.map do |k, v|
attributes[k] = public_send(v)
end
attributes
end
# def field_4_presence_check
# [1, 3, 5, 7, 9, 11].include?(field_1)
# end
end

134
app/services/bulk_upload/sales/validator.rb

@ -1,138 +1,6 @@
class BulkUpload::Sales::Validator
include ActiveModel::Validations
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 day of the exchange of contracts date?",
field_62: "What is the day of the practical completion or handover date?",
field_63: "What is the month of the practical completion or handover date?",
field_64: "What is the year of the practical completion or handover 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
def self.question_for_field(field)
QUESTIONS[field]
end
attr_reader :bulk_upload, :path
validate :validate_file_not_empty
@ -193,7 +61,7 @@ private
headers = ("field_1".."field_125").to_a
hash = Hash[headers.zip(stripped_row)]
BulkUpload::Sales::RowParser.new(hash)
BulkUpload::Sales::Year2022::RowParser.new(hash)
end
end

308
app/services/bulk_upload/sales/year2022/row_parser.rb

@ -0,0 +1,308 @@
class BulkUpload::Sales::Year2022::RowParser
include ActiveModel::Model
include ActiveModel::Attributes
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 day of the exchange of contracts date?",
field_62: "What is the day of the practical completion or handover date?",
field_63: "What is the month of the practical completion or handover date?",
field_64: "What is the year of the practical completion or handover 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 :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, :integer
attribute :field_8, :integer
attribute :field_9, :integer
attribute :field_10, :integer
attribute :field_11, :integer
attribute :field_12, :integer
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, :integer
attribute :field_21, :integer
attribute :field_22, :integer
attribute :field_23, :integer
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, :integer
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_1, presence: true, numericality: { in: (1..12) }
# validates :field_4, numericality: { in: (1..999), allow_blank: true }
# validates :field_4, presence: true, if: :field_4_presence_check
validate :validate_possible_answers
# delegate :valid?, to: :native_object
# delegate :errors, to: :native_object
def self.question_for_field(field)
QUESTIONS[field]
end
private
def native_object
@native_object ||= SalesLog.new(attributes_for_log)
end
def field_mapping
{
field_117: :buy1livein,
}
end
def validate_possible_answers
field_mapping.each do |field, attribute|
possible_answers = FormHandler.instance.current_sales_form.questions.find { |q| q.id == attribute.to_s }.answer_options.keys
unless possible_answers.include?(public_send(field))
errors.add(field, "Value supplied is not one of the permitted values")
end
end
end
def attributes_for_log
hash = field_mapping.invert
attributes = {}
hash.map do |k, v|
attributes[k] = public_send(v)
end
attributes
end
# def field_4_presence_check
# [1, 3, 5, 7, 9, 11].include?(field_1)
# end
end

6
config/initializers/feature_toggle.rb

@ -40,6 +40,12 @@ class FeatureToggle
!Rails.env.development?
end
def self.force_crossover?
return false if Rails.env.test?
!Rails.env.production?
end
def self.validate_valid_radio_options?
!(Rails.env.production? || Rails.env.staging?)
end

8
spec/components/bulk_upload_error_summary_table_component_spec.rb

@ -3,7 +3,7 @@ require "rails_helper"
RSpec.describe BulkUploadErrorSummaryTableComponent, type: :component do
subject(:component) { described_class.new(bulk_upload:) }
let(:bulk_upload) { create(:bulk_upload) }
let(:bulk_upload) { create(:bulk_upload, :lettings) }
before do
stub_const("BulkUploadErrorSummaryTableComponent::DISPLAY_THRESHOLD", 0)
@ -52,7 +52,7 @@ RSpec.describe BulkUploadErrorSummaryTableComponent, type: :component do
expect(row_1).to eql([
"A",
"1",
BulkUpload::Lettings::Validator.question_for_field(error_1.field.to_sym),
bulk_upload.prefix_namespace::RowParser.question_for_field(error_1.field.to_sym),
error_1.error,
error_1.field,
])
@ -62,7 +62,7 @@ RSpec.describe BulkUploadErrorSummaryTableComponent, type: :component do
expect(row_2).to eql([
"B",
"1",
BulkUpload::Lettings::Validator.question_for_field(error_2.field.to_sym),
bulk_upload.prefix_namespace::RowParser.question_for_field(error_2.field.to_sym),
error_2.error,
error_2.field,
])
@ -89,7 +89,7 @@ RSpec.describe BulkUploadErrorSummaryTableComponent, type: :component do
expect(row_1).to eql([
"A",
"2",
BulkUpload::Lettings::Validator.question_for_field(error_1.field.to_sym),
bulk_upload.prefix_namespace::RowParser.question_for_field(error_1.field.to_sym),
error_1.error,
error_1.field,
])

4
spec/models/forms/bulk_upload_lettings/year_spec.rb

@ -5,8 +5,8 @@ RSpec.describe Forms::BulkUploadLettings::Year do
describe "#options" do
it "returns correct years" do
expect(form.options.map(&:id)).to eql([2022, 2021])
expect(form.options.map(&:name)).to eql(%w[2022/2023 2021/2022])
expect(form.options.map(&:id)).to eql([2023, 2022])
expect(form.options.map(&:name)).to eql(%w[2023/2024 2022/2023])
end
end
end

8
spec/services/bulk_upload/lettings/log_creator_spec.rb

@ -30,9 +30,9 @@ RSpec.describe BulkUpload::Lettings::LogCreator do
let(:log) { LettingsLog.new }
before do
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
file.rewind
end
@ -61,7 +61,7 @@ RSpec.describe BulkUpload::Lettings::LogCreator do
end
before do
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
file.rewind
end

54
spec/services/bulk_upload/lettings/validator_spec.rb

@ -82,7 +82,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
let(:path) { file.path }
before do
file.write(BulkUpload::LogToCsv.new(log:, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log:, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.close
end
@ -122,8 +122,8 @@ RSpec.describe BulkUpload::Lettings::Validator do
let(:log_2) { build(:lettings_log, :completed, created_by: user) }
before do
file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0, overrides: { illness: 100 }).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0, overrides: { illness: 100 }).to_2022_csv_row)
file.close
end
@ -138,8 +138,8 @@ RSpec.describe BulkUpload::Lettings::Validator do
let(:log_2) { build(:lettings_log, :completed, renttype: 1, created_by: user) }
before do
file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.close
end
@ -155,7 +155,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::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.close
end
@ -169,7 +169,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::LogToCsv.new(log:, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log:, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.close
end
@ -188,11 +188,11 @@ RSpec.describe BulkUpload::Lettings::Validator do
let(:log_5) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
before do
file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_3, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_4, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_5, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_3, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_4, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_5, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.close
end
@ -210,11 +210,11 @@ RSpec.describe BulkUpload::Lettings::Validator do
let(:log_5) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
before do
file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_3, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_4, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_5, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_3, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_4, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_5, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.close
end
@ -238,11 +238,11 @@ RSpec.describe BulkUpload::Lettings::Validator do
let(:log_5) { build(:lettings_log, renttype: 2, created_by: user, builtype: nil, startdate: Time.zone.local(2022, 5, 1)) }
before do
file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_3, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_4, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_5, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_3, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_4, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_5, line_ending: "\r\n", col_offset: 0).to_2022_csv_row)
file.close
end
@ -262,11 +262,11 @@ RSpec.describe BulkUpload::Lettings::Validator do
before do
overrides = { age1: 50, age2: "R", age3: "R", age4: "4", age5: "R", age6: "R", age7: "R", age8: "R" }
file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0, overrides:).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0, overrides:).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_3, line_ending: "\r\n", col_offset: 0, overrides:).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_4, line_ending: "\r\n", col_offset: 0, overrides:).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_5, line_ending: "\r\n", col_offset: 0, overrides:).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0, overrides:).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_2, line_ending: "\r\n", col_offset: 0, overrides:).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_3, line_ending: "\r\n", col_offset: 0, overrides:).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_4, line_ending: "\r\n", col_offset: 0, overrides:).to_2022_csv_row)
file.write(BulkUpload::LogToCsv.new(log: log_5, line_ending: "\r\n", col_offset: 0, overrides:).to_2022_csv_row)
file.close
end

33
spec/services/bulk_upload/lettings/csv_parser_spec.rb → spec/services/bulk_upload/lettings/year2022/csv_parser_spec.rb

@ -1,6 +1,6 @@
require "rails_helper"
RSpec.describe BulkUpload::Lettings::CsvParser do
RSpec.describe BulkUpload::Lettings::Year2022::CsvParser do
subject(:service) { described_class.new(path:) }
let(:path) { file_fixture("2022_23_lettings_bulk_upload.csv") }
@ -22,7 +22,7 @@ RSpec.describe BulkUpload::Lettings::CsvParser do
let(:log) { build(:lettings_log, :completed) }
before do
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
file.rewind
end
@ -44,7 +44,7 @@ RSpec.describe BulkUpload::Lettings::CsvParser do
before do
file.write(bom)
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
file.close
end
@ -61,7 +61,7 @@ RSpec.describe BulkUpload::Lettings::CsvParser do
before do
file.write(invalid_sequence)
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_2022_csv_row)
file.close
end
@ -69,4 +69,29 @@ RSpec.describe BulkUpload::Lettings::CsvParser do
expect(service.row_parsers[0].field_12.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_134")).to eql("EE")
end
end
context "when no headers" do
let(:file) { Tempfile.new }
let(:path) { file.path }
let(:log) { build(:lettings_log, :completed) }
before do
file.write(BulkUpload::LogToCsv.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_134")).to eql("ED")
end
end
end
end

2
spec/services/bulk_upload/lettings/row_parser_spec.rb → spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb

@ -1,6 +1,6 @@
require "rails_helper"
RSpec.describe BulkUpload::Lettings::RowParser do
RSpec.describe BulkUpload::Lettings::Year2022::RowParser do
subject(:parser) { described_class.new(attributes) }
let(:now) { Time.zone.today }

181
spec/services/bulk_upload/lettings/year2023/csv_parser_spec.rb

@ -0,0 +1,181 @@
require "rails_helper"
RSpec.describe BulkUpload::Lettings::Year2023::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("Question\n")
file.write("Additional info\n")
file.write("Values\n")
file.write("Can be empty?\n")
file.write("Type of letting the question applies to\n")
file.write("Duplicate check field?\n")
file.write(BulkUpload::LogToCsv.new(log:).default_2023_field_numbers_row)
file.write(BulkUpload::LogToCsv.new(log:).to_2023_csv_row)
file.rewind
end
it "returns correct offsets" do
expect(service.row_offset).to eq(7)
expect(service.col_offset).to eq(1)
end
it "parses csv correctly" do
expect(service.row_parsers[0].field_13).to eql(log.tenancycode)
end
end
context "when parsing csv with headers with extra rows" do
before do
file.write("Section\n")
file.write("Question\n")
file.write("Additional info\n")
file.write("Values\n")
file.write("Can be empty?\n")
file.write("Type of letting the question applies to\n")
file.write("Duplicate check field?\n")
file.write(BulkUpload::LogToCsv.new(log:).default_2023_field_numbers_row)
file.write(BulkUpload::LogToCsv.new(log:).to_2023_csv_row)
file.rewind
end
it "returns correct offsets" do
expect(service.row_offset).to eq(8)
expect(service.col_offset).to eq(1)
end
it "parses csv correctly" do
expect(service.row_parsers[0].field_13).to eql(log.tenancycode)
end
end
context "when parsing csv with headers in arbitrary order" do
let(:seed) { rand }
before do
file.write("Question\n")
file.write("Additional info\n")
file.write("Values\n")
file.write("Can be empty?\n")
file.write("Type of letting the question applies to\n")
file.write("Duplicate check field?\n")
file.write(BulkUpload::LogToCsv.new(log:).default_2023_field_numbers_row(seed:))
file.write(BulkUpload::LogToCsv.new(log:).to_2023_csv_row(seed:))
file.rewind
end
it "returns correct offsets" do
expect(service.row_offset).to eq(7)
expect(service.col_offset).to eq(1)
end
it "parses csv correctly" do
expect(service.row_parsers[0].field_13).to eql(log.tenancycode)
end
end
context "when parsing csv without headers" do
before do
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_2023_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_13).to eql(log.tenancycode)
end
end
context "when parsing with BOM aka byte order mark" do
let(:bom) { "\uFEFF" }
before do
file.write(bom)
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_2023_csv_row)
file.rewind
end
it "parses csv correctly" do
expect(service.row_parsers[0].field_13).to eql(log.tenancycode)
end
end
context "when an invalid byte sequence" do
let(:invalid_sequence) { "\x81" }
before do
file.write(invalid_sequence)
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_2023_csv_row)
file.rewind
end
it "parses csv correctly" do
expect(service.row_parsers[0].field_13).to eql(log.tenancycode)
end
end
describe "#column_for_field", aggregate_failures: true do
context "when with headers using default ordering" do
before do
file.write("Question\n")
file.write("Additional info\n")
file.write("Values\n")
file.write("Can be empty?\n")
file.write("Type of letting the question applies to\n")
file.write("Duplicate check field?\n")
file.write(BulkUpload::LogToCsv.new(log:).default_2023_field_numbers_row)
file.write(BulkUpload::LogToCsv.new(log:).to_2023_csv_row)
file.rewind
end
it "returns correct column" do
expect(service.column_for_field("field_5")).to eql("B")
expect(service.column_for_field("field_22")).to eql("EL")
end
end
context "when without headers using default ordering" do
before do
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_2023_csv_row)
file.rewind
end
it "returns correct column" do
expect(service.column_for_field("field_5")).to eql("A")
expect(service.column_for_field("field_22")).to eql("EK")
end
end
context "when with headers using custom ordering" do
let(:seed) { 123 }
before do
file.write("Question\n")
file.write("Additional info\n")
file.write("Values\n")
file.write("Can be empty?\n")
file.write("Type of letting the question applies to\n")
file.write("Duplicate check field?\n")
file.write(BulkUpload::LogToCsv.new(log:).default_2023_field_numbers_row(seed:))
file.write(BulkUpload::LogToCsv.new(log:).to_2023_csv_row(seed:))
file.rewind
end
it "returns correct column" do
expect(service.column_for_field("field_5")).to eql("N")
expect(service.column_for_field("field_22")).to eql("O")
expect(service.column_for_field("field_26")).to eql("B")
expect(service.column_for_field("field_25")).to eql("EF")
end
end
end
end

1282
spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb

File diff suppressed because it is too large Load Diff

2
spec/services/bulk_upload/sales/row_parser_spec.rb → spec/services/bulk_upload/sales/year2022/row_parser_spec.rb

@ -1,6 +1,6 @@
require "rails_helper"
RSpec.describe BulkUpload::Sales::RowParser do
RSpec.describe BulkUpload::Sales::Year2022::RowParser do
subject(:parser) { described_class.new(attributes) }
describe "validations" do

49
spec/support/bulk_upload/log_to_csv.rb

@ -8,9 +8,49 @@ class BulkUpload::LogToCsv
@overrides = overrides
end
def to_csv_row
def row_prefix
[nil] * col_offset
end
def to_2022_csv_row
(row_prefix + to_2022_row).flatten.join(",") + line_ending
end
def to_2023_csv_row(seed: nil)
if seed
row = to_2023_row.shuffle(random: Random.new(seed))
(row_prefix + row).flatten.join(",") + line_ending
else
(row_prefix + to_2023_row).flatten.join(",") + line_ending
end
end
def to_2023_row
to_2022_row + [
log.needstype,
log.location&.id,
nil, # uprn,
nil, # address_line_1,
nil, # address_line_2,
nil, # town_or_city,
nil, # county,
]
end
def default_2023_field_numbers_row(seed: nil)
if seed
["Bulk upload field number"] + default_2023_field_numbers.shuffle(random: Random.new(seed))
else
["Bulk upload field number"] + default_2023_field_numbers
end.flatten.join(",") + line_ending
end
def default_2023_field_numbers
[5, nil, nil, 15, 16, nil, 13, 40, 41, 42, 43, 46, 52, 56, 60, 64, 68, 72, 76, 47, 53, 57, 61, 65, 69, 73, 77, 51, 55, 59, 63, 67, 71, 75, 50, 54, 58, 62, 66, 70, 74, 78, 48, 49, 79, 81, 82, 123, 124, 122, 120, 102, 103, nil, 83, 84, 85, 86, 87, 88, 104, 109, 107, 108, 106, 100, 101, 105, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 126, 128, 129, 130, 131, 132, 127, 125, 133, 134, 33, 34, 35, 36, 37, 38, nil, 7, 8, 9, 28, 14, 32, 29, 30, 31, 26, 27, 25, 23, 24, nil, 1, 3, 2, 80, nil, 121, 44, 89, 98, 92, 95, 90, 91, 93, 94, 97, 96, 99, 10, 11, 12, 45, 39, 6, 4, 17, 18, 19, 20, 21, 22]
end
def to_2022_row
[
[nil] * col_offset, # 0
log.renttype, # 1
nil,
nil,
@ -155,10 +195,11 @@ class BulkUpload::LogToCsv
log.declaration,
log.joint,
renewal,
line_ending,
].flatten.join(",")
]
end
private
def renewal
checkbox_value(log.renewal)
end

Loading…
Cancel
Save