Browse Source

Merge branch 'main' into CLDC-1770-activating-soon-locations-validations

pull/1739/head
natdeanlewissoftwire 3 years ago
parent
commit
874c33c40e
  1. 4
      app/controllers/form_controller.rb
  2. 1
      app/helpers/interruption_screen_helper.rb
  3. 21
      app/models/form.rb
  4. 25
      app/models/form/lettings/pages/max_rent_value_check.rb
  5. 11
      app/models/form/lettings/pages/min_rent_value_check.rb
  6. 15
      app/models/form/lettings/questions/max_rent_value_check.rb
  7. 3
      app/models/form/lettings/questions/min_rent_value_check.rb
  8. 14
      app/models/form_handler.rb
  9. 8
      app/models/log.rb
  10. 16
      app/models/validations/sales/setup_validations.rb
  11. 16
      app/models/validations/setup_validations.rb
  12. 2
      app/services/bulk_upload/lettings/year2022/csv_parser.rb
  13. 2
      app/services/bulk_upload/sales/year2022/csv_parser.rb
  14. 2
      app/views/form/_check_answers_summary_list.html.erb
  15. 4
      config/locales/en.yml
  16. 4
      config/sidekiq_cron_schedule.yml
  17. 2
      docs/adr/adr-019-form-end-dates.md
  18. 4
      spec/features/bulk_upload_lettings_logs_spec.rb
  19. 2
      spec/features/form/accessible_autocomplete_spec.rb
  20. 10
      spec/features/form/check_answers_page_lettings_logs_spec.rb
  21. 2
      spec/features/form/checkboxes_spec.rb
  22. 4
      spec/features/form/conditional_questions_spec.rb
  23. 4
      spec/features/form/form_navigation_spec.rb
  24. 2
      spec/features/form/page_routing_spec.rb
  25. 2
      spec/features/form/progressive_total_field_spec.rb
  26. 2
      spec/features/form/saving_data_spec.rb
  27. 2
      spec/features/form/tasklist_page_spec.rb
  28. 4
      spec/features/form/validations_spec.rb
  29. 1
      spec/fixtures/forms/2021_2022.json
  30. 7
      spec/helpers/interruption_screen_helper_spec.rb
  31. 4
      spec/helpers/locations_helper_spec.rb
  32. 2
      spec/helpers/schemes_helper_spec.rb
  33. 4
      spec/models/form/lettings/pages/max_rent_value_check_spec.rb
  34. 13
      spec/models/form/lettings/pages/min_rent_value_check_spec.rb
  35. 44
      spec/models/form/lettings/questions/max_rent_value_check_spec.rb
  36. 44
      spec/models/form/lettings/questions/min_rent_value_check_spec.rb
  37. 4
      spec/models/form_spec.rb
  38. 4
      spec/models/lettings_log_spec.rb
  39. 4
      spec/models/sales_log_spec.rb
  40. 46
      spec/models/validations/sales/setup_validations_spec.rb
  41. 42
      spec/models/validations/setup_validations_spec.rb
  42. 31
      spec/requests/form_controller_spec.rb
  43. 29
      spec/requests/lettings_logs_controller_spec.rb
  44. 21
      spec/requests/locations_controller_spec.rb
  45. 2
      spec/requests/schemes_controller_spec.rb
  46. 2
      spec/services/bulk_upload/lettings/year2022/csv_parser_spec.rb
  47. 2
      spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb
  48. 2
      spec/services/bulk_upload/sales/year2022/csv_parser_spec.rb

4
app/controllers/form_controller.rb

@ -11,7 +11,7 @@ class FormController < ApplicationController
mandatory_questions_with_no_response = mandatory_questions_with_no_response(responses_for_page)
if mandatory_questions_with_no_response.empty? && @log.update(responses_for_page.merge(updated_by: current_user))
flash[:notice] = "You have successfully updated #{@page.questions.map(&:check_answer_label).first.downcase}" if previous_interruption_screen_page_id.present?
flash[:notice] = "You have successfully updated #{@page.questions.map(&:check_answer_label).reject { |label| label.to_s.empty? }.first&.downcase}" if previous_interruption_screen_page_id.present?
redirect_to(successful_redirect_path)
else
mandatory_questions_with_no_response.map do |question|
@ -208,7 +208,7 @@ private
def check_collection_period
return unless @log
redirect_to lettings_log_path(@log) unless @log.collection_period_open?
redirect_to lettings_log_path(@log) unless @log.collection_period_open_for_editing?
end
CONFIRMATION_PAGE_IDS = %w[uprn_confirmation].freeze

1
app/helpers/interruption_screen_helper.rb

@ -1,5 +1,6 @@
module InterruptionScreenHelper
def display_informative_text(informative_text, log)
return informative_text if informative_text.is_a? String
return "" unless informative_text["arguments"]
translation_params = {}

21
app/models/form.rb

@ -1,16 +1,12 @@
class Form
attr_reader :form_definition, :sections, :subsections, :pages, :questions,
:start_date, :end_date, :submission_deadline, :type, :name, :setup_definition,
:start_date, :new_logs_end_date, :submission_deadline, :type, :name, :setup_definition,
:setup_sections, :form_sections, :unresolved_log_redirect_page_id, :edit_end_date
def initialize(form_path, start_year = "", sections_in_form = [], type = "lettings")
if sales_or_start_year_after_2022?(type, start_year)
@start_date = Time.zone.local(start_year, 4, 1)
@end_date = if start_year && start_year.to_i > 2022
Time.zone.local(start_year + 1, 6, 9)
else
Time.zone.local(start_year + 1, 8, 7)
end
@new_logs_end_date = Time.zone.local(start_year + 1, 12, 31) # this is to be manually updated each year when we want to stop users from creating new logs
@submission_deadline = if start_year && start_year.to_i > 2022
Time.zone.local(start_year + 1, 6, 7)
else
@ -26,10 +22,11 @@ class Form
@form_definition = {
"form_type" => type,
"start_date" => start_date,
"end_date" => end_date,
"end_date" => new_logs_end_date,
"sections" => sections,
}
@unresolved_log_redirect_page_id = "tenancy_start_date" if type == "lettings"
@edit_end_date = Time.zone.local(start_year + 1, 12, 31) # this is to be manually updated each year when we want to stop users from editing logs
else
raise "No form definition file exists for given year".freeze unless File.exist?(form_path)
@ -42,12 +39,12 @@ class Form
@pages = subsections.flat_map(&:pages)
@questions = pages.flat_map(&:questions)
@start_date = Time.iso8601(form_definition["start_date"])
@end_date = Time.iso8601(form_definition["end_date"])
@submission_deadline = Time.zone.local(2023, 6, 9)
@new_logs_end_date = Time.zone.local(@start_date.year + 1, 12, 31)
@submission_deadline = Time.zone.local(@start_date.year + 1, 6, 9)
@edit_end_date = Time.zone.local(@start_date.year + 1, 12, 31)
@unresolved_log_redirect_page_id = form_definition["unresolved_log_redirect_page_id"]
end
@edit_end_date = @end_date
@name = "#{start_date.year}_#{end_date.year}_#{type}"
@name = "#{start_date.year}_#{new_logs_end_date.year}_#{type}"
end
def get_subsection(id)
@ -304,7 +301,7 @@ class Form
end
def valid_start_date_for_form?(start_date)
start_date >= self.start_date && start_date <= end_date
start_date >= self.start_date && start_date <= new_logs_end_date
end
def sales_or_start_year_after_2022?(type, start_year)

