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 3 days ago committed by GitHub
parent
commit
71c3f41bf9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 11
      app/components/create_log_actions_component.html.erb
  2. 4
      app/components/create_log_actions_component.rb
  3. 34
      app/controllers/test_data_controller.rb
  4. 259
      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

11
app/components/create_log_actions_component.html.erb

@ -1,5 +1,5 @@
<div class="govuk-button-group app-filter-toggle <%= "govuk-!-margin-bottom-6" if display_actions? %>">
<% if display_actions? %>
<% if display_actions? %>
<%= govuk_button_to create_button_copy, create_button_href, class: "govuk-!-margin-right-3" %>
<% unless user.support? %>
<%= govuk_button_link_to upload_button_copy, upload_button_href, secondary: true %>
@ -9,9 +9,10 @@
<% end %>
<% if FeatureToggle.create_test_logs_enabled? %>
<%= govuk_link_to "Create test log", create_test_log_href %>
<%= govuk_link_to "Create 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 "New test log", create_test_log_href %>
<%= govuk_link_to "New test log (setup only)", create_setup_test_log_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 %>
</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")
end
def create_2024_test_bulk_upload_href
send("create_2024_test_#{log_type}_bulk_upload_path")
def create_test_bulk_upload_href(year)
send("create_#{year}_test_#{log_type}_bulk_upload_path")
end
def view_uploads_button_copy

34
app/controllers/test_data_controller.rb

@ -15,24 +15,28 @@ class TestDataController < ApplicationController
redirect_to lettings_log_path(log)
end
def create_2024_test_lettings_bulk_upload
return render_not_found unless FeatureToggle.create_test_logs_enabled?
%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?
file = Tempfile.new("test_lettings_log.csv")
log = FactoryBot.create(:lettings_log, :completed, assigned_to: current_user, ppostcode_full: "SW1A 1AA")
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.to_csv_row)
file.rewind
send_file file.path, type: "text/csv",
filename: "test_lettings_log.csv",
disposition: "attachment",
after_send: lambda {
file.close
file.unlink
}
file = Tempfile.new("#{year}_test_lettings_log.csv")
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}" })
file.write(log_to_csv.default_field_numbers_row)
file.write(log_to_csv.to_csv_row)
file.rewind
send_file file.path, type: "text/csv",
filename: "#{year}_test_lettings_log.csv",
disposition: "attachment",
after_send: lambda {
file.close
file.unlink
}
end
end
def create_2025_test_sales_bulk_upload; end
def create_test_sales_log
return render_not_found unless FeatureToggle.create_test_logs_enabled?

259
app/helpers/bulk_upload/lettings_log_to_csv.rb

