From 5d5054fc033e7b62c5e9daa9b043c2b5c3a10b15 Mon Sep 17 00:00:00 2001 From: Rachael Booth Date: Fri, 28 Jul 2023 10:11:02 +0100 Subject: [PATCH 1/4] Update confirmation link timeout to 24 hours (#1792) --- config/initializers/devise.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 63389432d..4759c94bd 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -152,7 +152,7 @@ Devise.setup do |config| # their account can't be confirmed with the token any more. # Default is nil, meaning there is no restriction on how long a user can take # before confirming their account. - config.confirm_within = 3.hours + config.confirm_within = 24.hours # If true, requires any email changes to be confirmed (exactly the same way as # initial account confirmation) to be applied. Requires additional unconfirmed_email From ea1bd30a41f5659c067f97b7a82f975d18a4e14d Mon Sep 17 00:00:00 2001 From: natdeanlewissoftwire <94526761+natdeanlewissoftwire@users.noreply.github.com> Date: Fri, 28 Jul 2023 11:27:04 +0100 Subject: [PATCH 2/4] feat: add owned sales logs for clarity and use in rake task (#1814) --- app/models/organisation.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/organisation.rb b/app/models/organisation.rb index eea36f754..1c048703f 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -75,6 +75,10 @@ class Organisation < ApplicationRecord LettingsLog.filter_by_owning_organisation(absorbed_organisations + [self]) end + def owned_sales_logs + sales_logs + end + def managed_lettings_logs LettingsLog.filter_by_managing_organisation(absorbed_organisations + [self]) end From 79b54e02e9bcca12ff4374b4cc65e4f987c4c089 Mon Sep 17 00:00:00 2001 From: Rachael Booth Date: Fri, 28 Jul 2023 11:34:26 +0100 Subject: [PATCH 3/4] Split migration task to give options to run/re-run specific sections (#1813) --- lib/tasks/full_import.rake | 54 +++++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/lib/tasks/full_import.rake b/lib/tasks/full_import.rake index 9d2ec11d3..780fe46fc 100644 --- a/lib/tasks/full_import.rake +++ b/lib/tasks/full_import.rake @@ -1,10 +1,10 @@ Import = Struct.new("Import", :import_class, :import_method, :folder) namespace :import do - desc "Run a full import for the institutions listed in the named file on s3" - task :full, %i[institutions_csv_name] => :environment do |_task, args| + desc "Run initial import steps - orgs, schemes, users etc (without triggering user invite emails)" + task :initial, %i[institutions_csv_name] => :environment do |_task, args| institutions_csv_name = args[:institutions_csv_name] - raise "Usage: rake core:full_import['institutions_csv_name']" if institutions_csv_name.blank? + raise "Usage: rake import:initial['institutions_csv_name']" if institutions_csv_name.blank? s3_service = Storage::S3Service.new(Configuration::PaasConfigurationService.new, ENV["IMPORT_PAAS_INSTANCE"]) csv = CSV.parse(s3_service.get_file_io(institutions_csv_name), headers: true) @@ -35,12 +35,24 @@ namespace :import do end end + Rails.logger.info("Finished initial imports") + end + + desc "Run logs import steps" + task :logs, %i[institutions_csv_name] => :environment do |_task, args| + institutions_csv_name = args[:institutions_csv_name] + raise "Usage: rake import:logs['institutions_csv_name']" if institutions_csv_name.blank? + + s3_service = Storage::S3Service.new(Configuration::PaasConfigurationService.new, ENV["IMPORT_PAAS_INSTANCE"]) + csv = CSV.parse(s3_service.get_file_io(institutions_csv_name), headers: true) + org_count = csv.length + logs_import_list = [ Import.new(Imports::LettingsLogsImportService, :create_logs, "logs"), Import.new(Imports::SalesLogsImportService, :create_logs, "logs"), ] - Rails.logger.info("Initial imports complete, beginning log imports for #{org_count} organisations") + Rails.logger.info("Beginning log imports for #{org_count} organisations") csv.each do |row| archive_path = row[1] @@ -57,17 +69,39 @@ namespace :import do end end - Rails.logger.info("Log import complete, triggering user invite emails") + Rails.logger.info("Log import complete") + end + + desc "Trigger invite emails for imported users" + task :trigger_invites, %i[institutions_csv_name] => :environment do |_task, args| + institutions_csv_name = args[:institutions_csv_name] + raise "Usage: rake import:trigger_invites['institutions_csv_name']" if institutions_csv_name.blank? + + s3_service = Storage::S3Service.new(Configuration::PaasConfigurationService.new, ENV["IMPORT_PAAS_INSTANCE"]) + csv = CSV.parse(s3_service.get_file_io(institutions_csv_name), headers: true) + + Rails.logger.info("Triggering user invite emails") csv.each do |row| organisation = Organisation.find_by(name: row[0]) next unless organisation - users = User.where(organisation:, active: true, initial_confirmation_sent: false) + users = User.where(organisation:, active: true, initial_confirmation_sent: nil) users.each { |user| ResendInvitationMailer.resend_invitation_email(user).deliver_later } end - Rails.logger.info("Invite emails triggered, generating report") + Rails.logger.info("Invite emails triggered") + end + + desc "Generate migrated logs report" + task :generate_report, %i[institutions_csv_name] => :environment do |_task, args| + institutions_csv_name = args[:institutions_csv_name] + raise "Usage: rake import:generate_report['institutions_csv_name']" if institutions_csv_name.blank? + + s3_service = Storage::S3Service.new(Configuration::PaasConfigurationService.new, ENV["IMPORT_PAAS_INSTANCE"]) + csv = CSV.parse(s3_service.get_file_io(institutions_csv_name), headers: true) + + Rails.logger.info("Generating migrated logs report") rep = CSV.generate do |report| headers = ["Institution name", "Id", "Old Completed lettings logs", "Old In progress lettings logs", "Old Completed sales logs", "Old In progress sales logs", "New Completed lettings logs", "New In Progress lettings logs", "New Completed sales logs", "New In Progress sales logs"] @@ -91,4 +125,10 @@ namespace :import do Rails.logger.info("Logs report available in s3 import bucket at #{report_name}") end + + desc "Run import from logs step to end" + task :logs_onwards, %i[institutions_csv_name] => %i[environment logs trigger_invites generate_report] + + desc "Run a full import for the institutions listed in the named file on s3" + task :full, %i[institutions_csv_name] => %i[environment initial logs trigger_invites generate_report] end From 87a5265c05000bb069a62d19125dec81fb4827f9 Mon Sep 17 00:00:00 2001 From: Jack <113976590+bibblobcode@users.noreply.github.com> Date: Fri, 28 Jul 2023 15:20:13 +0100 Subject: [PATCH 4/4] CLDC-2168 Bugfix - handle empty file (#1795) * Bugfix - handle empty file * Count rows without headers * Add host to sentry tags * Add test for blank file with headers * Fix sales validator * Validate also content of rows * Use compact * Add 2023-2024 lettings empty file spec * Update sales template --- .../bulk_upload/lettings/validator.rb | 4 +- app/services/bulk_upload/sales/validator.rb | 4 +- config/initializers/sentry.rb | 2 + config/locales/en.yml | 2 + .../bulk-upload-sales-template-2023-24.xlsx | Bin 293901 -> 18791 bytes ...ettings_bulk_upload_empty_with_headers.csv | 49 ++++++++++ ...ettings_bulk_upload_empty_with_headers.csv | 49 ++++++++++ .../bulk_upload/lettings/validator_spec.rb | 46 +++++++-- spec/services/bulk_upload/processor_spec.rb | 92 ++++++++++++++++++ .../bulk_upload/sales/validator_spec.rb | 13 +++ 10 files changed, 251 insertions(+), 10 deletions(-) create mode 100644 spec/fixtures/files/2022_23_lettings_bulk_upload_empty_with_headers.csv create mode 100644 spec/fixtures/files/2023_24_lettings_bulk_upload_empty_with_headers.csv diff --git a/app/services/bulk_upload/lettings/validator.rb b/app/services/bulk_upload/lettings/validator.rb index 0759a5cd2..7f811f709 100644 --- a/app/services/bulk_upload/lettings/validator.rb +++ b/app/services/bulk_upload/lettings/validator.rb @@ -144,8 +144,8 @@ private end def validate_file_not_empty - if File.size(path).zero? - errors.add(:file, :blank) + if File.size(path).zero? || csv_parser.body_rows.flatten.compact.empty? + errors.add(:base, :blank_file) halt_validations! end diff --git a/app/services/bulk_upload/sales/validator.rb b/app/services/bulk_upload/sales/validator.rb index 5e5cf3a89..aaee69849 100644 --- a/app/services/bulk_upload/sales/validator.rb +++ b/app/services/bulk_upload/sales/validator.rb @@ -138,8 +138,8 @@ private end def validate_file_not_empty - if File.size(path).zero? - errors.add(:file, :blank) + if File.size(path).zero? || csv_parser.body_rows.flatten.compact.empty? + errors.add(:base, :blank_file) halt_validations! end diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb index 596487d40..05eecc89f 100644 --- a/config/initializers/sentry.rb +++ b/config/initializers/sentry.rb @@ -22,3 +22,5 @@ Sentry.init do |config| end end end + +Sentry.set_tags("app_host": ENV["APP_HOST"]) diff --git a/config/locales/en.yml b/config/locales/en.yml index 07142821d..fb29431e1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -55,12 +55,14 @@ en: bulk_upload/lettings/validator: attributes: base: + blank_file: Template is blank - The template must be filled in for us to create the logs and check if data is correct. 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" wrong_template: "Incorrect start dates, please ensure you have used the correct template" bulk_upload/sales/validator: attributes: base: + blank_file: Template is blank - The template must be filled in for us to create the logs and check if data is correct. 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" wrong_template: "Incorrect sale dates, please ensure you have used the correct template" diff --git a/public/files/bulk-upload-sales-template-2023-24.xlsx b/public/files/bulk-upload-sales-template-2023-24.xlsx index 905995f59355e538fd0853574fccfcd6553fc99a..773054750baa349bd3ae0e31f571fa54f740cd5e 100644 GIT binary patch literal 18791 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

nVr$=X4@}%KwdoI|?g@ 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 Zt36cs0s*|c001oT;|FX}XT<;H{y*v}n@a!y literal 293901 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 Z=Jr-o#Kl9s`d;8l08*izGZ3yA{|9y?5+48n diff --git a/spec/fixtures/files/2022_23_lettings_bulk_upload_empty_with_headers.csv b/spec/fixtures/files/2022_23_lettings_bulk_upload_empty_with_headers.csv new file mode 100644 index 000000000..7628c034f --- /dev/null +++ b/spec/fixtures/files/2022_23_lettings_bulk_upload_empty_with_headers.csv @@ -0,0 +1,49 @@ + ,Setting up this lettings log,,,,,,,,,,,,,,,,,Property information,,,,,,,,,,,,,,,,,,,,,Tenancy information,,,,,,Household characteristics,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Household needs,,,,,,,,,,,,,,,,,,,,,Household situation,,,,,,,,,,,,,,,,,,,,"Income, benefits and outgoings",,,,,,,,,,,,,, +Question,Which organisation owns this property?,Which organisation manages this letting?,What is the CORE username of the account this letting log should be assigned to? ,What is the needs type?,What is the letting type?,Is this letting a renewal?,What is the tenancy start date? - day DD,What is the tenancy start date? - month MM,What is the tenancy start date? - year YY,Is this a London Affordable Rent letting?,Which type of Intermediate Rent is this letting?,Which 'Other' type of Intermediate Rent is this letting?,What is the tenant code?,What is the property reference?,What management group does this letting belong to?,What scheme does this letting belong to?,Which location is this letting for?,"If known, provide this property’s UPRN",Address Line 1,Address Line 2,Town or city,County,Part 1 of the property's postcode,Part 2 of the property's postcode,What is the property's local authority?,What type was the property most recently let as?,What is the reason for the property being vacant?,How many times was the property offered between becoming vacant and this letting?,What type of unit is the property?,Which type of building is the property?,Is the property built or adapted to wheelchair-user standards?,How many bedrooms does the property have?,What is the void date? - day DD,What is the void date? - month MM,What is the void date? - year YYYY,What date were any major repairs completed on? - day DD,What date were any major repairs completed on? - month MM,What date were any major repairs completed on? - year YY,Is this a joint tenancy?,Is this a starter tenancy?,What is the type of tenancy?,"If 'Other', what is the type of tenancy?",What is the length of the fixed-term tenancy to the nearest year?,Is this letting in sheltered accommodation?,Has the tenant seen the DLUHC privacy notice?,What is the lead tenant’s age?,Which of these best describes the lead tenant’s gender identity? ,Which of these best describes the lead tenant's ethnic background?,What is the lead tenant’s nationality?,Which of these best describes the lead tenant’s working situation?,What is person 2's relationship to the lead tenant?,What is person 2's age?,Which of these best describes person 2's gender identity?,Which of these best describes person 2's working situation?,What is person 3's relationship to the lead tenant?,What is person 3's age?,Which of these best describes person 3's gender identity?,Which of these best describes person 3's working situation?,What is person 4's relationship to the lead tenant?,What is person 4's age?,Which of these best describes person 4's gender identity?,Which of these best describes person 4's working situation?,What is person 5's relationship to the lead tenant?,What is person 5's age?,Which of these best describes person 5's gender identity?,Which of these best describes person 5's working situation?,What is person 6's relationship to the lead tenant?,What is person 6's age?,Which of these best describes person 6's gender identity?,Which of these best describes person 6's working situation?,What is person 7's relationship to the lead tenant?,What is person 7's age?,Which of these best describes person 7's gender identity?,Which of these best describes person 7's working situation?,What is person 8's relationship to the lead tenant?,What is person 8's age?,Which of these best describes person 8's gender identity?,Which of these best describes person 8's working situation?,Does anybody in the household have links to the UK armed forces?,Is this person still serving in the UK armed forces?,Was this person seriously injured or ill as a result of serving in the UK armed forces?,Is anybody in the household pregnant?,"Disabled access needs + +a) Fully wheelchair-accessible housing","Disabled access needs + +b) Wheelchair access to essential rooms","Disabled access needs + +c) Level access housing","Disabled access needs + +f) Other disabled access needs","Disabled access needs + +g) No disabled access needs","Disabled access needs + +h) Don’t know",Does anybody in the household have a physical or mental health condition (or other illness) expected to last 12 months or more?,Does this person's condition affect their dexterity?,Does this person's condition affect their learning or understanding or concentrating?,Does this person's condition affect their hearing?,Does this person's condition affect their memory?,Does this person's condition affect their mental health?,Does this person's condition affect their mobility?,Does this person's condition affect them socially or behaviourally?,Does this person's condition affect their stamina or breathing or fatigue?,Does this person's condition affect their vision?,Does this person's condition affect them in another way?,How long has the household continuously lived in the local authority area of the new letting?,How long has the household been on the local authority waiting list for the new letting?,What is the tenant’s main reason for the household leaving their last settled home?,"If 'Other', what was the main reason for leaving their last settled home?",Where was the household immediately before this letting?,Did the household experience homelessness immediately before this letting?,Do you know the postcode of the household's last settled home?,Part 1 of postcode of last settled home,Part 2 of postcode of last settled home,What is the local authority of the household's last settled home?,Was the household given 'reasonable preference' by the local authority?,"Reasonable preference reason + +They were homeless or about to lose their home (within 56 days)","Reasonable preference reason + +They were living in unsanitary, overcrowded or unsatisfactory housing","Reasonable preference reason + +They needed to move due to medical and welfare reasons (including disability)","Reasonable preference reason + +They needed to move to avoid hardship to themselves or others","Reasonable preference reason + +Don't know",Was the letting made under Choice-Based Lettings (CBL)?,Was the letting made under the Common Allocation Policy (CAP)? ,Was the letting made under the Common Housing Register (CHR)?,What was the source of referral for this letting?,Do you know the household's combined total income after tax?,How often does the household receive income?,How much income does the household have in total?,Is the tenant likely to be receiving any of these housing-related benefits?,"How much of the household's income is from Universal Credit, state pensions or benefits?",Does the household pay rent or other charges for the accommodation?,How often does the household pay rent and other charges?,"If this is a care home, how much does the household pay every [time period]?",What is the basic rent?,What is the service charge?,What is the personal service charge?,What is the support charge?,Total charge,"After the household has received any housing-related benefits, will they still need to pay for rent and charges?",What do you expect the outstanding amount to be? +Additional info,Organisation's CORE ID,,"If using new core then this will be the email address. If left empty, the letting log will be assigned to the account used to upload the log.","General needs housing includes both self-contained and shared housing without support or specific adaptations. Supported housing includes direct access hostels, group homes, residential care and nursing homes.",,This is a letting to the same tenant in the same property,,,,,,,This is how you usually refer to this tenancy on your own systems,This is how you usually refer to this property on your own systems,Provide management code if you have one,"Provide scheme code, include the 'S' at the beginning if it has one",Provide location code if you have one,"The UPRN is the Unique Property Reference Number. It's created by the Ordnance Survey so it's a unique number system used across all housing providers, all sectors (i.e. not just social housing) and all across the UK.",,,,,,,,,,"Do not include the offer that led to this letting. +This is after the last tenancy ended. If the property is being offered for let for the first time, enter 0.",,,,"If shared accommodation, enter the number of bedrooms occupied by this household. A bedsit has 1 bedroom.","This is the date the property became available to let. For a re-let, it's the day after the previous tenant's contract ends. For a new build, conversion, acquisition or lease it's the day after the provider legally took over possession or management.",,,"Major repairs are works that could not be reasonably carried out with a tenant living at the property. For example, structural repairs.",,,,Also known as an ‘introductory period’,"Fixed-term tenancies are for a set time (up to 20 years). Licence agreements are on a rolling basis, mainly for supported housing. Local authorities mostly provide secure tenancies, and housing associations mostly provide assured (ASTs).",,Not including starter or introductory period,,Make sure the tenant has seen the attached privacy notice before completing this log,"This is the household member who does the most paid work. If several people do the same paid work, it's the oldest household member.",,,,,A child is anyone eligible for child benefit: under age 16 or under 20 if still in full-time education.,Mark children under 1 as 1.,,,A child is anyone eligible for child benefit: under age 16 or under 20 if still in full-time education.,Mark children under 1 as 1.,,,A child is anyone eligible for child benefit: under age 16 or under 20 if still in full-time education.,Mark children under 1 as 1.,,,A child is anyone eligible for child benefit: under age 16 or under 20 if still in full-time education.,Mark children under 1 as 1.,,,A child is anyone eligible for child benefit: under age 16 or under 20 if still in full-time education.,Mark children under 1 as 1.,,,A child is anyone eligible for child benefit: under age 16 or under 20 if still in full-time education.,Mark children under 1 as 1.,,,A child is anyone eligible for child benefit: under age 16 or under 20 if still in full-time education.,Mark children under 1 as 1.,,,"Excluding national service. +If several household members have these links, answer for regular first. If no regular, answer for reserve. If no reserve, answer for spouses or civil partners.",,,,,,,,,,,"For example, lifting and carrying objects, or using a keyboard",,"For example, deafness or partial hearing",,"For example, depression or anxiety",,"Anything associated with autism spectrum disorder (ASD), including Asperger’s or attention deficit hyperactivity disorder (ADHD)",,"For example, blindness or partial sight",,,,"‘Last settled home' means last long-standing home. For tenants who had temporary accommodation or slept rough, it's where they lived previously.",,,,,,,,Social housing 'reasonable preference' is also known as 'priority need',,,,,,,,,,,,"Include any income after tax from employment, pensions, and Universal Credit. Don't include National Insurance (NI) contributions and tax, housing benefit, child benefit, or council tax support.",,,"If rent is charged on the property then answer Yes, even if tenants do not pay it themselves.",,,"Amount paid before any service charges, for example hot water or cleaning. Households may get household benefits towards basic rent.","For example, cleaning. Households may get household benefits towards service charge",For example heating or hot water. This doesn’t include housing benefit or Universal Credit.,Any support service charges included in the tenancy agreement,Unnecessary for new CORE users,,Approximate figure only +Values,Numeric,,"Email format if using new CORE. +Alphanumeric, except for commas if using old CORE.",1 - 2,1 - 12,1 - 2,1 - 31,1 - 12,23 - 24,1 - 3,,Text,"Alphanumeric, max 13 characters","Alphanumeric, max 12 characters",1 - 999,Alphanumeric,1 - 999,Numeric,Alphanumeric,,Text,,XX(XX),XXX,"ONS CODE: Alphanumeric, 9 characters beginning with 'E'",1 - 3 or 5 - 8,"5 - 6, or 8 - 20",0+,"1 - 2, 4 or 6 - 10",1 - 2,,1 - 7,1 - 31,1 - 12,20 - 24,1 - 31,1 - 12,14 - 24,1 - 3,1 - 2,2 - 7,Text,1 - 99,1 - 5,1,16 - 120 or R,"F, M, X or R",1 - 19,"12 - 13, 17 - 21",0 - 10,"P, C, X or R","Numeric, range 1 - 120 or text (upper case 'R') +Must be >= 16 if working situation = 1 - 8 or 0 +Must be <16 if working situation = 9","F, M, X or R",0 - 10,"P, C, X or R","Numeric, range 1 - 120 or text (upper case 'R') +Must be >= 16 if working situation = 1 - 8 or 0 +Must be <16 if working situation = 9","F, M, X or R",0 - 10,"P, C, X or R","Numeric, range 1 - 120 or text (upper case 'R') +Must be >= 16 if working situation = 1 - 8 or 0 +Must be <16 if working situation = 9","F, M, X or R",0 - 10,"P, C, X or R","Numeric, range 1 - 120 or text (upper case 'R') +Must be >= 16 if working situation = 1 - 8 or 0 +Must be <16 if working situation = 9","F, M, X or R",0 - 10,"P, C, X or R","Numeric, range 1 - 120 or text (upper case 'R') +Must be >= 16 if working situation = 1 - 8 or 0 +Must be <16 if working situation = 9","F, M, X or R",0 - 10,"P, C, X or R","Numeric, range 1 - 120 or text (upper case 'R') +Must be >= 16 if working situation = 1 - 8 or 0 +Must be <16 if working situation = 9","F, M, X or R",0 - 10,"P, C, X or R","Numeric, range 1 - 120 or text (upper case 'R') +Must be >= 16 if working situation = 1 - 8 or 0 +Must be <16 if working situation = 9","F, M, X or R",0 - 10,1 - 6,3 - 6,1 - 3,,1 or empty,,,,,,1 - 3,1 or empty,,,,,,,,,,1 - 2 or 5 - 10,2 or 5 - 10,"1 - 2, 4, 8 - 14, 16 - 20, 28 - 31 or 34 - 49",Text,"3 - 4, 6 - 7, 9 - 10, 13 - 14, 18 - 19, 21 or 23 - 37",1 or 11,1 - 2,XX(XX),XXX,"ONS CODE: Alphanumeric, 9 characters beginning with 'E'",1 - 3,1 or empty,,,,,1 - 2,,,"1 - 4, 7 - 10, 12 - 17",1 - 3,,0 - 99999,"1, 3, 6, 9 or 10",1 - 4,0 - 1,1 - 10,xxxx.xx,,,,,,1 - 3,xxxx.xx +Can be empty?,No,,Yes,No,,,No,,,"Yes, if letting type is not an Affordable Rent letting (if field 5 = 1 - 4 or 9 - 12)","Yes, if letting type is not an Intermediate Rent letting (if field 5 = 1 - 8)","Yes, if letting type is not an Intermediate Rent letting (if field 5 = 1 - 8) or if 'Other intermediate rent product' is not selected for type of Intermediate Rent (if field 11 is not 3)",Yes,,"Yes, if letting is general needs (if field 4 = 1) or if location code is provided (if field 17 is not empty)","Yes, if letting is general needs (if field 4 = 1)","Yes, if letting is general needs (if field 4 = 1) or if management code is provided (if field 15 is not empty)","Yes, if letting is supported housing (if field 4 = 2) or if the property's postcode is not empty (if fields 23 and 24 contain full and valid entries)","Yes, if letting is supported housing (if field 4 = 2) or if property's UPRN and local authority are known (if fields 18 and 25 are not empty)",Yes,"Yes, if letting is supported housing (if field 4 = 2) or if property's UPRN and local authority are known (if fields 18 and 25 are not empty)",Yes,"Yes, if letting is supported housing (if field 4 = 2) or if property's UPRN and local authority are known (if fields 18 and 25 are not empty)",,"Yes, if letting is supported housing (if field 4 = 2)","Yes, if letting is a renewal (if field 6 = 1) or a first-time let (if field 27 = 15 - 17)","Yes, if letting is a renewal (if field 6 = 1)",,"Yes, if letting is supported housing (if field 4 = 2)",,,,"Yes, if letting is a renewal (if field 6 = 1)",,,Yes,,,No,,,"Yes, if 'Other' is not selected for tenancy type (if field 41 is not 3)","Yes, if letting is not a fixed-term tenancy (if field 41 = 2, 3, 5 or 7)","Yes, if letting is general needs (if field 4 = 1)",No,No,,,,,"Yes, if the other fields about this person (fields 52, 53 and 54) are also empty","Yes, if the other fields about this person (fields 51, 53 and 54) are also empty","Yes, if the other fields about this person (fields 51, 52 and 54) are also empty","Yes, if the other fields about this person (fields 51, 52 and 53) are also empty","Yes, if the other fields about this person (fields 56, 57 and 58) are also empty","Yes, if the other fields about this person (fields 55, 57 and 58) are also empty","Yes, if the other fields about this person (fields 55, 56 and 58) are also empty","Yes, if the other fields about this person (fields 55, 56 and 57) are also empty","Yes, if the other fields about this person (fields 60, 61 and 62) are also empty","Yes, if the other fields about this person (fields 59, 61 and 62) are also empty","Yes, if the other fields about this person (fields 59, 60 and 62) are also empty","Yes, if the other fields about this person (fields 59, 60 and 61) are also empty","Yes, if the other fields about this person (fields 64, 65 and 66) are also empty","Yes, if the other fields about this person (fields 63, 65 and 66) are also empty","Yes, if the other fields about this person (fields 63, 64 and 66) are also empty","Yes, if the other fields about this person (fields 63, 64 and 65) are also empty","Yes, if the other fields about this person (fields 68, 69 and 70) are also empty","Yes, if the other fields about this person (fields 67, 69 and 70) are also empty","Yes, if the other fields about this person (fields 67, 68 and 70) are also empty","Yes, if the other fields about this person (fields 67, 68 and 69) are also empty","Yes, if the other fields about this person (fields 72, 73 and 74) are also empty","Yes, if the other fields about this person (fields 71, 73 and 74) are also empty","Yes, if the other fields about this person (fields 71, 72 and 74) are also empty","Yes, if the other fields about this person (fields 71, 72 and 73) are also empty","Yes, if the other fields about this person (fields 76, 77 and 78) are also empty","Yes, if the other fields about this person (fields 75, 77 and 78) are also empty","Yes, if the other fields about this person (fields 75, 76 and 78) are also empty","Yes, if the other fields about this person (fields 75, 76 and 77) are also empty",No,"Yes, if no one in the household is a current or former regular (if field 79 is not 1)","Yes, if no one in the household is a current or former regular or reserve (if field 79 = 2, 3, 5 or 6)",No,"Yes, if no household members have access needs or if it is unknown (if field 87 or 88 = 1)",,,,"Yes, if a household member has an access need (if at least one of fields 83, 84, 85 and 86 = 1)",,No,"Yes, if no one in the household has a physical or mental health condition (if field 89 is 2 or 3). +If someone in the household does have such a condition (if field 89 = 1), then at least 1 of these fields must be 1.",,,,,,,,,,No,"Yes, if letting is a renewal (if field 6 = 1)",No,"Yes, if 'Other' is not selected for reason for leaving last settled home (if field 102 is not 20)","Yes, if letting is a renewal (if field 6 = 1)",No,,"Yes, if postcode of household's last settled home is not known (if 106 = 2)",,Yes,No,"If household was given 'reasonable preference' (if field 110 = 1), at least one of these fields must be 1. +If household was not given 'reasonable preference' (if field 110 = 2 or 3), these fields will be ignored.",,,,,No,,,"Yes, if letting is a renewal (if field 6 = 1)",No,"Yes, if household's income is unknown (if field 120 = 2 or 3)",,No,,"Yes, if all rent and accommodation charges are provided (if fields 127 - 132 are not empty)",No,"Yes, if accommodation is not a care home (if field 125 = 1) and all rent and accommodation charges are provided (if fields 128 - 132 are not empty)","Yes, if the household does not pay rent (if field 125 = 1) or if the accommodation is not a care home (if field 127 is empty)",Yes,,,"Yes, if submitting data to new CORE, if the household does not pay rent (if field 125 = 1) or if the accommodation is not a care home (if field 127 is empty)","Yes, if the household doesn't receive housing benefits, or if it is unknown (if field 123 = 3, 9 or 10)","Yes, if the household does not need to pay rent/charges after receiving housing benefits (if field133 = 2 or 3)" +Type of letting the question applies to,,,,,,,,,,Affordable Rent only,Intermediate Rent only,,,,Supported housing only,,,General needs only,,,,,,,,,,,General needs only,,,,,,,,,,,,,,,Supported housing only,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Supported housing only,,,,,,,,, +Duplicate check field?,Yes,,,,,,Yes,,,,,,Yes,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Yes,,,,Yes,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +Bulk upload field number,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134 diff --git a/spec/fixtures/files/2023_24_lettings_bulk_upload_empty_with_headers.csv b/spec/fixtures/files/2023_24_lettings_bulk_upload_empty_with_headers.csv new file mode 100644 index 000000000..7628c034f --- /dev/null +++ b/spec/fixtures/files/2023_24_lettings_bulk_upload_empty_with_headers.csv @@ -0,0 +1,49 @@ + ,Setting up this lettings log,,,,,,,,,,,,,,,,,Property information,,,,,,,,,,,,,,,,,,,,,Tenancy information,,,,,,Household characteristics,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Household needs,,,,,,,,,,,,,,,,,,,,,Household situation,,,,,,,,,,,,,,,,,,,,"Income, benefits and outgoings",,,,,,,,,,,,,, +Question,Which organisation owns this property?,Which organisation manages this letting?,What is the CORE username of the account this letting log should be assigned to? ,What is the needs type?,What is the letting type?,Is this letting a renewal?,What is the tenancy start date? - day DD,What is the tenancy start date? - month MM,What is the tenancy start date? - year YY,Is this a London Affordable Rent letting?,Which type of Intermediate Rent is this letting?,Which 'Other' type of Intermediate Rent is this letting?,What is the tenant code?,What is the property reference?,What management group does this letting belong to?,What scheme does this letting belong to?,Which location is this letting for?,"If known, provide this property’s UPRN",Address Line 1,Address Line 2,Town or city,County,Part 1 of the property's postcode,Part 2 of the property's postcode,What is the property's local authority?,What type was the property most recently let as?,What is the reason for the property being vacant?,How many times was the property offered between becoming vacant and this letting?,What type of unit is the property?,Which type of building is the property?,Is the property built or adapted to wheelchair-user standards?,How many bedrooms does the property have?,What is the void date? - day DD,What is the void date? - month MM,What is the void date? - year YYYY,What date were any major repairs completed on? - day DD,What date were any major repairs completed on? - month MM,What date were any major repairs completed on? - year YY,Is this a joint tenancy?,Is this a starter tenancy?,What is the type of tenancy?,"If 'Other', what is the type of tenancy?",What is the length of the fixed-term tenancy to the nearest year?,Is this letting in sheltered accommodation?,Has the tenant seen the DLUHC privacy notice?,What is the lead tenant’s age?,Which of these best describes the lead tenant’s gender identity? ,Which of these best describes the lead tenant's ethnic background?,What is the lead tenant’s nationality?,Which of these best describes the lead tenant’s working situation?,What is person 2's relationship to the lead tenant?,What is person 2's age?,Which of these best describes person 2's gender identity?,Which of these best describes person 2's working situation?,What is person 3's relationship to the lead tenant?,What is person 3's age?,Which of these best describes person 3's gender identity?,Which of these best describes person 3's working situation?,What is person 4's relationship to the lead tenant?,What is person 4's age?,Which of these best describes person 4's gender identity?,Which of these best describes person 4's working situation?,What is person 5's relationship to the lead tenant?,What is person 5's age?,Which of these best describes person 5's gender identity?,Which of these best describes person 5's working situation?,What is person 6's relationship to the lead tenant?,What is person 6's age?,Which of these best describes person 6's gender identity?,Which of these best describes person 6's working situation?,What is person 7's relationship to the lead tenant?,What is person 7's age?,Which of these best describes person 7's gender identity?,Which of these best describes person 7's working situation?,What is person 8's relationship to the lead tenant?,What is person 8's age?,Which of these best describes person 8's gender identity?,Which of these best describes person 8's working situation?,Does anybody in the household have links to the UK armed forces?,Is this person still serving in the UK armed forces?,Was this person seriously injured or ill as a result of serving in the UK armed forces?,Is anybody in the household pregnant?,"Disabled access needs + +a) Fully wheelchair-accessible housing","Disabled access needs + +b) Wheelchair access to essential rooms","Disabled access needs + +c) Level access housing","Disabled access needs + +f) Other disabled access needs","Disabled access needs + +g) No disabled access needs","Disabled access needs + +h) Don’t know",Does anybody in the household have a physical or mental health condition (or other illness) expected to last 12 months or more?,Does this person's condition affect their dexterity?,Does this person's condition affect their learning or understanding or concentrating?,Does this person's condition affect their hearing?,Does this person's condition affect their memory?,Does this person's condition affect their mental health?,Does this person's condition affect their mobility?,Does this person's condition affect them socially or behaviourally?,Does this person's condition affect their stamina or breathing or fatigue?,Does this person's condition affect their vision?,Does this person's condition affect them in another way?,How long has the household continuously lived in the local authority area of the new letting?,How long has the household been on the local authority waiting list for the new letting?,What is the tenant’s main reason for the household leaving their last settled home?,"If 'Other', what was the main reason for leaving their last settled home?",Where was the household immediately before this letting?,Did the household experience homelessness immediately before this letting?,Do you know the postcode of the household's last settled home?,Part 1 of postcode of last settled home,Part 2 of postcode of last settled home,What is the local authority of the household's last settled home?,Was the household given 'reasonable preference' by the local authority?,"Reasonable preference reason + +They were homeless or about to lose their home (within 56 days)","Reasonable preference reason + +They were living in unsanitary, overcrowded or unsatisfactory housing","Reasonable preference reason + +They needed to move due to medical and welfare reasons (including disability)","Reasonable preference reason + +They needed to move to avoid hardship to themselves or others","Reasonable preference reason + +Don't know",Was the letting made under Choice-Based Lettings (CBL)?,Was the letting made under the Common Allocation Policy (CAP)? ,Was the letting made under the Common Housing Register (CHR)?,What was the source of referral for this letting?,Do you know the household's combined total income after tax?,How often does the household receive income?,How much income does the household have in total?,Is the tenant likely to be receiving any of these housing-related benefits?,"How much of the household's income is from Universal Credit, state pensions or benefits?",Does the household pay rent or other charges for the accommodation?,How often does the household pay rent and other charges?,"If this is a care home, how much does the household pay every [time period]?",What is the basic rent?,What is the service charge?,What is the personal service charge?,What is the support charge?,Total charge,"After the household has received any housing-related benefits, will they still need to pay for rent and charges?",What do you expect the outstanding amount to be? +Additional info,Organisation's CORE ID,,"If using new core then this will be the email address. If left empty, the letting log will be assigned to the account used to upload the log.","General needs housing includes both self-contained and shared housing without support or specific adaptations. Supported housing includes direct access hostels, group homes, residential care and nursing homes.",,This is a letting to the same tenant in the same property,,,,,,,This is how you usually refer to this tenancy on your own systems,This is how you usually refer to this property on your own systems,Provide management code if you have one,"Provide scheme code, include the 'S' at the beginning if it has one",Provide location code if you have one,"The UPRN is the Unique Property Reference Number. It's created by the Ordnance Survey so it's a unique number system used across all housing providers, all sectors (i.e. not just social housing) and all across the UK.",,,,,,,,,,"Do not include the offer that led to this letting. +This is after the last tenancy ended. If the property is being offered for let for the first time, enter 0.",,,,"If shared accommodation, enter the number of bedrooms occupied by this household. A bedsit has 1 bedroom.","This is the date the property became available to let. For a re-let, it's the day after the previous tenant's contract ends. For a new build, conversion, acquisition or lease it's the day after the provider legally took over possession or management.",,,"Major repairs are works that could not be reasonably carried out with a tenant living at the property. For example, structural repairs.",,,,Also known as an ‘introductory period’,"Fixed-term tenancies are for a set time (up to 20 years). Licence agreements are on a rolling basis, mainly for supported housing. Local authorities mostly provide secure tenancies, and housing associations mostly provide assured (ASTs).",,Not including starter or introductory period,,Make sure the tenant has seen the attached privacy notice before completing this log,"This is the household member who does the most paid work. If several people do the same paid work, it's the oldest household member.",,,,,A child is anyone eligible for child benefit: under age 16 or under 20 if still in full-time education.,Mark children under 1 as 1.,,,A child is anyone eligible for child benefit: under age 16 or under 20 if still in full-time education.,Mark children under 1 as 1.,,,A child is anyone eligible for child benefit: under age 16 or under 20 if still in full-time education.,Mark children under 1 as 1.,,,A child is anyone eligible for child benefit: under age 16 or under 20 if still in full-time education.,Mark children under 1 as 1.,,,A child is anyone eligible for child benefit: under age 16 or under 20 if still in full-time education.,Mark children under 1 as 1.,,,A child is anyone eligible for child benefit: under age 16 or under 20 if still in full-time education.,Mark children under 1 as 1.,,,A child is anyone eligible for child benefit: under age 16 or under 20 if still in full-time education.,Mark children under 1 as 1.,,,"Excluding national service. +If several household members have these links, answer for regular first. If no regular, answer for reserve. If no reserve, answer for spouses or civil partners.",,,,,,,,,,,"For example, lifting and carrying objects, or using a keyboard",,"For example, deafness or partial hearing",,"For example, depression or anxiety",,"Anything associated with autism spectrum disorder (ASD), including Asperger’s or attention deficit hyperactivity disorder (ADHD)",,"For example, blindness or partial sight",,,,"‘Last settled home' means last long-standing home. For tenants who had temporary accommodation or slept rough, it's where they lived previously.",,,,,,,,Social housing 'reasonable preference' is also known as 'priority need',,,,,,,,,,,,"Include any income after tax from employment, pensions, and Universal Credit. Don't include National Insurance (NI) contributions and tax, housing benefit, child benefit, or council tax support.",,,"If rent is charged on the property then answer Yes, even if tenants do not pay it themselves.",,,"Amount paid before any service charges, for example hot water or cleaning. Households may get household benefits towards basic rent.","For example, cleaning. Households may get household benefits towards service charge",For example heating or hot water. This doesn’t include housing benefit or Universal Credit.,Any support service charges included in the tenancy agreement,Unnecessary for new CORE users,,Approximate figure only +Values,Numeric,,"Email format if using new CORE. +Alphanumeric, except for commas if using old CORE.",1 - 2,1 - 12,1 - 2,1 - 31,1 - 12,23 - 24,1 - 3,,Text,"Alphanumeric, max 13 characters","Alphanumeric, max 12 characters",1 - 999,Alphanumeric,1 - 999,Numeric,Alphanumeric,,Text,,XX(XX),XXX,"ONS CODE: Alphanumeric, 9 characters beginning with 'E'",1 - 3 or 5 - 8,"5 - 6, or 8 - 20",0+,"1 - 2, 4 or 6 - 10",1 - 2,,1 - 7,1 - 31,1 - 12,20 - 24,1 - 31,1 - 12,14 - 24,1 - 3,1 - 2,2 - 7,Text,1 - 99,1 - 5,1,16 - 120 or R,"F, M, X or R",1 - 19,"12 - 13, 17 - 21",0 - 10,"P, C, X or R","Numeric, range 1 - 120 or text (upper case 'R') +Must be >= 16 if working situation = 1 - 8 or 0 +Must be <16 if working situation = 9","F, M, X or R",0 - 10,"P, C, X or R","Numeric, range 1 - 120 or text (upper case 'R') +Must be >= 16 if working situation = 1 - 8 or 0 +Must be <16 if working situation = 9","F, M, X or R",0 - 10,"P, C, X or R","Numeric, range 1 - 120 or text (upper case 'R') +Must be >= 16 if working situation = 1 - 8 or 0 +Must be <16 if working situation = 9","F, M, X or R",0 - 10,"P, C, X or R","Numeric, range 1 - 120 or text (upper case 'R') +Must be >= 16 if working situation = 1 - 8 or 0 +Must be <16 if working situation = 9","F, M, X or R",0 - 10,"P, C, X or R","Numeric, range 1 - 120 or text (upper case 'R') +Must be >= 16 if working situation = 1 - 8 or 0 +Must be <16 if working situation = 9","F, M, X or R",0 - 10,"P, C, X or R","Numeric, range 1 - 120 or text (upper case 'R') +Must be >= 16 if working situation = 1 - 8 or 0 +Must be <16 if working situation = 9","F, M, X or R",0 - 10,"P, C, X or R","Numeric, range 1 - 120 or text (upper case 'R') +Must be >= 16 if working situation = 1 - 8 or 0 +Must be <16 if working situation = 9","F, M, X or R",0 - 10,1 - 6,3 - 6,1 - 3,,1 or empty,,,,,,1 - 3,1 or empty,,,,,,,,,,1 - 2 or 5 - 10,2 or 5 - 10,"1 - 2, 4, 8 - 14, 16 - 20, 28 - 31 or 34 - 49",Text,"3 - 4, 6 - 7, 9 - 10, 13 - 14, 18 - 19, 21 or 23 - 37",1 or 11,1 - 2,XX(XX),XXX,"ONS CODE: Alphanumeric, 9 characters beginning with 'E'",1 - 3,1 or empty,,,,,1 - 2,,,"1 - 4, 7 - 10, 12 - 17",1 - 3,,0 - 99999,"1, 3, 6, 9 or 10",1 - 4,0 - 1,1 - 10,xxxx.xx,,,,,,1 - 3,xxxx.xx +Can be empty?,No,,Yes,No,,,No,,,"Yes, if letting type is not an Affordable Rent letting (if field 5 = 1 - 4 or 9 - 12)","Yes, if letting type is not an Intermediate Rent letting (if field 5 = 1 - 8)","Yes, if letting type is not an Intermediate Rent letting (if field 5 = 1 - 8) or if 'Other intermediate rent product' is not selected for type of Intermediate Rent (if field 11 is not 3)",Yes,,"Yes, if letting is general needs (if field 4 = 1) or if location code is provided (if field 17 is not empty)","Yes, if letting is general needs (if field 4 = 1)","Yes, if letting is general needs (if field 4 = 1) or if management code is provided (if field 15 is not empty)","Yes, if letting is supported housing (if field 4 = 2) or if the property's postcode is not empty (if fields 23 and 24 contain full and valid entries)","Yes, if letting is supported housing (if field 4 = 2) or if property's UPRN and local authority are known (if fields 18 and 25 are not empty)",Yes,"Yes, if letting is supported housing (if field 4 = 2) or if property's UPRN and local authority are known (if fields 18 and 25 are not empty)",Yes,"Yes, if letting is supported housing (if field 4 = 2) or if property's UPRN and local authority are known (if fields 18 and 25 are not empty)",,"Yes, if letting is supported housing (if field 4 = 2)","Yes, if letting is a renewal (if field 6 = 1) or a first-time let (if field 27 = 15 - 17)","Yes, if letting is a renewal (if field 6 = 1)",,"Yes, if letting is supported housing (if field 4 = 2)",,,,"Yes, if letting is a renewal (if field 6 = 1)",,,Yes,,,No,,,"Yes, if 'Other' is not selected for tenancy type (if field 41 is not 3)","Yes, if letting is not a fixed-term tenancy (if field 41 = 2, 3, 5 or 7)","Yes, if letting is general needs (if field 4 = 1)",No,No,,,,,"Yes, if the other fields about this person (fields 52, 53 and 54) are also empty","Yes, if the other fields about this person (fields 51, 53 and 54) are also empty","Yes, if the other fields about this person (fields 51, 52 and 54) are also empty","Yes, if the other fields about this person (fields 51, 52 and 53) are also empty","Yes, if the other fields about this person (fields 56, 57 and 58) are also empty","Yes, if the other fields about this person (fields 55, 57 and 58) are also empty","Yes, if the other fields about this person (fields 55, 56 and 58) are also empty","Yes, if the other fields about this person (fields 55, 56 and 57) are also empty","Yes, if the other fields about this person (fields 60, 61 and 62) are also empty","Yes, if the other fields about this person (fields 59, 61 and 62) are also empty","Yes, if the other fields about this person (fields 59, 60 and 62) are also empty","Yes, if the other fields about this person (fields 59, 60 and 61) are also empty","Yes, if the other fields about this person (fields 64, 65 and 66) are also empty","Yes, if the other fields about this person (fields 63, 65 and 66) are also empty","Yes, if the other fields about this person (fields 63, 64 and 66) are also empty","Yes, if the other fields about this person (fields 63, 64 and 65) are also empty","Yes, if the other fields about this person (fields 68, 69 and 70) are also empty","Yes, if the other fields about this person (fields 67, 69 and 70) are also empty","Yes, if the other fields about this person (fields 67, 68 and 70) are also empty","Yes, if the other fields about this person (fields 67, 68 and 69) are also empty","Yes, if the other fields about this person (fields 72, 73 and 74) are also empty","Yes, if the other fields about this person (fields 71, 73 and 74) are also empty","Yes, if the other fields about this person (fields 71, 72 and 74) are also empty","Yes, if the other fields about this person (fields 71, 72 and 73) are also empty","Yes, if the other fields about this person (fields 76, 77 and 78) are also empty","Yes, if the other fields about this person (fields 75, 77 and 78) are also empty","Yes, if the other fields about this person (fields 75, 76 and 78) are also empty","Yes, if the other fields about this person (fields 75, 76 and 77) are also empty",No,"Yes, if no one in the household is a current or former regular (if field 79 is not 1)","Yes, if no one in the household is a current or former regular or reserve (if field 79 = 2, 3, 5 or 6)",No,"Yes, if no household members have access needs or if it is unknown (if field 87 or 88 = 1)",,,,"Yes, if a household member has an access need (if at least one of fields 83, 84, 85 and 86 = 1)",,No,"Yes, if no one in the household has a physical or mental health condition (if field 89 is 2 or 3). +If someone in the household does have such a condition (if field 89 = 1), then at least 1 of these fields must be 1.",,,,,,,,,,No,"Yes, if letting is a renewal (if field 6 = 1)",No,"Yes, if 'Other' is not selected for reason for leaving last settled home (if field 102 is not 20)","Yes, if letting is a renewal (if field 6 = 1)",No,,"Yes, if postcode of household's last settled home is not known (if 106 = 2)",,Yes,No,"If household was given 'reasonable preference' (if field 110 = 1), at least one of these fields must be 1. +If household was not given 'reasonable preference' (if field 110 = 2 or 3), these fields will be ignored.",,,,,No,,,"Yes, if letting is a renewal (if field 6 = 1)",No,"Yes, if household's income is unknown (if field 120 = 2 or 3)",,No,,"Yes, if all rent and accommodation charges are provided (if fields 127 - 132 are not empty)",No,"Yes, if accommodation is not a care home (if field 125 = 1) and all rent and accommodation charges are provided (if fields 128 - 132 are not empty)","Yes, if the household does not pay rent (if field 125 = 1) or if the accommodation is not a care home (if field 127 is empty)",Yes,,,"Yes, if submitting data to new CORE, if the household does not pay rent (if field 125 = 1) or if the accommodation is not a care home (if field 127 is empty)","Yes, if the household doesn't receive housing benefits, or if it is unknown (if field 123 = 3, 9 or 10)","Yes, if the household does not need to pay rent/charges after receiving housing benefits (if field133 = 2 or 3)" +Type of letting the question applies to,,,,,,,,,,Affordable Rent only,Intermediate Rent only,,,,Supported housing only,,,General needs only,,,,,,,,,,,General needs only,,,,,,,,,,,,,,,Supported housing only,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Supported housing only,,,,,,,,, +Duplicate check field?,Yes,,,,,,Yes,,,,,,Yes,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Yes,,,,Yes,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +Bulk upload field number,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134 diff --git a/spec/services/bulk_upload/lettings/validator_spec.rb b/spec/services/bulk_upload/lettings/validator_spec.rb index a9c181b00..595192210 100644 --- a/spec/services/bulk_upload/lettings/validator_spec.rb +++ b/spec/services/bulk_upload/lettings/validator_spec.rb @@ -11,12 +11,6 @@ RSpec.describe BulkUpload::Lettings::Validator do let(:file) { Tempfile.new } describe "validations" do - context "when file is empty" do - it "is not valid" do - expect(validator).not_to be_valid - end - end - context "when 2022" do let(:bulk_upload) { create(:bulk_upload, user:, year: 2022) } @@ -34,6 +28,25 @@ RSpec.describe BulkUpload::Lettings::Validator do end end + context "and is empty" do + it "is not valid" do + expect(validator).not_to be_valid + expect(validator.errors["base"]).to eql(["Template is blank - The template must be filled in for us to create the logs and check if data is correct."]) + end + end + + context "and has a new line in it (empty)" do + before do + file.write("\n") + file.rewind + end + + it "is not valid" do + expect(validator).not_to be_valid + expect(validator.errors["base"]).to eql(["Template is blank - The template must be filled in for us to create the logs and check if data is correct."]) + end + end + context "and doesn't have too many columns" do before do file.write(("a" * 95).chars.join(",")) @@ -71,6 +84,13 @@ RSpec.describe BulkUpload::Lettings::Validator do end end + context "and is empty" do + it "is not valid" do + expect(validator).not_to be_valid + expect(validator.errors["base"]).to eql(["Template is blank - The template must be filled in for us to create the logs and check if data is correct."]) + end + end + context "and file has too few valid headers" do let(:seed) { rand } let(:log_to_csv) { BulkUpload::LettingsLogToCsv.new(log:) } @@ -128,6 +148,13 @@ RSpec.describe BulkUpload::Lettings::Validator do end end + context "and is empty" do + it "is not valid" do + expect(validator).not_to be_valid + expect(validator.errors["base"]).to eql(["Template is blank - The template must be filled in for us to create the logs and check if data is correct."]) + end + end + context "and doesn't have too many columns" do before do file.write(("a" * 142).chars.join(",")) @@ -142,6 +169,13 @@ RSpec.describe BulkUpload::Lettings::Validator do end context "when file has headers" do + context "and is empty" do + it "is not valid" do + expect(validator).not_to be_valid + expect(validator.errors["base"]).to eql(["Template is blank - The template must be filled in for us to create the logs and check if data is correct."]) + end + end + context "and file has extra invalid headers" do let(:seed) { rand } let(:log_to_csv) { BulkUpload::LettingsLogToCsv.new(log:) } diff --git a/spec/services/bulk_upload/processor_spec.rb b/spec/services/bulk_upload/processor_spec.rb index 733f8ed5e..f01f2daf7 100644 --- a/spec/services/bulk_upload/processor_spec.rb +++ b/spec/services/bulk_upload/processor_spec.rb @@ -207,6 +207,98 @@ RSpec.describe BulkUpload::Processor do end end + context "when processing an empty file" do + let(:mock_downloader) do + instance_double( + BulkUpload::Downloader, + call: nil, + path:, + delete_local_file!: nil, + ) + end + + let(:file) { Tempfile.new } + let(:path) { file.path } + + before do + allow(BulkUpload::Downloader).to receive(:new).with(bulk_upload:).and_return(mock_downloader) + end + + it "sends failure email" do + mail_double = instance_double("ActionMailer::MessageDelivery", deliver_later: nil) + + allow(BulkUploadMailer).to receive(:send_bulk_upload_failed_service_error_mail).and_return(mail_double) + + processor.call + + expect(BulkUploadMailer).to have_received(:send_bulk_upload_failed_service_error_mail).with( + bulk_upload:, + errors: ["Template is blank - The template must be filled in for us to create the logs and check if data is correct."], + ) + expect(mail_double).to have_received(:deliver_later) + end + end + + context "when processing an empty file with headers" do + context "when 2022-23" do + let(:mock_downloader) do + instance_double( + BulkUpload::Downloader, + call: nil, + path: file_fixture("2022_23_lettings_bulk_upload_empty_with_headers.csv"), + delete_local_file!: nil, + ) + end + + before do + allow(BulkUpload::Downloader).to receive(:new).with(bulk_upload:).and_return(mock_downloader) + end + + it "sends failure email" do + mail_double = instance_double("ActionMailer::MessageDelivery", deliver_later: nil) + + allow(BulkUploadMailer).to receive(:send_bulk_upload_failed_service_error_mail).and_return(mail_double) + + processor.call + + expect(BulkUploadMailer).to have_received(:send_bulk_upload_failed_service_error_mail).with( + bulk_upload:, + errors: ["Template is blank - The template must be filled in for us to create the logs and check if data is correct."], + ) + expect(mail_double).to have_received(:deliver_later) + end + end + end + + context "when 2023-24" do + let(:mock_downloader) do + instance_double( + BulkUpload::Downloader, + call: nil, + path: file_fixture("2023_24_lettings_bulk_upload_empty_with_headers.csv"), + delete_local_file!: nil, + ) + end + + before do + allow(BulkUpload::Downloader).to receive(:new).with(bulk_upload:).and_return(mock_downloader) + end + + it "sends failure email" do + mail_double = instance_double("ActionMailer::MessageDelivery", deliver_later: nil) + + allow(BulkUploadMailer).to receive(:send_bulk_upload_failed_service_error_mail).and_return(mail_double) + + processor.call + + expect(BulkUploadMailer).to have_received(:send_bulk_upload_failed_service_error_mail).with( + bulk_upload:, + errors: ["Template is blank - The template must be filled in for us to create the logs and check if data is correct."], + ) + expect(mail_double).to have_received(:deliver_later) + end + end + context "when a bulk upload has an in progress log" do let(:mock_downloader) do instance_double( diff --git a/spec/services/bulk_upload/sales/validator_spec.rb b/spec/services/bulk_upload/sales/validator_spec.rb index 8d543b4a6..165abf8df 100644 --- a/spec/services/bulk_upload/sales/validator_spec.rb +++ b/spec/services/bulk_upload/sales/validator_spec.rb @@ -13,6 +13,19 @@ RSpec.describe BulkUpload::Sales::Validator do context "when file is empty" do it "is not valid" do expect(validator).not_to be_valid + expect(validator.errors["base"]).to eql(["Template is blank - The template must be filled in for us to create the logs and check if data is correct."]) + end + end + + context "and has a new line in it (empty)" do + before do + file.write("\n") + file.rewind + end + + it "is not valid" do + expect(validator).not_to be_valid + expect(validator.errors["base"]).to eql(["Template is blank - The template must be filled in for us to create the logs and check if data is correct."]) end end