25
app/models/form/lettings/pages/max_rent_value_check.rb

@ -4,29 +4,18 @@ class Form::Lettings::Pages::MaxRentValueCheck < ::Form::Page
@depends_on = [{ "rent_in_soft_max_range?" => true }]
@title_text = {
"translation" => "soft_validations.rent.outside_range_title",
"arguments" => [
{
"key" => "brent",
"label" => true,
"i18n_template" => "brent",
},
],
}
@informative_text = {
"translation" => "soft_validations.rent.max_hint_text",
"arguments" => [
{
"key" => "field_formatted_as_currency",
"arguments_for_key" => "soft_max_for_period",
"i18n_template" => "soft_max_for_period",
},
],
"arguments" => [{
"key" => "brent",
"label" => true,
"i18n_template" => "brent",
}],
}
@informative_text = I18n.t("soft_validations.rent.informative_text", higher_or_lower: "higher")
@check_answers_card_number = check_answers_card_number
end
def questions
@questions ||= [Form::Lettings::Questions::RentValueCheck.new(nil, nil, self, check_answers_card_number: @check_answers_card_number)]
@questions ||= [Form::Lettings::Questions::MaxRentValueCheck.new(nil, nil, self, check_answers_card_number: @check_answers_card_number)]
end
def interruption_screen_question_ids

11
app/models/form/lettings/pages/min_rent_value_check.rb

@ -10,19 +10,12 @@ class Form::Lettings::Pages::MinRentValueCheck < ::Form::Page
"i18n_template" => "brent",
}],
}
@informative_text = {
"translation" => "soft_validations.rent.min_hint_text",
"arguments" => [{
"key" => "field_formatted_as_currency",
"arguments_for_key" => "soft_min_for_period",
"i18n_template" => "soft_min_for_period",
}],
}
@informative_text = I18n.t("soft_validations.rent.informative_text", higher_or_lower: "lower")
@check_answers_card_number = check_answers_card_number
end
def questions
@questions ||= [Form::Lettings::Questions::RentValueCheck.new(nil, nil, self, check_answers_card_number: @check_answers_card_number)]
@questions ||= [Form::Lettings::Questions::MinRentValueCheck.new(nil, nil, self, check_answers_card_number: @check_answers_card_number)]
end
def interruption_screen_question_ids

15
app/models/form/lettings/questions/max_rent_value_check.rb

@ -0,0 +1,15 @@
class Form::Lettings::Questions::MaxRentValueCheck < ::Form::Question
def initialize(id, hsh, page, check_answers_card_number:)
super(id, hsh, page)
@id = "rent_value_check"
@check_answer_label = "Total rent confirmation"
@header = "Are you sure this is correct?"
@type = "interruption_screen"
@hint_text = I18n.t("soft_validations.rent.hint_text", higher_or_lower: "higher")
@check_answers_card_number = check_answers_card_number
@answer_options = ANSWER_OPTIONS
@hidden_in_check_answers = { "depends_on" => [{ "rent_value_check" => 0 }, { "rent_value_check" => 1 }] }
end
ANSWER_OPTIONS = { "0" => { "value" => "Yes" }, "1" => { "value" => "No" } }.freeze
end

3
app/models/form/lettings/questions/rent_value_check.rb → app/models/form/lettings/questions/min_rent_value_check.rb

@ -1,10 +1,11 @@
class Form::Lettings::Questions::RentValueCheck < ::Form::Question
class Form::Lettings::Questions::MinRentValueCheck < ::Form::Question
def initialize(id, hsh, page, check_answers_card_number:)
super(id, hsh, page)
@id = "rent_value_check"
@check_answer_label = "Total rent confirmation"
@header = "Are you sure this is correct?"
@type = "interruption_screen"
@hint_text = I18n.t("soft_validations.rent.hint_text", higher_or_lower: "lower")
@check_answers_card_number = check_answers_card_number
@answer_options = ANSWER_OPTIONS
@hidden_in_check_answers = { "depends_on" => [{ "rent_value_check" => 0 }, { "rent_value_check" => 1 }] }

14
app/models/form_handler.rb

@ -109,12 +109,22 @@ class FormHandler
def lettings_in_crossover_period?(now: Time.zone.now)
forms = lettings_forms.values
forms.count { |form| now.between?(form.start_date, form.end_date) } > 1
forms.count { |form| now.between?(form.start_date, form.new_logs_end_date) } > 1
end
def lettings_in_edit_crossover_period?(now: Time.zone.now)
forms = lettings_forms.values
forms.count { |form| now.between?(form.start_date, form.edit_end_date) } > 1
end
def sales_in_crossover_period?(now: Time.zone.now)
forms = sales_forms.values
forms.count { |form| now.between?(form.start_date, form.end_date) } > 1
forms.count { |form| now.between?(form.start_date, form.new_logs_end_date) } > 1
end
def sales_in_edit_crossover_period?(now: Time.zone.now)
forms = sales_forms.values
forms.count { |form| now.between?(form.start_date, form.edit_end_date) } > 1
end
def use_fake_forms!(fake_forms = nil)