@ -17,12 +17,8 @@ class BulkUpload::LettingsLogToCsv
def to_csv_row(seed: nil)
year = log.collection_start_year
case year
when 2022
to_2022_csv_row(seed:)
when 2023
to_2023_csv_row(seed:)
when 2024
to_2024_csv_row(seed:)
when 2022, 2023, 2024, 2025
to_year_csv_row(year, seed:)
else
raise NotImplementedError "No mapping function implemented for year #{year}"
end
@ -30,82 +26,32 @@ class BulkUpload::LettingsLogToCsv
def to_row
year = log.collection_start_year
case year
when 2022
to_2022_row
when 2023
to_2023_row
when 2024
to_2024_row
else
raise NotImplementedError "No mapping function implemented for year #{year}"
end
send("to_#{year}_row")
rescue NoMethodError
raise NotImplementedError "No mapping function implemented for year #{year}"
end
def default_field_numbers_row(seed: nil)
year = log.collection_start_year
case year
when 2022
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}"
end
default_field_numbers_row_for_year(year, seed:)
rescue NoMethodError
raise NotImplementedError "No mapping function implemented for year #{year}"
end
def default_field_numbers
year = log.collection_start_year
case year
when 2022
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}"
end
send("default_#{year}_field_numbers")
rescue NoMethodError
raise NotImplementedError "No mapping function implemented for year #{year}"
end
def to_2022_csv_row(seed: nil)
def to_year_csv_row(year, seed: nil)
unshuffled_row = send("to_#{year}_row")
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
else
(row_prefix + to_2022_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
(row_prefix + unshuffled_row).flatten.join(",") + line_ending
end
end
@ -121,20 +67,16 @@ class BulkUpload::LettingsLogToCsv
]
end
def default_2023_field_numbers_row(seed: nil)
def default_field_numbers_row_for_year(year, seed: nil)
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
["Field number"] + default_2023_field_numbers
["Field number"] + send("default_#{year}_field_numbers")
end.flatten.join(",") + line_ending
end
def default_2024_field_numbers_row(seed: nil)
if seed
["Field number"] + default_2024_field_numbers.shuffle(random: Random.new(seed))
else
["Field number"] + default_2024_field_numbers
end.flatten.join(",") + line_ending
def default_2022_field_numbers
(1..134).to_a
end
def default_2023_field_numbers
@ -145,6 +87,156 @@ class BulkUpload::LettingsLogToCsv
(1..130).to_a
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
[
overrides[:organisation_id] || log.owning_organisation&.old_visible_id, # 1
@ -551,4 +643,15 @@ private
log.hhregres
end
end
def relat_number(value)
case value
when "P"
1
when "R"
3
when "C", "X"
2
end
end
end

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

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

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

@ -111,6 +111,8 @@ private
BulkUpload::Lettings::Year2023::CsvParser.new(path:)
when 2024
BulkUpload::Lettings::Year2024::CsvParser.new(path:)
when 2025
BulkUpload::Lettings::Year2025::CsvParser.new(path:)
else
raise "csv parser not found"
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-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-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-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-2025-test-sales-bulk-upload", to: "test_data#create_2025_test_sales_bulk_upload"
end
end

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

@ -103,7 +103,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
before do
values = log_to_csv.to_2024_row
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.rewind
end
@ -146,7 +146,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
before do
log.needstype = nil
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.close
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("Type of letting the question applies to\n")
file.write("Duplicate check field?\n")
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2023_field_numbers_row)
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2023_csv_row)
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2023))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2023))
file.rewind
end
@ -39,8 +39,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::CsvParser do
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_2023_field_numbers_row)
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2023_csv_row)
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2023))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2023))
file.rewind
end
@ -64,8 +64,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::CsvParser do
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_2023_field_numbers_row(seed:))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2023_csv_row(seed:))
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2023, seed:))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2023, seed:))
file.rewind
end
@ -108,7 +108,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::CsvParser do
context "when parsing csv without headers" 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
end
@ -127,7 +127,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::CsvParser do
before do
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
end
@ -141,7 +141,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::CsvParser do
before do
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
end
@ -158,8 +158,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::CsvParser do
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_2023_field_numbers_row)
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2023_csv_row)
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2023))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2023))
file.rewind
end
@ -177,8 +177,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::CsvParser do
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_2023_field_numbers_row)
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2023_csv_row)
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2023))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2023))
file.rewind
end
@ -190,7 +190,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::CsvParser do
context "when without headers using default ordering" 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
end
@ -210,8 +210,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::CsvParser do
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_2023_field_numbers_row(seed:))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2023_csv_row(seed:))
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2023, seed:))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2023, seed:))
file.rewind
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("Type of letting the question applies to\n")
file.write("Duplicate check field?\n")
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_2024_field_numbers_row)
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2024_csv_row)
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2024))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2024))
file.rewind
end
@ -38,8 +38,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
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_2024_field_numbers_row)
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2024_csv_row)
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2024))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2024))
file.rewind
end
@ -62,8 +62,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
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_2024_field_numbers_row)
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2024_csv_row)
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2024))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2024))
file.write("\n")
file.rewind
end
@ -92,8 +92,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
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_2024_field_numbers_row(seed:))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2024_csv_row(seed:))
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2024, seed:))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2024, seed:))
file.rewind
end
@ -136,7 +136,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
context "when parsing csv without headers" 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
end
@ -155,7 +155,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
before do
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
end
@ -169,7 +169,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
before do
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
end
@ -186,8 +186,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
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_2024_field_numbers_row)
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2024_csv_row)
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2024))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2024))
file.rewind
end
@ -205,8 +205,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
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_2024_field_numbers_row)
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2024_csv_row)
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2024))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2024))
file.rewind
end
@ -218,7 +218,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
context "when without headers using default ordering" 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
end
@ -238,8 +238,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::CsvParser do
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_2024_field_numbers_row(seed:))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_2024_csv_row(seed:))
file.write(BulkUpload::LettingsLogToCsv.new(log:).default_field_numbers_row_for_year(2024, seed:))
file.write(BulkUpload::LettingsLogToCsv.new(log:).to_year_csv_row(2024, seed:))
file.rewind
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