Browse Source

CLDC-3803 Add lettings 25/26 BU (#2939)

* Copy row and csv parser for 2025 lettings

* Add 2025 BU translations file

* Update row parser field number

* Add "apecified accommodation" to rent type mapping

* Update csv parser

* Add prepare your file and connect the csv parser to log creator and validator

* Update test file creation

* Refactor some csv methods, add person relationship mapping

* Update row parser specs

* Further refactor some test helper methods

* Merge update
main
kosiakkatrina 4 days ago committed by GitHub
parent
commit
71c3f41bf9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 7
      app/components/create_log_actions_component.html.erb
  2. 4
      app/components/create_log_actions_component.rb
  3. 12
      app/controllers/test_data_controller.rb
  4. 253
      app/helpers/bulk_upload/lettings_log_to_csv.rb
  5. 2
      app/services/bulk_upload/lettings/log_creator.rb
  6. 2
      app/services/bulk_upload/lettings/validator.rb
  7. 122
      app/services/bulk_upload/lettings/year2025/csv_parser.rb
  8. 1654
      app/services/bulk_upload/lettings/year2025/row_parser.rb
  9. 60
      config/locales/validations/lettings/2025/bulk_upload.en.yml
  10. 2
      config/routes.rb
  11. 4
      spec/services/bulk_upload/lettings/validator_spec.rb
  12. 32
      spec/services/bulk_upload/lettings/year2023/csv_parser_spec.rb
  13. 36
      spec/services/bulk_upload/lettings/year2024/csv_parser_spec.rb
  14. 254
      spec/services/bulk_upload/lettings/year2025/csv_parser_spec.rb
  15. 2808
      spec/services/bulk_upload/lettings/year2025/row_parser_spec.rb

7
app/components/create_log_actions_component.html.erb

@ -9,9 +9,10 @@
<% end %> <% end %>
<% if FeatureToggle.create_test_logs_enabled? %> <% if FeatureToggle.create_test_logs_enabled? %>
<%= govuk_link_to "Create test log", create_test_log_href %> <%= govuk_link_to "New test log", create_test_log_href %>
<%= govuk_link_to "Create test log (setup only)", create_setup_test_log_href %> <%= govuk_link_to "New test log (setup only)", create_setup_test_log_href %>
<%= govuk_link_to "Get test BU file (2024)", create_2024_test_bulk_upload_href %> <%= govuk_link_to "24 BU test file", create_test_bulk_upload_href(2024) %>
<%= govuk_link_to "25 BU test file", create_test_bulk_upload_href(2025) %>
<% end %> <% end %>
<% end %> <% end %>
</div> </div>

4
app/components/create_log_actions_component.rb

@ -42,8 +42,8 @@ class CreateLogActionsComponent < ViewComponent::Base
send("create_setup_test_#{log_type}_log_path") send("create_setup_test_#{log_type}_log_path")
end end
def create_2024_test_bulk_upload_href def create_test_bulk_upload_href(year)
send("create_2024_test_#{log_type}_bulk_upload_path") send("create_#{year}_test_#{log_type}_bulk_upload_path")
end end
def view_uploads_button_copy def view_uploads_button_copy

12
app/controllers/test_data_controller.rb

@ -15,23 +15,27 @@ class TestDataController < ApplicationController
redirect_to lettings_log_path(log) redirect_to lettings_log_path(log)
end end
def create_2024_test_lettings_bulk_upload %w[2024 2025].each do |year|
define_method("create_#{year}_test_lettings_bulk_upload") do
return render_not_found unless FeatureToggle.create_test_logs_enabled? return render_not_found unless FeatureToggle.create_test_logs_enabled?
file = Tempfile.new("test_lettings_log.csv") file = Tempfile.new("#{year}_test_lettings_log.csv")
log = FactoryBot.create(:lettings_log, :completed, assigned_to: current_user, ppostcode_full: "SW1A 1AA") log = FactoryBot.create(:lettings_log, :completed, assigned_to: current_user, ppostcode_full: "SW1A 1AA", startdate: Time.zone.local(year.to_i, rand(4..12), rand(1..28)))
log_to_csv = BulkUpload::LettingsLogToCsv.new(log:, line_ending: "\n", overrides: { organisation_id: "ORG#{log.owning_organisation_id}", managing_organisation_id: "ORG#{log.owning_organisation_id}" }) log_to_csv = BulkUpload::LettingsLogToCsv.new(log:, line_ending: "\n", overrides: { organisation_id: "ORG#{log.owning_organisation_id}", managing_organisation_id: "ORG#{log.owning_organisation_id}" })
file.write(log_to_csv.default_field_numbers_row) file.write(log_to_csv.default_field_numbers_row)
file.write(log_to_csv.to_csv_row) file.write(log_to_csv.to_csv_row)
file.rewind file.rewind
send_file file.path, type: "text/csv", send_file file.path, type: "text/csv",
filename: "test_lettings_log.csv", filename: "#{year}_test_lettings_log.csv",
disposition: "attachment", disposition: "attachment",
after_send: lambda { after_send: lambda {
file.close file.close
file.unlink file.unlink
} }
end end
end
def create_2025_test_sales_bulk_upload; end
def create_test_sales_log def create_test_sales_log
return render_not_found unless FeatureToggle.create_test_logs_enabled? return render_not_found unless FeatureToggle.create_test_logs_enabled?

253
app/helpers/bulk_upload/lettings_log_to_csv.rb

@ -17,12 +17,8 @@ class BulkUpload::LettingsLogToCsv
def to_csv_row(seed: nil) def to_csv_row(seed: nil)
year = log.collection_start_year year = log.collection_start_year
case year case year
when 2022 when 2022, 2023, 2024, 2025
to_2022_csv_row(seed:) to_year_csv_row(year, seed:)
when 2023
to_2023_csv_row(seed:)
when 2024
to_2024_csv_row(seed:)
else else
raise NotImplementedError "No mapping function implemented for year #{year}" raise NotImplementedError "No mapping function implemented for year #{year}"
end end
@ -30,82 +26,32 @@ class BulkUpload::LettingsLogToCsv
def to_row def to_row
year = log.collection_start_year year = log.collection_start_year
case year send("to_#{year}_row")
when 2022 rescue NoMethodError
to_2022_row
when 2023
to_2023_row
when 2024
to_2024_row
else
raise NotImplementedError "No mapping function implemented for year #{year}" raise NotImplementedError "No mapping function implemented for year #{year}"
end end
end
def default_field_numbers_row(seed: nil) def default_field_numbers_row(seed: nil)
year = log.collection_start_year year = log.collection_start_year
case year default_field_numbers_row_for_year(year, seed:)
when 2022 rescue NoMethodError
default_2022_field_numbers_row(seed:)
when 2023
default_2023_field_numbers_row(seed:)
when 2024
default_2024_field_numbers_row(seed:)
else
raise NotImplementedError "No mapping function implemented for year #{year}" raise NotImplementedError "No mapping function implemented for year #{year}"
end end
end
def default_field_numbers def default_field_numbers
year = log.collection_start_year year = log.collection_start_year
case year send("default_#{year}_field_numbers")
when 2022 rescue NoMethodError
default_2022_field_numbers
when 2023
default_2023_field_numbers
when 2024
default_2024_field_numbers
else
raise NotImplementedError "No mapping function implemented for year #{year}" raise NotImplementedError "No mapping function implemented for year #{year}"
end end
end
def to_2022_csv_row(seed: nil) def to_year_csv_row(year, seed: nil)
unshuffled_row = send("to_#{year}_row")
if seed if seed
row = to_2022_row.shuffle(random: Random.new(seed)) row = unshuffled_row.shuffle(random: Random.new(seed))
(row_prefix + row).flatten.join(",") + line_ending (row_prefix + row).flatten.join(",") + line_ending
else else
(row_prefix + to_2022_row).flatten.join(",") + line_ending (row_prefix + unshuffled_row).flatten.join(",") + line_ending
end
end
def default_2022_field_numbers
(1..134).to_a
end
def default_2022_field_numbers_row(seed: nil)
if seed
["Field number"] + default_2022_field_numbers.shuffle(random: Random.new(seed))
else
["Field number"] + default_2022_field_numbers
end.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_2024_csv_row(seed: nil)
if seed
row = to_2024_row.shuffle(random: Random.new(seed))
(row_prefix + row).flatten.join(",") + line_ending
else
(row_prefix + to_2024_row).flatten.join(",") + line_ending
end end
end end
@ -121,20 +67,16 @@ class BulkUpload::LettingsLogToCsv
] ]
end end
def default_2023_field_numbers_row(seed: nil) def default_field_numbers_row_for_year(year, seed: nil)
if seed if seed
["Field number"] + default_2023_field_numbers.shuffle(random: Random.new(seed)) ["Field number"] + send("default_#{year}_field_numbers").shuffle(random: Random.new(seed))
else else
["Field number"] + default_2023_field_numbers ["Field number"] + send("default_#{year}_field_numbers")
end.flatten.join(",") + line_ending end.flatten.join(",") + line_ending
end end
def default_2024_field_numbers_row(seed: nil) def default_2022_field_numbers
if seed (1..134).to_a
["Field number"] + default_2024_field_numbers.shuffle(random: Random.new(seed))
else
["Field number"] + default_2024_field_numbers
end.flatten.join(",") + line_ending
end end
def default_2023_field_numbers def default_2023_field_numbers
@ -145,6 +87,156 @@ class BulkUpload::LettingsLogToCsv
(1..130).to_a (1..130).to_a
end end
def default_2025_field_numbers
(1..129).to_a
end
def to_2025_row
[
overrides[:organisation_id] || log.owning_organisation&.old_visible_id, # 1
overrides[:managing_organisation_id] || log.managing_organisation&.old_visible_id,
log.assigned_to&.email,
log.needstype,
log.scheme&.id ? "S#{log.scheme&.id}" : "",
log.location&.id,
renewal,
log.startdate&.day,
log.startdate&.month,
log.startdate&.strftime("%y"), # 10
rent_type,
log.irproduct_other,
log.tenancycode,
log.propcode,
log.declaration,
log.rsnvac,
log.unitletas,
log.uprn,
log.address_line1&.tr(",", " "),
log.address_line2&.tr(",", " "), # 20
log.town_or_city&.tr(",", " "),
log.county&.tr(",", " "),
((log.postcode_full || "").split(" ") || [""]).first,
((log.postcode_full || "").split(" ") || [""]).last,
log.la,
log.unittype_gn,
log.builtype,
log.wchair,
log.beds,
log.voiddate&.day, # 30
log.voiddate&.month,
log.voiddate&.strftime("%y"),
log.mrcdate&.day,
log.mrcdate&.month,
log.mrcdate&.strftime("%y"),
log.sheltered,
log.joint,
log.startertenancy,
log.tenancy,
log.tenancyother, # 40
log.tenancylength,
log.age1 || overrides[:age1],
log.sex1,
log.ethnic,
log.nationality_all_group,
log.ecstat1,
relat_number(log.relat2),
log.age2 || overrides[:age2],
log.sex2,
log.ecstat2, # 50
relat_number(log.relat3),
log.age3 || overrides[:age3],
log.sex3,
log.ecstat3,
relat_number(log.relat4),
log.age4 || overrides[:age4],
log.sex4,
log.ecstat4,
relat_number(log.relat5),
log.age5 || overrides[:age5], # 60
log.sex5,
log.ecstat5,
relat_number(log.relat6),
log.age6 || overrides[:age6],
log.sex6,
log.ecstat6,
relat_number(log.relat7),
log.age7 || overrides[:age7],
log.sex7,
log.ecstat7, # 70
relat_number(log.relat8),
log.age8 || overrides[:age8],
log.sex8,
log.ecstat8,
log.armedforces,
log.leftreg,
log.reservist,
log.preg_occ,
log.housingneeds_a,
log.housingneeds_b, # 80
log.housingneeds_c,
log.housingneeds_f,
log.housingneeds_g,
log.housingneeds_h,
overrides[:illness] || log.illness,
log.illness_type_1,
log.illness_type_2,
log.illness_type_3,
log.illness_type_4,
log.illness_type_5, # 90
log.illness_type_6,
log.illness_type_7,
log.illness_type_8,
log.illness_type_9,
log.illness_type_10,
log.layear,
log.waityear,
log.reason,
log.reasonother,
log.prevten, # 100
homeless,
previous_postcode_known,
((log.ppostcode_full || "").split(" ") || [""]).first,
((log.ppostcode_full || "").split(" ") || [""]).last,
log.prevloc,
log.reasonpref,
log.rp_homeless,
log.rp_insan_unsat,
log.rp_medwel,
log.rp_hardship, # 110
log.rp_dontknow,
cbl,
chr,
cap,
accessible_register,
log.referral,
net_income_known,
log.incfreq,
log.earnings,
log.hb, # 120
log.benefits,
log.household_charge,
log.period,
log.brent,
log.scharge,
log.pscharge,
log.supcharg,
log.hbrentshortfall,
log.tshortfall, # 129
]
end
def to_2024_row def to_2024_row
[ [
overrides[:organisation_id] || log.owning_organisation&.old_visible_id, # 1 overrides[:organisation_id] || log.owning_organisation&.old_visible_id, # 1
@ -551,4 +643,15 @@ private
log.hhregres log.hhregres
end end
end end
def relat_number(value)
case value
when "P"
1
when "R"
3
when "C", "X"
2
end
end
end end

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

@ -34,6 +34,8 @@ private
BulkUpload::Lettings::Year2023::CsvParser.new(path:) BulkUpload::Lettings::Year2023::CsvParser.new(path:)
when 2024 when 2024
BulkUpload::Lettings::Year2024::CsvParser.new(path:) BulkUpload::Lettings::Year2024::CsvParser.new(path:)
when 2025
BulkUpload::Lettings::Year2025::CsvParser.new(path:)
else else
raise "csv parser not found" raise "csv parser not found"
end end

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

@ -111,6 +111,8 @@ private
BulkUpload::Lettings::Year2023::CsvParser.new(path:) BulkUpload::Lettings::Year2023::CsvParser.new(path:)
when 2024 when 2024
BulkUpload::Lettings::Year2024::CsvParser.new(path:) BulkUpload::Lettings::Year2024::CsvParser.new(path:)
when 2025
BulkUpload::Lettings::Year2025::CsvParser.new(path:)
else else
raise "csv parser not found" raise "csv parser not found"
end end

122
app/services/bulk_upload/lettings/year2025/csv_parser.rb

@ -0,0 +1,122 @@
require "csv"
class BulkUpload::Lettings::Year2025::CsvParser
include CollectionTimeHelper
FIELDS = 129
MAX_COLUMNS = 130
FORM_YEAR = 2025
attr_reader :path
def initialize(path:)
@path = path
end
def row_offset
if with_headers?
rows.find_index { |row| row[0].present? && row[0].match(/field number/i) } + 1
else
0
end
end
def col_offset
with_headers? ? 1 : 0
end
def cols
@cols ||= ("A".."DZ").to_a
end
def row_parsers
@row_parsers ||= body_rows.map { |row|
next if row.empty?
stripped_row = row[col_offset..]
hash = Hash[field_numbers.zip(stripped_row)]
BulkUpload::Lettings::Year2025::RowParser.new(hash)
}.compact
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?
collection_start_year_for_date(first_record_start_date) != FORM_YEAR
rescue Date::Error
false
end
def missing_required_headers?
!with_headers?
end
private
def default_field_numbers
(1..FIELDS).map { |h| h.present? && h.to_s.match?(/^[0-9]+$/) ? "field_#{h}" : "field_blank" }
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
def first_record_start_date
if with_headers?
year = row_parsers.first.field_10.to_s.strip.length.between?(1, 2) ? row_parsers.first.field_10.to_i + 2000 : row_parsers.first.field_10.to_i
Date.new(year, row_parsers.first.field_9.to_i, row_parsers.first.field_8.to_i)
else
year = rows.first[9].to_s.strip.length.between?(1, 2) ? rows.first[9].to_i + 2000 : rows.first[9].to_i
Date.new(year, rows.first[8].to_i, rows.first[7].to_i)
end
end
end

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

File diff suppressed because it is too large Load Diff

60
config/locales/validations/lettings/2025/bulk_upload.en.yml

@ -0,0 +1,60 @@
en:
validations:
lettings:
2025:
bulk_upload:
not_answered: "You must answer %{question}"
invalid_option: "Enter a valid value for %{question}"
invalid_number: "Enter a number for %{question}"
spreadsheet_dupe: "This is a duplicate of a log in your file."
duplicate: "This is a duplicate log."
blank_file: "Template is blank - The template must be filled in for us to create the logs and check if data is correct."
wrong_template:
wrong_template: "Incorrect start dates, please ensure you have used the correct template."
no_headers: "Your file does not contain the required header rows. Add or check the header rows and upload your file again. [Read more about using the template headers](%{guidance_link})."
wrong_field_numbers_count: "Incorrect number of fields, please ensure you have used the correct template."
over_max_column_count: "Too many columns, please ensure you have used the correct template."
owning_organisation:
not_found: "The owning organisation code is incorrect."
not_stock_owner: "The owning organisation code provided is for an organisation that does not own stock."
not_permitted:
not_support: "You do not have permission to add logs for this owning organisation."
support: "This owning organisation is not affiliated with %{org_name}."
managing_organisation:
no_relationship: "This managing organisation does not have a relationship with the owning organisation."
not_found: "The managing organisation code is incorrect."
assigned_to:
not_found: "User with the specified email could not be found."
organisation_not_related: "User must be related to owning organisation or managing organisation."
startdate:
outside_collection_window: "Enter a date within the %{year_combo} collection year, which is between 1st April %{start_year} and 31st March %{end_year}."
year_not_two_or_four_digits: "Tenancy start year must be 2 or 4 digits."
housingneeds:
no_and_dont_know_disabled_needs_conjunction: "No disabled access needs and don’t know disabled access needs cannot be selected together."
dont_know_disabled_needs_conjunction: "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."
no_disabled_needs_conjunction: "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."
housingneeds_type:
only_one_option_permitted: "Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected."
condition_effects:
no_choices: "You cannot answer this question as you told us nobody in the household has a physical or mental health condition (or other illness) expected to last 12 months or more."
reason:
renewal_reason_needed: "The reason for leaving must be \"End of social or private sector tenancy - no fault\", \"End of social or private sector tenancy - evicted due to anti-social behaviour (ASB)\", \"End of social or private sector tenancy - evicted due to rent arrears\" or \"End of social or private sector tenancy - evicted for any other reason\"."
referral:
general_needs_prp_referred_by_la: "The source of the referral cannot be referred by local authority housing department for a general needs log."
nominated_by_local_ha_but_la: "The source of the referral cannot be Nominated by local housing authority as your organisation is a local authority."
scheme:
must_relate_to_org: "This scheme code does not belong to the owning organisation or managing organisation."
location:
must_relate_to_org: "Location code must relate to a location that is owned by the owning organisation or managing organisation."
age:
invalid: "Age of person %{person_num} must be a number or the letter R"
address:
not_found: "We could not find this address. Check the address data in your CSV file is correct and complete, or find the correct address in the service."
not_determined:
one: "There is a possible match for this address which doesn't look right. Check the address data in your CSV file is correct and complete, or confirm the address in the service."
multiple: "There are multiple matches for this address. Check the address data in your CSV file is correct and complete, or select the correct address in the service."
not_answered: "Enter either the UPRN or the full address."
nationality:
invalid: "Select a valid nationality."
charges:
missing_charges: "Please enter the %{sentence_fragment}. If there is no %{sentence_fragment}, please enter '0'."

2
config/routes.rb

@ -400,8 +400,10 @@ Rails.application.routes.draw do
get "create-test-lettings-log", to: "test_data#create_test_lettings_log" get "create-test-lettings-log", to: "test_data#create_test_lettings_log"
get "create-setup-test-lettings-log", to: "test_data#create_setup_test_lettings_log" get "create-setup-test-lettings-log", to: "test_data#create_setup_test_lettings_log"
get "create-2024-test-lettings-bulk-upload", to: "test_data#create_2024_test_lettings_bulk_upload" get "create-2024-test-lettings-bulk-upload", to: "test_data#create_2024_test_lettings_bulk_upload"
get "create-2025-test-lettings-bulk-upload", to: "test_data#create_2025_test_lettings_bulk_upload"
get "create-test-sales-log", to: "test_data#create_test_sales_log" get "create-test-sales-log", to: "test_data#create_test_sales_log"
get "create-setup-test-sales-log", to: "test_data#create_setup_test_sales_log" get "create-setup-test-sales-log", to: "test_data#create_setup_test_sales_log"
get "create-2024-test-sales-bulk-upload", to: "test_data#create_2024_test_sales_bulk_upload" get "create-2024-test-sales-bulk-upload", to: "test_data#create_2024_test_sales_bulk_upload"
get "create-2025-test-sales-bulk-upload", to: "test_data#create_2025_test_sales_bulk_upload"
end end
end end

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

@ -103,7 +103,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
before do before do
values = log_to_csv.to_2024_row values = log_to_csv.to_2024_row
values[7] = nil values[7] = nil
file.write(log_to_csv.default_2024_field_numbers_row) file.write(log_to_csv.default_field_numbers_row_for_year(2024))
file.write(log_to_csv.to_custom_csv_row(seed: nil, field_values: values)) file.write(log_to_csv.to_custom_csv_row(seed: nil, field_values: values))
file.rewind file.rewind
end end
@ -146,7 +146,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
before do before do
log.needstype = nil log.needstype = nil
values = log_to_csv.to_2024_row values = log_to_csv.to_2024_row
file.write(log_to_csv.default_2024_field_numbers_row(seed:)) file.write(log_to_csv.default_field_numbers_row_for_year(2024, seed:))
file.write(log_to_csv.to_custom_csv_row(seed:, field_values: values)) file.write(log_to_csv.to_custom_csv_row(seed:, field_values: values))
file.close file.close
end end

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

@ -15,8 +15,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::CsvParser do
file.write("Can be empty?\n") file.write("Can be empty?\n")
file.write("Type of letting the question applies to\n") file.write("Type of letting the question applies to\n")
file.write("Duplicate check field?\n") file.write("Duplicate check field?\n")
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2023_field_numbers_row) file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2023))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2023_csv_row) file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2023))
file.rewind file.rewind
end end
@ -39,8 +39,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::CsvParser do
file.write("Can be empty?\n") file.write("Can be empty?\n")
file.write("Type of letting the question applies to\n") file.write("Type of letting the question applies to\n")
file.write("Duplicate check field?\n") file.write("Duplicate check field?\n")
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2023_field_numbers_row) file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2023))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2023_csv_row) file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2023))
file.rewind file.rewind
end end
@ -64,8 +64,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::CsvParser do
file.write("Can be empty?\n") file.write("Can be empty?\n")
file.write("Type of letting the question applies to\n") file.write("Type of letting the question applies to\n")
file.write("Duplicate check field?\n") file.write("Duplicate check field?\n")
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2023_field_numbers_row(seed:)) file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2023, seed:))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2023_csv_row(seed:)) file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2023, seed:))
file.rewind file.rewind
end end
@ -108,7 +108,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::CsvParser do
context "when parsing csv without headers" do context "when parsing csv without headers" do
before do before do
file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2023_csv_row) file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_year_csv_row(2023))
file.rewind file.rewind
end end
@ -127,7 +127,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::CsvParser do
before do before do
file.write(bom) file.write(bom)
file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2023_csv_row) file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_year_csv_row(2023))
file.rewind file.rewind
end end
@ -141,7 +141,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::CsvParser do
before do before do
file.write(invalid_sequence) file.write(invalid_sequence)
file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2023_csv_row) file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_year_csv_row(2023))
file.rewind file.rewind
end end
@ -158,8 +158,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::CsvParser do
file.write("Can be empty?\r") file.write("Can be empty?\r")
file.write("Type of letting the question applies to\r\n") file.write("Type of letting the question applies to\r\n")
file.write("Duplicate check field?\r") file.write("Duplicate check field?\r")
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2023_field_numbers_row) file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2023))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2023_csv_row) file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2023))
file.rewind file.rewind
end end
@ -177,8 +177,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::CsvParser do
file.write("Can be empty?\n") file.write("Can be empty?\n")
file.write("Type of letting the question applies to\n") file.write("Type of letting the question applies to\n")
file.write("Duplicate check field?\n") file.write("Duplicate check field?\n")
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2023_field_numbers_row) file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2023))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2023_csv_row) file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2023))
file.rewind file.rewind
end end
@ -190,7 +190,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::CsvParser do
context "when without headers using default ordering" do context "when without headers using default ordering" do
before do before do
file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2023_csv_row) file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_year_csv_row(2023))
file.rewind file.rewind
end end
@ -210,8 +210,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::CsvParser do
file.write("Can be empty?\n") file.write("Can be empty?\n")
file.write("Type of letting the question applies to\n") file.write("Type of letting the question applies to\n")
file.write("Duplicate check field?\n") file.write("Duplicate check field?\n")
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2023_field_numbers_row(seed:)) file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2023, seed:))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2023_csv_row(seed:)) file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2023, seed:))
file.rewind file.rewind
end end