8
app/models/log.rb

@ -94,7 +94,13 @@ class Log < ApplicationRecord
def collection_period_open?
return false if older_than_previous_collection_year?
form.end_date > Time.zone.today
form.new_logs_end_date > Time.zone.today
end
def collection_period_open_for_editing?
return false if older_than_previous_collection_year?
form.edit_end_date > Time.zone.today
end
def blank_invalid_non_setup_fields!

16
app/models/validations/sales/setup_validations.rb

@ -5,7 +5,13 @@ module Validations::Sales::SetupValidations
def validate_saledate_collection_year(record)
return unless record.saledate && date_valid?("saledate", record) && FeatureToggle.saledate_collection_window_validation_enabled?
unless record.saledate.between?(active_collection_start_date, current_collection_end_date)
first_collection_start_date = if record.saledate_was.present?
editable_collection_start_date
else
active_collection_start_date
end
unless record.saledate.between?(first_collection_start_date, current_collection_end_date)
record.errors.add :saledate, saledate_validation_error_message
end
end
@ -28,6 +34,14 @@ private
end
end
def editable_collection_start_date
if FormHandler.instance.sales_in_edit_crossover_period?
previous_collection_start_date
else
current_collection_start_date
end
end
def saledate_validation_error_message
current_end_year_long = current_collection_end_date.strftime("#{current_collection_end_date.day.ordinalize} %B %Y")

16
app/models/validations/setup_validations.rb

@ -5,7 +5,13 @@ module Validations::SetupValidations
def validate_startdate_setup(record)
return unless record.startdate && date_valid?("startdate", record)
unless record.startdate.between?(active_collection_start_date, current_collection_end_date)
first_collection_start_date = if record.startdate_was.present?
editable_collection_start_date
else
active_collection_start_date
end
unless record.startdate.between?(first_collection_start_date, current_collection_end_date)
record.errors.add :startdate, startdate_validation_error_message
end
end
@ -60,6 +66,14 @@ private
end
end
def editable_collection_start_date
if FormHandler.instance.lettings_in_edit_crossover_period?
previous_collection_start_date
else
current_collection_start_date
end
end
def startdate_validation_error_message
current_end_year_long = current_collection_end_date.strftime("#{current_collection_end_date.day.ordinalize} %B %Y")

2
app/services/bulk_upload/lettings/year2022/csv_parser.rb

@ -63,7 +63,7 @@ class BulkUpload::Lettings::Year2022::CsvParser
end
def wrong_template_for_year?
!(first_record_start_date >= form.start_date && first_record_start_date <= form.end_date)
!(first_record_start_date >= form.start_date && first_record_start_date <= form.new_logs_end_date)
end
private

2
app/services/bulk_upload/sales/year2022/csv_parser.rb

@ -45,7 +45,7 @@ class BulkUpload::Sales::Year2022::CsvParser
end
def wrong_template_for_year?
!(first_record_sale_date >= form.start_date && first_record_sale_date <= form.end_date)
!(first_record_sale_date >= form.start_date && first_record_sale_date <= form.new_logs_end_date)
end
private

2
app/views/form/_check_answers_summary_list.html.erb

@ -25,7 +25,7 @@
<% end %>
<% end %>
<% if @log.collection_period_open? %>
<% if @log.collection_period_open_for_editing? %>
<% row.action(
text: question.action_text(@log),
href: action_href(@log, question.page.id, referrer),

4
config/locales/en.yml

@ -600,8 +600,8 @@ en:
over_soft_max_for_la_combined: "You told us the combined income of this household is %{combined_income}. This seems high. Are you sure this is correct?"
rent:
outside_range_title: "You told us the rent is %{brent}"
min_hint_text: "The minimum rent expected for this type of property in this local authority is %{soft_min_for_period}."
max_hint_text: "The maximum rent expected for this type of property in this local authority is %{soft_max_for_period}."
informative_text: "This is %{higher_or_lower} than we would expect."
hint_text: "Check the following:<ul class=\"govuk-body-l app-panel--interruption\"><li>the decimal point</li><li>the frequency, for example every week or every calendar month</li><li>the rent type is correct, for example affordable or social rent</li></ul>"
purchase_price:
title_text: "You told us the purchase price is %{value}"
hint_text: "This is %{higher_or_lower} than we would expect"

4
config/sidekiq_cron_schedule.yml

@ -1,7 +1,3 @@
data_export_csv:
cron: "every day at 4am"
class: "DataExportCsvJob"
queue: default
data_export_xml:
cron: "every day at 5am"
class: "DataExportXmlJob"

2
docs/adr/adr-019-form-end-dates.md

@ -13,5 +13,5 @@ Also, if incorrect data is found during QA process, data providers might be aske
To accommodate the different end dates, we will now store 3 different dates on the form definition:
- Submission deadline (submission_deadline) - this is the date displayed at the top of a completed log in lettings and sales - "You can review and make changes to this log until 9 June 2024.". Nothing happens on this date
- New logs end date (end_date) - no new logs for that collection year can be submitted, but logs can be edited
- New logs end date (new_logs_end_date) - no new logs for that collection year can be submitted, but logs can be edited
- Edit and delete logs end date (edit_end_date) - logs can no longer be edited or deleted. Completed logs can still be viewed. Materials / references to the collection year are removed.

4
spec/features/bulk_upload_lettings_logs_spec.rb

@ -83,7 +83,7 @@ RSpec.describe "Bulk upload lettings log" do
context "when not it crossover period" do
it "shows journey with year option" do
Timecop.freeze(2023, 10, 1) do
Timecop.freeze(2024, 1, 1) do
visit("/lettings-logs")
expect(page).to have_link("Upload lettings logs in bulk")
click_link("Upload lettings logs in bulk")
@ -98,7 +98,7 @@ RSpec.describe "Bulk upload lettings log" do
context "when the collection year isn't 22/23" do
it "shows journey without the needstype" do
Timecop.freeze(2023, 10, 1) do
Timecop.freeze(2024, 1, 1) do
visit("/lettings-logs")
expect(page).to have_link("Upload lettings logs in bulk")
click_link("Upload lettings logs in bulk")

2
spec/features/form/accessible_autocomplete_spec.rb

@ -28,7 +28,7 @@ RSpec.describe "Accessible Autocomplete" do
end
before do
allow(lettings_log.form).to receive(:end_date).and_return(Time.zone.today + 1.day)
allow(lettings_log.form).to receive(:new_logs_end_date).and_return(Time.zone.today + 1.day)
sign_in user
end

10
spec/features/form/check_answers_page_lettings_logs_spec.rb

@ -51,8 +51,8 @@ RSpec.describe "Lettings Log Check Answers Page" do
let(:fake_2021_2022_form) { Form.new("spec/fixtures/forms/2021_2022.json") }
before do
allow(lettings_log.form).to receive(:end_date).and_return(Time.zone.today + 1.day)
allow(fake_2021_2022_form).to receive(:end_date).and_return(Time.zone.today + 1.day)
allow(lettings_log.form).to receive(:new_logs_end_date).and_return(Time.zone.today + 1.day)
allow(fake_2021_2022_form).to receive(:new_logs_end_date).and_return(Time.zone.today + 1.day)
sign_in user
allow(FormHandler.instance).to receive(:current_lettings_form).and_return(fake_2021_2022_form)
end
@ -72,7 +72,7 @@ RSpec.describe "Lettings Log Check Answers Page" do
it "has question headings based on the subsection" do
visit("/lettings-logs/#{id}/#{subsection}/check-answers")
question_labels = ["Tenant code", "Lead tenant’s age", "Lead tenant’s gender identity", "Number of Household Members"]
question_labels = ["Tenant code", "Lead tenant’s age", "Number of Household Members"]
question_labels.each do |label|
expect(page).to have_content(label)
end
@ -91,7 +91,7 @@ RSpec.describe "Lettings Log Check Answers Page" do
# This way only the links in the table will get picked up
it "has an answer link for questions missing an answer" do
visit("/lettings-logs/#{empty_lettings_log.id}/#{subsection}/check-answers?referrer=check_answers")
assert_selector "a", text: /Answer (?!the missing questions)/, count: 5
assert_selector "a", text: /Answer (?!the missing questions)/, count: 4
assert_selector "a", text: "Change", count: 0
expect(page).to have_link("Answer", href: "/lettings-logs/#{empty_lettings_log.id}/person-1-age?referrer=check_answers")
end
@ -99,7 +99,7 @@ RSpec.describe "Lettings Log Check Answers Page" do
it "has a change link for answered questions" do
fill_in_number_question(empty_lettings_log.id, "age1", 28, "person-1-age")
visit("/lettings-logs/#{empty_lettings_log.id}/#{subsection}/check-answers")
assert_selector "a", text: /Answer (?!the missing questions)/, count: 4
assert_selector "a", text: /Answer (?!the missing questions)/, count: 3
assert_selector "a", text: "Change", count: 1
expect(page).to have_link("Change", href: "/lettings-logs/#{empty_lettings_log.id}/person-1-age?referrer=check_answers")
end

2
spec/features/form/checkboxes_spec.rb

@ -24,7 +24,7 @@ RSpec.describe "Checkboxes" do
end
before do
allow(lettings_log.form).to receive(:end_date).and_return(Time.zone.today + 1.day)
allow(lettings_log.form).to receive(:new_logs_end_date).and_return(Time.zone.today + 1.day)
RequestHelper.stub_http_requests
sign_in user
end

4
spec/features/form/conditional_questions_spec.rb

@ -33,8 +33,8 @@ RSpec.describe "Form Conditional Questions" do
before do
sign_in user
allow(sales_log.form).to receive(:end_date).and_return(Time.zone.today + 1.day)
allow(lettings_log.form).to receive(:end_date).and_return(Time.zone.today + 1.day)
allow(sales_log.form).to receive(:new_logs_end_date).and_return(Time.zone.today + 1.day)
allow(lettings_log.form).to receive(:new_logs_end_date).and_return(Time.zone.today + 1.day)
allow(FormHandler.instance).to receive(:current_lettings_form).and_return(fake_2021_2022_form)
end

4
spec/features/form/form_navigation_spec.rb

@ -44,8 +44,8 @@ RSpec.describe "Form Navigation" do
let(:fake_2021_2022_form) { Form.new("spec/fixtures/forms/2021_2022.json") }
before do
allow(lettings_log.form).to receive(:end_date).and_return(Time.zone.today + 1.day)
allow(fake_2021_2022_form).to receive(:end_date).and_return(Time.zone.today + 1.day)
allow(lettings_log.form).to receive(:new_logs_end_date).and_return(Time.zone.today + 1.day)
allow(fake_2021_2022_form).to receive(:new_logs_end_date).and_return(Time.zone.today + 1.day)
sign_in user
allow(FormHandler.instance).to receive(:current_lettings_form).and_return(fake_2021_2022_form)
end

2
spec/features/form/page_routing_spec.rb

@ -15,7 +15,7 @@ RSpec.describe "Form Page Routing" do
let(:validator) { lettings_log._validators[nil].first }
before do
allow(lettings_log.form).to receive(:end_date).and_return(Time.zone.today + 1.day)
allow(lettings_log.form).to receive(:new_logs_end_date).and_return(Time.zone.today + 1.day)
sign_in user
end

2
spec/features/form/progressive_total_field_spec.rb

@ -22,7 +22,7 @@ RSpec.describe "Accessible Autocomplete" do
end
before do
allow(lettings_log.form).to receive(:end_date).and_return(Time.zone.today + 1.day)
allow(lettings_log.form).to receive(:new_logs_end_date).and_return(Time.zone.today + 1.day)
sign_in user
end

2
spec/features/form/saving_data_spec.rb

@ -37,7 +37,7 @@ RSpec.describe "Form Saving Data" do
end
before do
allow(lettings_log.form).to receive(:end_date).and_return(Time.zone.today + 1.day)
allow(lettings_log.form).to receive(:new_logs_end_date).and_return(Time.zone.today + 1.day)
sign_in user
end

2
spec/features/form/tasklist_page_spec.rb

@ -54,7 +54,7 @@ RSpec.describe "Task List" do
before do
Timecop.freeze(Time.zone.local(2021, 5, 1))
setup_completed_log.update!(startdate: Time.zone.local(2021, 5, 1))
allow(lettings_log.form).to receive(:end_date).and_return(Time.zone.today + 1.day)
allow(lettings_log.form).to receive(:new_logs_end_date).and_return(Time.zone.today + 1.day)
sign_in user
end

4
spec/features/form/validations_spec.rb

@ -42,8 +42,8 @@ RSpec.describe "validations" do
let(:id) { lettings_log.id }
before do
allow(fake_2021_2022_form).to receive(:end_date).and_return(Time.zone.today + 1.day)
allow(lettings_log.form).to receive(:end_date).and_return(Time.zone.today + 1.day)
allow(fake_2021_2022_form).to receive(:new_logs_end_date).and_return(Time.zone.today + 1.day)
allow(lettings_log.form).to receive(:new_logs_end_date).and_return(Time.zone.today + 1.day)
sign_in user
allow(FormHandler.instance).to receive(:current_lettings_form).and_return(fake_2021_2022_form)
end

1
spec/fixtures/forms/2021_2022.json vendored

@ -57,7 +57,6 @@
"questions": {
"sex1": {
"check_answers_card_number": 1,
"check_answer_label": "Lead tenant’s gender identity",
"header": "Which of these best describes the tenant’s gender identity?",
"type": "radio",
"answer_options": {

7
spec/helpers/interruption_screen_helper_spec.rb

@ -163,6 +163,13 @@ RSpec.describe InterruptionScreenHelper do
expect(display_informative_text(informative_text_hash, lettings_log)).to eq("You said this: £12,345.00")
end
end
context "when a string given" do
it "returns the string" do
test_string = "some words"
expect(display_informative_text(test_string, lettings_log)).to eq(test_string)
end
end
end
describe "display_title_text" do

4
spec/helpers/locations_helper_spec.rb

@ -51,7 +51,7 @@ RSpec.describe LocationsHelper do
let(:location) { FactoryBot.create(:location, startdate: nil) }
before do
Timecop.freeze(2022, 10, 10)
Timecop.freeze(2023, 10, 10)
end
after do
@ -201,7 +201,7 @@ RSpec.describe LocationsHelper do
context "when viewing availability" do
context "with no deactivations" do
it "displays current collection start date as availability date if created_at is later than collection start date" do
location.update!(startdate: nil, created_at: Time.zone.local(2023, 8, 16))
location.update!(startdate: nil, created_at: Time.zone.local(2024, 1, 16))
availability_attribute = display_location_attributes(location).find { |x| x[:name] == "Availability" }[:value]
expect(availability_attribute).to eq("Active from 1 April 2023")

2
spec/helpers/schemes_helper_spec.rb

@ -5,7 +5,7 @@ RSpec.describe SchemesHelper do
let(:scheme) { FactoryBot.create(:scheme, created_at: Time.zone.today) }
before do
Timecop.freeze(2022, 10, 10)
Timecop.freeze(2023, 1, 10)
end
after do

4
spec/models/form/lettings/pages/max_rent_value_check_spec.rb

@ -31,10 +31,6 @@ RSpec.describe Form::Lettings::Pages::MaxRentValueCheck, type: :model do
expect(page.title_text).to eq({ "arguments" => [{ "i18n_template" => "brent", "key" => "brent", "label" => true }], "translation" => "soft_validations.rent.outside_range_title" })
end
it "has the correct informative_text" do
expect(page.informative_text).to eq({ "arguments" => [{ "arguments_for_key" => "soft_max_for_period", "i18n_template" => "soft_max_for_period", "key" => "field_formatted_as_currency" }], "translation" => "soft_validations.rent.max_hint_text" })
end
it "has the correct interruption_screen_question_ids" do
expect(page.interruption_screen_question_ids).to eq(%w[brent startdate uprn postcode_full la beds rent_type needstype])
end

13
spec/models/form/lettings/pages/min_rent_value_check_spec.rb

@ -40,19 +40,6 @@ RSpec.describe Form::Lettings::Pages::MinRentValueCheck, type: :model do
})
end
it "has the correct informative_text" do
expect(page.informative_text).to eq({
"translation" => "soft_validations.rent.min_hint_text",
"arguments" => [
{
"key" => "field_formatted_as_currency",
"arguments_for_key" => "soft_min_for_period",
"i18n_template" => "soft_min_for_period",
},
],
})
end
it "has the correct interruption_screen_question_ids" do
expect(page.interruption_screen_question_ids).to eq(%w[brent startdate uprn postcode_full la beds rent_type needstype])
end

44
spec/models/form/lettings/questions/max_rent_value_check_spec.rb

@ -0,0 +1,44 @@
require "rails_helper"
RSpec.describe Form::Lettings::Questions::MaxRentValueCheck, type: :model do
subject(:question) { described_class.new(nil, question_definition, page, check_answers_card_number:) }
let(:question_definition) { nil }
let(:check_answers_card_number) { nil }
let(:page) { instance_double(Form::Page) }
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("rent_value_check")
end
it "has the correct header" do
expect(question.header).to eq("Are you sure this is correct?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Total rent confirmation")
end
it "has the correct type" do
expect(question.type).to eq("interruption_screen")
end
it "has the correct hint" do
expect(question.hint_text).to eq("Check the following:<ul class=\"govuk-body-l app-panel--interruption\"><li>the decimal point</li><li>the frequency, for example every week or every calendar month</li><li>the rent type is correct, for example affordable or social rent</li></ul>")
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"0" => { "value" => "Yes" },
"1" => { "value" => "No" },
})
end
it "has the correct hidden_in_check_answers" do
expect(question.hidden_in_check_answers).to eq({ "depends_on" => [{ "rent_value_check" => 0 }, { "rent_value_check" => 1 }] })
end
end