36
spec/services/bulk_upload/lettings/year2024/csv_parser_spec.rb

@ -15,8 +15,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
file.write("Can be empty?\n") file.write("Can be empty?\n")
file.write("Type of letting the question applies to\n") file.write("Type of letting the question applies to\n")
file.write("Duplicate check field?\n") file.write("Duplicate check field?\n")
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2024_field_numbers_row) file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2024))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2024_csv_row) file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2024))
file.rewind file.rewind
end end
@ -38,8 +38,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
file.write("\n") file.write("\n")
file.write("Type of letting the question applies to\n") file.write("Type of letting the question applies to\n")
file.write("Duplicate check field?\n") file.write("Duplicate check field?\n")
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2024_field_numbers_row) file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2024))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2024_csv_row) file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2024))
file.rewind file.rewind
end end
@ -62,8 +62,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
file.write("Can be empty?\n") file.write("Can be empty?\n")
file.write("Type of letting the question applies to\n") file.write("Type of letting the question applies to\n")
file.write("Duplicate check field?\n") file.write("Duplicate check field?\n")
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2024_field_numbers_row) file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2024))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2024_csv_row) file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2024))
file.write("\n") file.write("\n")
file.rewind file.rewind
end end
@ -92,8 +92,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
file.write("Can be empty?\n") file.write("Can be empty?\n")
file.write("Type of letting the question applies to\n") file.write("Type of letting the question applies to\n")
file.write("Duplicate check field?\n") file.write("Duplicate check field?\n")
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2024_field_numbers_row(seed:)) file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2024, seed:))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2024_csv_row(seed:)) file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2024, seed:))
file.rewind file.rewind
end end
@ -136,7 +136,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
context "when parsing csv without headers" do context "when parsing csv without headers" do
before do before do
file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2024_csv_row) file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_year_csv_row(2024))
file.rewind file.rewind
end end
@ -155,7 +155,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
before do before do
file.write(bom) file.write(bom)
file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2024_csv_row) file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_year_csv_row(2024))
file.rewind file.rewind
end end
@ -169,7 +169,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
before do before do
file.write(invalid_sequence) file.write(invalid_sequence)
file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2024_csv_row) file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_year_csv_row(2024))
file.rewind file.rewind
end end
@ -186,8 +186,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
file.write("Can be empty?\r") file.write("Can be empty?\r")
file.write("Type of letting the question applies to\r\n") file.write("Type of letting the question applies to\r\n")
file.write("Duplicate check field?\r") file.write("Duplicate check field?\r")
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2024_field_numbers_row) file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2024))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2024_csv_row) file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2024))
file.rewind file.rewind
end end
@ -205,8 +205,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
file.write("Can be empty?\n") file.write("Can be empty?\n")
file.write("Type of letting the question applies to\n") file.write("Type of letting the question applies to\n")
file.write("Duplicate check field?\n") file.write("Duplicate check field?\n")
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2024_field_numbers_row) file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2024))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2024_csv_row) file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2024))
file.rewind file.rewind
end end
@ -218,7 +218,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
context "when without headers using default ordering" do context "when without headers using default ordering" do
before do before do
file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2024_csv_row) file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_year_csv_row(2024))
file.rewind file.rewind
end end
@ -238,8 +238,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
file.write("Can be empty?\n") file.write("Can be empty?\n")
file.write("Type of letting the question applies to\n") file.write("Type of letting the question applies to\n")
file.write("Duplicate check field?\n") file.write("Duplicate check field?\n")
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2024_field_numbers_row(seed:)) file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2024, seed:))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2024_csv_row(seed:)) file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2024, seed:))
file.rewind file.rewind
end end