44
spec/models/form/lettings/questions/min_rent_value_check_spec.rb

@ -0,0 +1,44 @@
require "rails_helper"
RSpec.describe Form::Lettings::Questions::MinRentValueCheck, type: :model do
subject(:question) { described_class.new(nil, question_definition, page, check_answers_card_number:) }
let(:question_definition) { nil }
let(:check_answers_card_number) { nil }
let(:page) { instance_double(Form::Page) }
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("rent_value_check")
end
it "has the correct header" do
expect(question.header).to eq("Are you sure this is correct?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Total rent confirmation")
end
it "has the correct type" do
expect(question.type).to eq("interruption_screen")
end
it "has the correct hint" do
expect(question.hint_text).to eq("Check the following:<ul class=\"govuk-body-l app-panel--interruption\"><li>the decimal point</li><li>the frequency, for example every week or every calendar month</li><li>the rent type is correct, for example affordable or social rent</li></ul>")
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"0" => { "value" => "Yes" },
"1" => { "value" => "No" },
})
end
it "has the correct hidden_in_check_answers" do
expect(question.hidden_in_check_answers).to eq({ "depends_on" => [{ "rent_value_check" => 0 }, { "rent_value_check" => 1 }] })
end
end

4
spec/models/form_spec.rb

@ -371,7 +371,9 @@ RSpec.describe Form, type: :model do
expect(form.questions.count).to eq(13)
expect(form.questions.first.id).to eq("owning_organisation_id")
expect(form.start_date).to eq(Time.zone.parse("2022-04-01"))
expect(form.end_date).to eq(Time.zone.parse("2023-08-07"))
expect(form.new_logs_end_date).to eq(Time.zone.parse("2023-12-31"))
expect(form.edit_end_date).to eq(Time.zone.parse("2023-12-31"))
expect(form.submission_deadline).to eq(Time.zone.parse("2023-06-09"))
expect(form.unresolved_log_redirect_page_id).to eq(nil)
end

4
spec/models/lettings_log_spec.rb

@ -3040,7 +3040,7 @@ RSpec.describe LettingsLog do
let(:startdate) { nil }
before do
allow(log).to receive_message_chain(:form, :end_date).and_return(Time.zone.now + 1.day)
allow(log).to receive_message_chain(:form, :new_logs_end_date).and_return(Time.zone.now + 1.day)
end
it "returns true" do
@ -3052,7 +3052,7 @@ RSpec.describe LettingsLog do
let(:startdate) { Time.zone.local(2020, 4, 1) }
before do
allow(log).to receive_message_chain(:form, :end_date).and_return(Time.zone.now - 1.day)
allow(log).to receive_message_chain(:form, :new_logs_end_date).and_return(Time.zone.now - 1.day)
end
it "returns false" do

4
spec/models/sales_log_spec.rb

@ -635,7 +635,7 @@ RSpec.describe SalesLog, type: :model do
let(:saledate) { nil }
before do
allow(log).to receive_message_chain(:form, :end_date).and_return(Time.zone.now + 1.day)
allow(log).to receive_message_chain(:form, :new_logs_end_date).and_return(Time.zone.now + 1.day)
end
it "returns true" do
@ -647,7 +647,7 @@ RSpec.describe SalesLog, type: :model do
let(:saledate) { Time.zone.local(2020, 4, 1) }
before do
allow(log).to receive_message_chain(:form, :end_date).and_return(Time.zone.now - 1.day)
allow(log).to receive_message_chain(:form, :new_logs_end_date).and_return(Time.zone.now - 1.day)
end
it "returns false" do

46
spec/models/validations/sales/setup_validations_spec.rb

@ -105,6 +105,52 @@ RSpec.describe Validations::Sales::SetupValidations do
expect(record.errors[:saledate]).to include("Enter a date within the 23/24 or 24/25 collection years, which is between 1st April 2023 and 31st March 2025")
end
end
context "when current time is after the new logs end date but before edit end date for the previous period" do
let(:record) { build(:sales_log, saledate: Time.zone.local(2025, 4, 1)) }
before do
allow(Time).to receive(:now).and_return(Time.zone.local(2025, 1, 8))
end
it "cannot create new logs for the previous collection year" do
record.update!(saledate: nil)
record.saledate = Time.zone.local(2024, 1, 1)
setup_validator.validate_saledate_collection_year(record)
expect(record.errors["saledate"]).to include(match "Enter a date within the 24/25 collection year, which is between 1st April 2024 and 31st March 2025")
end
xit "can edit already created logs for the previous collection year" do
record.saledate = Time.zone.local(2024, 1, 2)
record.save!(validate: false)
record.saledate = Time.zone.local(2024, 1, 1)
setup_validator.validate_saledate_collection_year(record)
expect(record.errors["saledate"]).not_to include(match "Enter a date within the 24/25 collection year, which is between 1st April 2024 and 31st March 2025")
end
end
context "when after the new logs end date and after the edit end date for the previous period" do
let(:record) { build(:sales_log, saledate: Time.zone.local(2025, 4, 1)) }
before do
allow(Time).to receive(:now).and_return(Time.zone.local(2025, 1, 8))
end
it "cannot create new logs for the previous collection year" do
record.update!(saledate: nil)
record.saledate = Time.zone.local(2024, 1, 1)
setup_validator.validate_saledate_collection_year(record)
expect(record.errors["saledate"]).to include(match "Enter a date within the 24/25 collection year, which is between 1st April 2024 and 31st March 2025")
end
it "cannot edit already created logs for the previous collection year" do
record.saledate = Time.zone.local(2024, 1, 2)
record.save!(validate: false)
record.saledate = Time.zone.local(2024, 1, 1)
setup_validator.validate_saledate_collection_year(record)
expect(record.errors["saledate"]).to include(match "Enter a date within the 24/25 collection year, which is between 1st April 2024 and 31st March 2025")
end
end
end
end

42
spec/models/validations/setup_validations_spec.rb

@ -85,6 +85,48 @@ RSpec.describe Validations::SetupValidations do
expect(record.errors["startdate"]).to include(match "Enter a date within the 23/24 collection year, which is between 1st April 2023 and 31st March 2024")
end
end
context "when after the new logs end date but before edit end date for the previous period" do
before do
allow(Time).to receive(:now).and_return(Time.zone.local(2024, 1, 8))
end
it "cannot create new logs for the previous collection year" do
record.update!(startdate: nil)
record.startdate = Time.zone.local(2023, 1, 1)
setup_validator.validate_startdate_setup(record)
expect(record.errors["startdate"]).to include(match "Enter a date within the 23/24 collection year, which is between 1st April 2023 and 31st March 2024")
end
xit "can edit already created logs for the previous collection year" do
record.startdate = Time.zone.local(2023, 1, 2)
record.save!(validate: false)
record.startdate = Time.zone.local(2023, 1, 1)
setup_validator.validate_startdate_setup(record)
expect(record.errors["startdate"]).not_to include(match "Enter a date within the 23/24 collection year, which is between 1st April 2023 and 31st March 2024")
end
end
context "when after the new logs end date and after the edit end date for the previous period" do
before do
allow(Time).to receive(:now).and_return(Time.zone.local(2024, 1, 8))
end
it "cannot create new logs for the previous collection year" do
record.update!(startdate: nil)
record.startdate = Time.zone.local(2023, 1, 1)
setup_validator.validate_startdate_setup(record)
expect(record.errors["startdate"]).to include(match "Enter a date within the 23/24 collection year, which is between 1st April 2023 and 31st March 2024")
end
it "cannot edit already created logs for the previous collection year" do
record.startdate = Time.zone.local(2023, 1, 2)
record.save!(validate: false)
record.startdate = Time.zone.local(2023, 1, 1)
setup_validator.validate_startdate_setup(record)
expect(record.errors["startdate"]).to include(match "Enter a date within the 23/24 collection year, which is between 1st April 2023 and 31st March 2024")
end
end
end
end