254
spec/services/bulk_upload/lettings/year2025/csv_parser_spec.rb

@ -0,0 +1,254 @@
require "rails_helper"
RSpec.describe BulkUpload::Lettings::Year2025::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::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2025))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2025))
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 some csv headers are empty (and we don't care about them)" do
before do
file.write("Question\n")
file.write("Additional info\n")
file.write("Values\n")
file.write("\n")
file.write("Type of letting the question applies to\n")
file.write("Duplicate check field?\n")
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2025))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2025))
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::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2025))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2025))
file.write("\n")
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
it "does not parse the last empty row" do
expect(service.row_parsers.count).to eq(1)
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::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2025, seed:))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2025, 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 with extra invalid headers" do
let(:seed) { rand }
let(:log_to_csv) { BulkUpload::LettingsLogToCsv.new(log:) }
let(:field_numbers) { log_to_csv.default_2025_field_numbers + %w[invalid_field_number] }
let(:field_values) { log_to_csv.to_2025_row + %w[value_for_invalid_field_number] }
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(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_13).to eql(log.tenancycode)
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_year_csv_row(2025))
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::LettingsLogToCsv.new(log:, col_offset: 0).to_year_csv_row(2025))
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::LettingsLogToCsv.new(log:, col_offset: 0).to_year_csv_row(2025))
file.rewind
end
it "parses csv correctly" do
expect(service.row_parsers[0].field_13).to eql(log.tenancycode)
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_field_numbers_row_for_year(2025))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2025))
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::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2025))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2025))
file.rewind
end
it "returns correct column" do
expect(service.column_for_field("field_5")).to eql("F")
expect(service.column_for_field("field_22")).to eql("W")
end
end
context "when without headers using default ordering" do
before do
file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_year_csv_row(2025))
file.rewind
end
it "returns correct column" do
expect(service.column_for_field("field_5")).to eql("E")
expect(service.column_for_field("field_22")).to eql("V")
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::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2025, seed:))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2025, seed:))
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("AS")
expect(service.column_for_field("field_26")).to eql("DG")
expect(service.column_for_field("field_25")).to eql("I")
end
end
end
end

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

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save