31
spec/requests/form_controller_spec.rb

@ -31,7 +31,8 @@ RSpec.describe FormController, type: :request do
let(:fake_2021_2022_form) { Form.new("spec/fixtures/forms/2021_2022.json") }
before do
allow(fake_2021_2022_form).to receive(:end_date).and_return(Time.zone.today + 1.day)
allow(fake_2021_2022_form).to receive(:new_logs_end_date).and_return(Time.zone.today + 1.day)
allow(fake_2021_2022_form).to receive(:edit_end_date).and_return(Time.zone.today + 2.months)
allow(FormHandler.instance).to receive(:current_lettings_form).and_return(fake_2021_2022_form)
end
@ -546,7 +547,7 @@ RSpec.describe FormController, type: :request do
end
before do
post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}?referrer=interruption_screen", params:
post "/lettings-logs/#{lettings_log.id}/lead-tenant-age?referrer=interruption_screen", params:
end
it "redirects back to the soft validation page" do
@ -560,6 +561,29 @@ RSpec.describe FormController, type: :request do
end
end
context "when the question was accessed from an interruption screen and it has no check answers" do
let(:params) do
{
id: lettings_log.id,
lettings_log: {
page: "person_1_gender",
sex1: "F",
interruption_page_id: "retirement_value_check",
},
}
end
before do
post "/lettings-logs/#{lettings_log.id}/lead-tenant-gender-identity?referrer=interruption_screen", params:
end
it "displays a success banner without crashing" do
follow_redirect!
follow_redirect!
expect(response.body).to include("You have successfully updated")
end
end
context "when requesting a soft validation page for validation that isn't triggering" do
before do
get "/lettings-logs/#{lettings_log.id}/retirement-value-check", headers: headers.merge({ "HTTP_REFERER" => referrer })
@ -763,7 +787,8 @@ RSpec.describe FormController, type: :request do
before do
completed_lettings_log.update!(ecstat1: 1, earnings: 130, hhmemb: 1) # we're not routing to that page, so it gets cleared?
allow(completed_lettings_log).to receive(:net_income_soft_validation_triggered?).and_return(true)
allow(completed_lettings_log.form).to receive(:end_date).and_return(Time.zone.today + 1.day)
allow(completed_lettings_log.form).to receive(:new_logs_end_date).and_return(Time.zone.today + 1.day)
allow(completed_lettings_log.form).to receive(:edit_end_date).and_return(Time.zone.today + 2.months)
post "/lettings-logs/#{completed_lettings_log.id}/net-income-value-check", params: interrupt_params, headers: headers.merge({ "HTTP_REFERER" => referrer })
end

29
spec/requests/lettings_logs_controller_spec.rb

@ -937,14 +937,14 @@ RSpec.describe LettingsLogsController, type: :request do
completed_lettings_log.reload
get "/lettings-logs/#{completed_lettings_log.id}", headers:, params: {}
expect(completed_lettings_log.form.end_date).to eq(Time.zone.local(2023, 7, 1))
expect(completed_lettings_log.form.new_logs_end_date).to eq(Time.zone.local(2023, 12, 31))
expect(completed_lettings_log.status).to eq("completed")
expect(page).to have_link("review and make changes to this log", href: "/lettings-logs/#{completed_lettings_log.id}/review")
end
xit "displays a closed collection window message for previous collection year logs" do
get "/lettings-logs/#{completed_lettings_log.id}", headers:, params: {}
expect(completed_lettings_log.form.end_date).to eq(Time.zone.local(2022, 7, 1))
expect(completed_lettings_log.form.new_logs_end_date).to eq(Time.zone.local(2022, 7, 1))
expect(completed_lettings_log.status).to eq("completed")
expect(page).to have_content("This log is from the 2021/2022 collection window, which is now closed.")
end
@ -1095,6 +1095,29 @@ RSpec.describe LettingsLogsController, type: :request do
expect(page).not_to have_link("Answer")
end
context "when the edit end date is in the future" do
before do
Timecop.freeze(2022, 7, 5)
end
after do
Timecop.return
end
it "allows you to change the answers for previous collection year logs" do
get "/lettings-logs/#{completed_lettings_log.id}/setup/check-answers", headers: { "Accept" => "text/html" }, params: {}
expect(page).to have_link("Change")
get "/lettings-logs/#{completed_lettings_log.id}/income-and-benefits/check-answers", headers: { "Accept" => "text/html" }, params: {}
expect(page).to have_link("Change")
end
it "lets the user navigate to questions for previous collection year logs" do
get "/lettings-logs/#{completed_lettings_log.id}/needs-type", headers: { "Accept" => "text/html" }, params: {}
expect(response).to have_http_status(:ok)
end
end
it "does not let the user navigate to questions for previous collection year logs" do
get "/lettings-logs/#{completed_lettings_log.id}/needs-type", headers: { "Accept" => "text/html" }, params: {}
expect(response).to redirect_to("/lettings-logs/#{completed_lettings_log.id}")
@ -1198,7 +1221,7 @@ RSpec.describe LettingsLogsController, type: :request do
let(:headers) { { "Accept" => "text/html" } }
before do
allow(affected_lettings_log.form).to receive(:end_date).and_return(Time.zone.today + 1.day)
allow(affected_lettings_log.form).to receive(:edit_end_date).and_return(Time.zone.today + 1.day)
allow(user).to receive(:need_two_factor_authentication?).and_return(false)
sign_in user
end

21
spec/requests/locations_controller_spec.rb

@ -1401,7 +1401,7 @@ RSpec.describe LocationsController, type: :request do
let(:setup_locations) { nil }
before do
Timecop.freeze(Time.utc(2022, 10, 10))
Timecop.freeze(Time.utc(2023, 10, 10))
sign_in user
add_deactivations
setup_locations
@ -1608,7 +1608,7 @@ RSpec.describe LocationsController, type: :request do
end
end
context "when the date is entered is before the beginning of current collection window" do
context "when the date entered is before the beginning of current collection window" do
let(:params) { { location_deactivation_period: { deactivation_date_type: "other", "deactivation_date(3i)": "10", "deactivation_date(2i)": "4", "deactivation_date(1i)": "2020" } } }
it "displays the new page with an error message" do
@ -1656,9 +1656,9 @@ RSpec.describe LocationsController, type: :request do
end
context "when there is an earlier open deactivation" do
let(:deactivation_date) { Time.zone.local(2022, 10, 10) }
let(:params) { { location_deactivation_period: { deactivation_date_type: "other", "deactivation_date(3i)": "8", "deactivation_date(2i)": "9", "deactivation_date(1i)": "2023" } } }
let(:add_deactivations) { create(:location_deactivation_period, deactivation_date: Time.zone.local(2023, 6, 5), reactivation_date: nil, location:) }
let(:deactivation_date) { Time.zone.local(2023, 10, 10) }
let(:params) { { location_deactivation_period: { deactivation_date_type: "other", "deactivation_date(3i)": "8", "deactivation_date(2i)": "9", "deactivation_date(1i)": "2024" } } }
let(:add_deactivations) { create(:location_deactivation_period, deactivation_date: Time.zone.local(2024, 6, 5), reactivation_date: nil, location:) }
it "redirects to the location page and updates the existing deactivation period" do
follow_redirect!
@ -1667,14 +1667,13 @@ RSpec.describe LocationsController, type: :request do
expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success")
location.reload
expect(location.location_deactivation_periods.count).to eq(1)
expect(location.location_deactivation_periods.first.deactivation_date).to eq(Time.zone.local(2023, 9, 8))
expect(location.location_deactivation_periods.first.deactivation_date).to eq(Time.zone.local(2024, 9, 8))
end
end
context "when there is a later open deactivation" do
let(:deactivation_date) { Time.zone.local(2022, 10, 10) }
let(:params) { { location_deactivation_period: { deactivation_date_type: "other", "deactivation_date(3i)": "8", "deactivation_date(2i)": "9", "deactivation_date(1i)": "2022" } } }
let(:add_deactivations) { create(:location_deactivation_period, deactivation_date: Time.zone.local(2023, 6, 5), reactivation_date: nil, location:) }
let(:add_deactivations) { create(:location_deactivation_period, deactivation_date: Time.zone.local(2024, 6, 5), reactivation_date: nil, location:) }
it "redirects to the confirmation page" do
follow_redirect!
@ -1833,7 +1832,7 @@ RSpec.describe LocationsController, type: :request do
let(:startdate) { Time.utc(2022, 9, 11) }
before do
Timecop.freeze(Time.utc(2022, 9, 10))
Timecop.freeze(Time.utc(2023, 1, 10))
sign_in user
create(:location_deactivation_period, deactivation_date:, location:)
location.save!
@ -1882,13 +1881,13 @@ RSpec.describe LocationsController, type: :request do
end
context "with other future date" do
let(:params) { { location_deactivation_period: { reactivation_date_type: "other", "reactivation_date(3i)": "14", "reactivation_date(2i)": "12", "reactivation_date(1i)": "2022" } } }
let(:params) { { location_deactivation_period: { reactivation_date_type: "other", "reactivation_date(3i)": "14", "reactivation_date(2i)": "12", "reactivation_date(1i)": "2023" } } }
it "redirects to the location page and displays a success banner" do
expect(response).to redirect_to("/schemes/#{scheme.id}/locations/#{location.id}")
follow_redirect!
expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success")
expect(page).to have_content("#{location.name} will reactivate on 14 December 2022")
expect(page).to have_content("#{location.name} will reactivate on 14 December 2023")
end
end

2
spec/requests/schemes_controller_spec.rb

@ -1893,7 +1893,7 @@ RSpec.describe SchemesController, type: :request do
let(:setup_schemes) { nil }
before do
Timecop.freeze(Time.utc(2022, 10, 10))
Timecop.freeze(Time.utc(2023, 10, 10))
sign_in user
setup_schemes
patch "/schemes/#{scheme.id}/new-deactivation", params:

2
spec/services/bulk_upload/lettings/year2022/csv_parser_spec.rb

@ -190,7 +190,7 @@ RSpec.describe BulkUpload::Lettings::Year2022::CsvParser do
describe "#wrong_template_for_year?" do
context "when 23/24 file with 23/24 data" do
let(:log) { build(:lettings_log, :completed, startdate: Date.new(2023, 10, 1)) }
let(:log) { build(:lettings_log, :completed, startdate: Date.new(2024, 1, 1)) }
before do
file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2023_csv_row)

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

@ -1098,7 +1098,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do
it "populates with correct error message" do
expect(parser.errors.where(:field_128, category: :soft_validation).count).to be(1)
expect(parser.errors.where(:field_128, category: :soft_validation).first.message).to eql("You told us the rent is £120.00 every week. The maximum rent expected for this type of property in this local authority is ££118.85 every week.")
expect(parser.errors.where(:field_128, category: :soft_validation).first.message).to eql("You told us the rent is £120.00 every week. This is higher than we would expect.")
end
end
end

2
spec/services/bulk_upload/sales/year2022/csv_parser_spec.rb

@ -122,7 +122,7 @@ RSpec.describe BulkUpload::Sales::Year2022::CsvParser do
let(:path) { file.path }
context "when 23/24 file with 23/24 data" do
let(:log) { build(:sales_log, :completed, saledate: Date.new(2023, 10, 1)) }
let(:log) { build(:sales_log, :completed, saledate: Date.new(2024, 1, 1)) }
before do
file.write(BulkUpload::SalesLogToCsv.new(log:, col_offset: 0).to_2023_csv_row)

Loading…
Cancel
Save