Browse Source

fix merge conflicts

pull/1101/head
Arthur Campbell 4 years ago
parent
commit
0705944de0
  1. 3
      .github/workflows/review_pipeline.yml
  2. 2
      Gemfile
  3. 4
      app/controllers/lettings_logs_controller.rb
  4. 4
      app/controllers/organisations_controller.rb
  5. 4
      app/controllers/sales_logs_controller.rb
  6. 20
      app/helpers/collection_time_helper.rb
  7. 8
      app/helpers/filters_helper.rb
  8. 4
      app/helpers/tasklist_helper.rb
  9. 28
      app/mailers/bulk_upload_mailer.rb
  10. 12
      app/models/bulk_upload.rb
  11. 4
      app/models/derived_variables/lettings_log_variables.rb
  12. 6
      app/models/form.rb
  13. 2
      app/models/form/lettings/questions/working_situation1.rb
  14. 9
      app/models/form/sales/pages/about_staircase.rb
  15. 15
      app/models/form/sales/pages/buyer2_ethnic_background_arab.rb
  16. 15
      app/models/form/sales/pages/buyer2_ethnic_background_asian.rb
  17. 15
      app/models/form/sales/pages/buyer2_ethnic_background_black.rb
  18. 15
      app/models/form/sales/pages/buyer2_ethnic_background_mixed.rb
  19. 15
      app/models/form/sales/pages/buyer2_ethnic_background_white.rb
  20. 22
      app/models/form/sales/pages/buyer2_ethnic_group.rb
  21. 15
      app/models/form/sales/pages/previous_shared.rb
  22. 16
      app/models/form/sales/questions/buyer2_ethnic_background_arab.rb
  23. 19
      app/models/form/sales/questions/buyer2_ethnic_background_asian.rb
  24. 17
      app/models/form/sales/questions/buyer2_ethnic_background_black.rb
  25. 18
      app/models/form/sales/questions/buyer2_ethnic_background_mixed.rb
  26. 18
      app/models/form/sales/questions/buyer2_ethnic_background_white.rb
  27. 27
      app/models/form/sales/questions/buyer2_ethnic_group.rb
  28. 17
      app/models/form/sales/questions/prevshared.rb
  29. 16
      app/models/form/sales/questions/staircase_sale.rb
  30. 14
      app/models/form/sales/subsections/household_characteristics.rb
  31. 11
      app/models/form/sales/subsections/income_benefits_and_savings.rb
  32. 12
      app/models/lettings_log.rb
  33. 4
      app/models/log.rb
  34. 27
      app/models/organisation.rb
  35. 12
      app/models/sales_log.rb
  36. 9
      app/models/validations/sales/property_validations.rb
  37. 2
      app/services/bulk_upload/lettings/log_creator.rb
  38. 144
      app/services/bulk_upload/lettings/row_parser.rb
  39. 1
      app/services/bulk_upload/lettings/validator.rb
  40. 5
      app/services/bulk_upload/processor.rb
  41. 9
      app/services/exports/lettings_log_export_constants.rb
  42. 11
      app/services/exports/lettings_log_export_service.rb
  43. 22
      app/views/form/review.html.erb
  44. 3
      app/views/logs/_log_filters.erb
  45. 2
      config/credentials.yml.enc
  46. 2
      config/forms/2022_2023.json
  47. 10
      config/initializers/feature_toggle.rb
  48. 13
      config/locales/en.yml
  49. 4
      config/routes.rb
  50. 1185
      config/sale_range_data/2023.csv
  51. 7
      db/migrate/20230203153051_add_prev_shared_to_sales.rb
  52. 5
      db/migrate/20230210122037_add_staircasesale_to_sales_logs.rb
  53. 8
      db/migrate/20230210143120_add_ethnic_fields_for_buyer2.rb
  54. 8
      db/schema.rb
  55. 63
      docs/Gemfile.lock
  56. 18
      lib/tasks/data_export.rake
  57. 8
      spec/factories/organisation.rb
  58. 1
      spec/factories/sales_log.rb
  59. 2
      spec/fixtures/exports/general_needs_log.csv
  60. 2
      spec/fixtures/exports/general_needs_log.xml
  61. 2
      spec/fixtures/exports/supported_housing_logs.xml
  62. 46
      spec/helpers/collection_time_helper_spec.rb
  63. 26
      spec/helpers/filters_helper_spec.rb
  64. 4
      spec/helpers/tasklist_helper_spec.rb
  65. 54
      spec/mailers/bulk_upload_mailer_spec.rb
  66. 16
      spec/models/form/sales/pages/about_staircase_spec.rb
  67. 33
      spec/models/form/sales/pages/buyer2_ethnic_background_arab_spec.rb
  68. 33
      spec/models/form/sales/pages/buyer2_ethnic_background_asian_spec.rb
  69. 33
      spec/models/form/sales/pages/buyer2_ethnic_background_black_spec.rb
  70. 33
      spec/models/form/sales/pages/buyer2_ethnic_background_mixed_spec.rb
  71. 33
      spec/models/form/sales/pages/buyer2_ethnic_background_white_spec.rb
  72. 42
      spec/models/form/sales/pages/buyer2_ethnic_group_spec.rb
  73. 29
      spec/models/form/sales/pages/previous_shared_spec.rb
  74. 48
      spec/models/form/sales/questions/buyer2_ethnic_background_arab_spec.rb
  75. 51
      spec/models/form/sales/questions/buyer2_ethnic_background_asian_spec.rb
  76. 49
      spec/models/form/sales/questions/buyer2_ethnic_background_black_spec.rb
  77. 50
      spec/models/form/sales/questions/buyer2_ethnic_background_mixed_spec.rb
  78. 50
      spec/models/form/sales/questions/buyer2_ethnic_background_white_spec.rb
  79. 62
      spec/models/form/sales/questions/buyer2_ethnic_group_spec.rb
  80. 49
      spec/models/form/sales/questions/prevshared_spec.rb
  81. 49
      spec/models/form/sales/questions/staircase_sale_spec.rb
  82. 1
      spec/models/form/sales/subsections/discounted_ownership_scheme_spec.rb
  83. 88
      spec/models/form/sales/subsections/household_characteristics_spec.rb
  84. 35
      spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb
  85. 2
      spec/models/form_handler_spec.rb
  86. 2
      spec/models/form_spec.rb
  87. 20
      spec/models/lettings_log_spec.rb
  88. 7
      spec/models/sales_log_spec.rb
  89. 36
      spec/models/validations/sales/property_validations_spec.rb
  90. 6
      spec/requests/form_controller_spec.rb
  91. 25
      spec/requests/sales_logs_controller_spec.rb
  92. 17
      spec/services/bulk_upload/lettings/log_creator_spec.rb
  93. 287
      spec/services/bulk_upload/lettings/row_parser_spec.rb
  94. 20
      spec/services/bulk_upload/lettings/validator_spec.rb
  95. 11
      spec/services/bulk_upload/processor_spec.rb
  96. 12
      spec/services/exports/lettings_log_export_service_spec.rb

3
.github/workflows/review_pipeline.yml

@ -72,6 +72,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: staging environment: staging
needs: [postgres, redis] needs: [postgres, redis]
permissions:
issues: write
pull-requests: write
steps: steps:
- name: Checkout code - name: Checkout code

2
Gemfile

@ -68,6 +68,7 @@ group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console # Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem "byebug", platforms: %i[mri mingw x64_mingw] gem "byebug", platforms: %i[mri mingw x64_mingw]
gem "dotenv-rails" gem "dotenv-rails"
gem "factory_bot_rails"
gem "pry-byebug" gem "pry-byebug"
gem "parallel_tests" gem "parallel_tests"
@ -90,7 +91,6 @@ end
group :test do group :test do
gem "capybara", require: false gem "capybara", require: false
gem "capybara-lockstep" gem "capybara-lockstep"
gem "factory_bot_rails"
gem "faker" gem "faker"
gem "rspec-rails", require: false gem "rspec-rails", require: false
gem "selenium-webdriver", require: false gem "selenium-webdriver", require: false

4
app/controllers/lettings_logs_controller.rb

@ -1,7 +1,7 @@
class LettingsLogsController < LogsController class LettingsLogsController < LogsController
before_action :find_resource, except: %i[create index edit] before_action :find_resource, except: %i[create index edit]
before_action :session_filters, if: :current_user before_action :session_filters, if: :current_user, only: %i[index email_csv download_csv]
before_action :set_session_filters, if: :current_user before_action :set_session_filters, if: :current_user, only: %i[index email_csv download_csv]
before_action :extract_bulk_upload_from_session_filters, only: [:index] before_action :extract_bulk_upload_from_session_filters, only: [:index]
before_action :redirect_if_bulk_upload_resolved, only: [:index] before_action :redirect_if_bulk_upload_resolved, only: [:index]

4
app/controllers/organisations_controller.rb

@ -6,8 +6,8 @@ class OrganisationsController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
before_action :find_resource, except: %i[index new create] before_action :find_resource, except: %i[index new create]
before_action :authenticate_scope!, except: [:index] before_action :authenticate_scope!, except: [:index]
before_action -> { session_filters(specific_org: true) }, if: -> { current_user.support? || current_user.organisation.has_managing_agents? } before_action -> { session_filters(specific_org: true) }, if: -> { current_user.support? || current_user.organisation.has_managing_agents? }, only: %i[lettings_logs sales_logs email_csv download_csv]
before_action :set_session_filters, if: -> { current_user.support? || current_user.organisation.has_managing_agents? } before_action :set_session_filters, if: -> { current_user.support? || current_user.organisation.has_managing_agents? }, only: %i[lettings_logs sales_logs email_csv download_csv]
def index def index
redirect_to organisation_path(current_user.organisation) unless current_user.support? redirect_to organisation_path(current_user.organisation) unless current_user.support?

4
app/controllers/sales_logs_controller.rb

@ -1,6 +1,6 @@
class SalesLogsController < LogsController class SalesLogsController < LogsController
before_action :session_filters, if: :current_user before_action :session_filters, if: :current_user, only: %i[index email_csv download_csv]
before_action :set_session_filters, if: :current_user before_action :set_session_filters, if: :current_user, only: %i[index email_csv download_csv]
def create def create
super { SalesLog.new(log_params) } super { SalesLog.new(log_params) }

20
app/helpers/collection_time_helper.rb

@ -1,16 +1,26 @@
module CollectionTimeHelper module CollectionTimeHelper
def collection_start_year(date)
window_end_date = Time.zone.local(date.year, 4, 1)
date < window_end_date ? date.year - 1 : date.year
end
def current_collection_start_year def current_collection_start_year
today = Time.zone.now collection_start_year(Time.zone.now)
window_end_date = Time.zone.local(today.year, 4, 1)
today < window_end_date ? today.year - 1 : today.year
end end
def collection_start_date(date) def collection_start_date(date)
window_end_date = Time.zone.local(date.year, 4, 1) Time.zone.local(collection_start_year(date), 4, 1)
date < window_end_date ? Time.zone.local(date.year - 1, 4, 1) : Time.zone.local(date.year, 4, 1)
end end
def current_collection_start_date def current_collection_start_date
Time.zone.local(current_collection_start_year, 4, 1) Time.zone.local(current_collection_start_year, 4, 1)
end end
def collection_end_date(date)
Time.zone.local(collection_start_year(date) + 1, 3, 31)
end
def current_collection_end_date
Time.zone.local(current_collection_start_year + 1, 3, 31)
end
end end

8
app/helpers/filters_helper.rb

@ -27,4 +27,12 @@ module FiltersHelper
organisation_options = user.support? ? Organisation.all : [user.organisation] + user.organisation.managing_agents organisation_options = user.support? ? Organisation.all : [user.organisation] + user.organisation.managing_agents
[OpenStruct.new(id: "", name: "Select an option")] + organisation_options.map { |org| OpenStruct.new(id: org.id, name: org.name) } [OpenStruct.new(id: "", name: "Select an option")] + organisation_options.map { |org| OpenStruct.new(id: org.id, name: org.name) }
end end
def collection_year_options
if FeatureToggle.collection_2023_2024_year_enabled?
{ "2023": "2023/24", "2022": "2022/23", "2021": "2021/22" }
else
{ "2022": "2022/23", "2021": "2021/22" }
end
end
end end

4
app/helpers/tasklist_helper.rb

@ -39,7 +39,9 @@ module TasklistHelper
def review_log_text(log) def review_log_text(log)
if log.collection_period_open? if log.collection_period_open?
"You can #{govuk_link_to 'review and make changes to this log', review_lettings_log_path(log)} until #{log.form.end_date.to_formatted_s(:govuk_date)}.".html_safe link = log.sales? ? review_sales_log_path(id: log, sales_log: true) : review_lettings_log_path(log)
"You can #{govuk_link_to 'review and make changes to this log', link} until #{log.form.end_date.to_formatted_s(:govuk_date)}.".html_safe
else else
"This log is from the #{log.form.start_date.year}/#{log.form.start_date.year + 1} collection window, which is now closed." "This log is from the #{log.form.start_date.year}/#{log.form.start_date.year + 1} collection window, which is now closed."
end end

28
app/mailers/bulk_upload_mailer.rb

@ -33,17 +33,29 @@ class BulkUploadMailer < NotifyMailer
) )
end end
def send_bulk_upload_failed_csv_errors_mail(user, bulk_upload) def columns_with_errors(bulk_upload:)
array = bulk_upload.columns_with_errors
if array.size > 3
"#{array.take(3).join(', ')} and more"
else
array.join(", ")
end
end
def send_correct_and_upload_again_mail(bulk_upload:)
error_description = "We noticed that you have a lot of similar errors in column #{columns_with_errors(bulk_upload:)}. Please correct your data export and upload again."
send_email( send_email(
user.email, bulk_upload.user.email,
BULK_UPLOAD_FAILED_CSV_ERRORS_TEMPLATE_ID, BULK_UPLOAD_FAILED_CSV_ERRORS_TEMPLATE_ID,
{ {
filename: "[#{bulk_upload} filename]", filename: bulk_upload.filename,
upload_timestamp: "[#{bulk_upload} upload_timestamp]", upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
year_combo: "[#{bulk_upload} year_combo]", year_combo: bulk_upload.year_combo,
lettings_or_sales: "[#{bulk_upload} lettings_or_sales]", lettings_or_sales: bulk_upload.log_type,
error_description: "[#{bulk_upload} error_description]", error_description:,
summary_report_link: "[#{bulk_upload} summary_report_link]", summary_report_link: summary_bulk_upload_lettings_result_url(bulk_upload),
}, },
) )
end end

12
app/models/bulk_upload.rb

@ -30,10 +30,22 @@ class BulkUpload < ApplicationRecord
end end
end end
def columns_with_errors
bulk_upload_errors
.select(:col)
.distinct(:col)
.pluck(:col)
.sort_by { |col| col.rjust(2, "0") }
end
def general_needs? def general_needs?
needstype == 1 needstype == 1
end end
def supported_housing?
needstype == 2
end
private private
def generate_identifier def generate_identifier

4
app/models/derived_variables/lettings_log_variables.rb

@ -45,6 +45,9 @@ module DerivedVariables::LettingsLogVariables
self.underoccupation_benefitcap = 2 if collection_start_year == 2021 self.underoccupation_benefitcap = 2 if collection_start_year == 2021
self.referral = 1 self.referral = 1
self.waityear = 2 self.waityear = 2
self.offered = 0
self.voiddate = startdate
self.first_time_property_let_as_social_housing = 0
if is_general_needs? if is_general_needs?
# fixed term # fixed term
self.prevten = 32 if managing_organisation&.provider_type == "PRP" self.prevten = 32 if managing_organisation&.provider_type == "PRP"
@ -60,7 +63,6 @@ module DerivedVariables::LettingsLogVariables
if is_supported_housing? && location if is_supported_housing? && location
self.wchair = location.mobility_type_before_type_cast == "W" ? 1 : 2 self.wchair = location.mobility_type_before_type_cast == "W" ? 1 : 2
end end
self.voiddate = startdate if is_renewal?
self.vacdays = property_vacant_days self.vacdays = property_vacant_days
set_housingneeds_fields if housingneeds? set_housingneeds_fields if housingneeds?

6
app/models/form.rb

@ -6,7 +6,11 @@ class Form
def initialize(form_path, start_year = "", sections_in_form = [], type = "lettings") def initialize(form_path, start_year = "", sections_in_form = [], type = "lettings")
if sales_or_start_year_after_2022?(type, start_year) if sales_or_start_year_after_2022?(type, start_year)
@start_date = Time.zone.local(start_year, 4, 1) @start_date = Time.zone.local(start_year, 4, 1)
@end_date = Time.zone.local(start_year + 1, 7, 1) @end_date = if start_year && start_year.to_i > 2022
Time.zone.local(start_year + 1, 7, 9)
else
Time.zone.local(start_year + 1, 7, 7)
end
@setup_sections = type == "sales" ? [Form::Sales::Sections::Setup.new(nil, nil, self)] : [Form::Lettings::Sections::Setup.new(nil, nil, self)] @setup_sections = type == "sales" ? [Form::Sales::Sections::Setup.new(nil, nil, self)] : [Form::Lettings::Sections::Setup.new(nil, nil, self)]
@form_sections = sections_in_form.map { |sec| sec.new(nil, nil, self) } @form_sections = sections_in_form.map { |sec| sec.new(nil, nil, self) }
@type = type @type = type

2
app/models/form/lettings/questions/working_situation1.rb

@ -11,8 +11,8 @@ class Form::Lettings::Questions::WorkingSituation1 < ::Form::Question
end end
ANSWER_OPTIONS = { ANSWER_OPTIONS = {
"2" => { "value" => "Part-time – Less than 30 hours" },
"1" => { "value" => "Full-time – 30 hours or more" }, "1" => { "value" => "Full-time – 30 hours or more" },
"2" => { "value" => "Part-time – Less than 30 hours" },
"7" => { "value" => "Full-time student" }, "7" => { "value" => "Full-time student" },
"3" => { "value" => "In government training into work, such as New Deal" }, "3" => { "value" => "In government training into work, such as New Deal" },
"4" => { "value" => "Jobseeker" }, "4" => { "value" => "Jobseeker" },

9
app/models/form/sales/pages/about_staircase.rb

@ -12,6 +12,13 @@ class Form::Sales::Pages::AboutStaircase < ::Form::Page
@questions ||= [ @questions ||= [
Form::Sales::Questions::StaircaseBought.new(nil, nil, self), Form::Sales::Questions::StaircaseBought.new(nil, nil, self),
Form::Sales::Questions::StaircaseOwned.new(nil, nil, self), Form::Sales::Questions::StaircaseOwned.new(nil, nil, self),
] staircase_sale_question,
].compact
end
def staircase_sale_question
if form.start_date.year >= 2023
Form::Sales::Questions::StaircaseSale.new(nil, nil, self)
end
end end
end end

15
app/models/form/sales/pages/buyer2_ethnic_background_arab.rb

@ -0,0 +1,15 @@
class Form::Sales::Pages::Buyer2EthnicBackgroundArab < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_2_ethnic_background_arab"
@depends_on = [{
"ethnic_group2" => 4,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::Buyer2EthnicBackgroundArab.new(nil, nil, self),
]
end
end

15
app/models/form/sales/pages/buyer2_ethnic_background_asian.rb

@ -0,0 +1,15 @@
class Form::Sales::Pages::Buyer2EthnicBackgroundAsian < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_2_ethnic_background_asian"
@depends_on = [{
"ethnic_group2" => 2,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::Buyer2EthnicBackgroundAsian.new(nil, nil, self),
]
end
end

15
app/models/form/sales/pages/buyer2_ethnic_background_black.rb

@ -0,0 +1,15 @@
class Form::Sales::Pages::Buyer2EthnicBackgroundBlack < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_2_ethnic_background_black"
@depends_on = [{
"ethnic_group2" => 3,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::Buyer2EthnicBackgroundBlack.new(nil, nil, self),
]
end
end

15
app/models/form/sales/pages/buyer2_ethnic_background_mixed.rb

@ -0,0 +1,15 @@
class Form::Sales::Pages::Buyer2EthnicBackgroundMixed < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_2_ethnic_background_mixed"
@depends_on = [{
"ethnic_group2" => 1,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::Buyer2EthnicBackgroundMixed.new(nil, nil, self),
]
end
end

15
app/models/form/sales/pages/buyer2_ethnic_background_white.rb

@ -0,0 +1,15 @@
class Form::Sales::Pages::Buyer2EthnicBackgroundWhite < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_2_ethnic_background_white"
@depends_on = [{
"ethnic_group2" => 0,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::Buyer2EthnicBackgroundWhite.new(nil, nil, self),
]
end
end

22
app/models/form/sales/pages/buyer2_ethnic_group.rb

@ -0,0 +1,22 @@
class Form::Sales::Pages::Buyer2EthnicGroup < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_2_ethnic_group"
@depends_on = [
{
"jointpur" => 1,
"privacynotice" => 1,
},
{
"jointpur" => 1,
"noint" => 1,
},
]
end
def questions
@questions ||= [
Form::Sales::Questions::Buyer2EthnicGroup.new(nil, nil, self),
]
end
end

15
app/models/form/sales/pages/previous_shared.rb

@ -0,0 +1,15 @@
class Form::Sales::Pages::PreviousShared < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "previous_shared"
@depends_on = [
{ "prevown" => 1 },
]
end
def questions
@questions ||= [
Form::Sales::Questions::Prevshared.new(nil, nil, self),
]
end
end

16
app/models/form/sales/questions/buyer2_ethnic_background_arab.rb

@ -0,0 +1,16 @@
class Form::Sales::Questions::Buyer2EthnicBackgroundArab < ::Form::Question
def initialize(id, hsh, page)
super
@id = "ethnicbuy2"
@check_answer_label = "Buyer 2’s ethnic background"
@header = "Which of the following best describes the buyer 2’s Arab background?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@check_answers_card_number = 2
end
ANSWER_OPTIONS = {
"19" => { "value" => "Arab" },
"16" => { "value" => "Other ethnic group" },
}.freeze
end

19
app/models/form/sales/questions/buyer2_ethnic_background_asian.rb

@ -0,0 +1,19 @@
class Form::Sales::Questions::Buyer2EthnicBackgroundAsian < ::Form::Question
def initialize(id, hsh, page)
super
@id = "ethnicbuy2"
@check_answer_label = "Buyer 2’s ethnic background"
@header = "Which of the following best describes the buyer 2’s Asian or Asian British background?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@check_answers_card_number = 2
end
ANSWER_OPTIONS = {
"10" => { "value" => "Bangladeshi" },
"15" => { "value" => "Chinese" },
"8" => { "value" => "Indian" },
"9" => { "value" => "Pakistani" },
"11" => { "value" => "Any other Asian or Asian British background" },
}.freeze
end

17
app/models/form/sales/questions/buyer2_ethnic_background_black.rb

@ -0,0 +1,17 @@
class Form::Sales::Questions::Buyer2EthnicBackgroundBlack < ::Form::Question
def initialize(id, hsh, page)
super
@id = "ethnicbuy2"
@check_answer_label = "Buyer 2’s ethnic background"
@header = "Which of the following best describes the buyer 2’s Black, African, Caribbean or Black British background?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@check_answers_card_number = 2
end
ANSWER_OPTIONS = {
"13" => { "value" => "African" },
"12" => { "value" => "Caribbean" },
"14" => { "value" => "Any other Black, African or Caribbean background" },
}.freeze
end

18
app/models/form/sales/questions/buyer2_ethnic_background_mixed.rb

@ -0,0 +1,18 @@
class Form::Sales::Questions::Buyer2EthnicBackgroundMixed < ::Form::Question
def initialize(id, hsh, page)
super
@id = "ethnicbuy2"
@check_answer_label = "Buyer 2’s ethnic background"
@header = "Which of the following best describes the buyer 2’s Mixed or Multiple ethnic groups background?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@check_answers_card_number = 2
end
ANSWER_OPTIONS = {
"4" => { "value" => "White and Black Caribbean" },
"5" => { "value" => "White and Black African" },
"6" => { "value" => "White and Asian" },
"7" => { "value" => "Any other Mixed or Multiple ethnic background" },
}.freeze
end

18
app/models/form/sales/questions/buyer2_ethnic_background_white.rb

@ -0,0 +1,18 @@
class Form::Sales::Questions::Buyer2EthnicBackgroundWhite < ::Form::Question
def initialize(id, hsh, page)
super
@id = "ethnicbuy2"
@check_answer_label = "Buyer 2’s ethnic background"
@header = "Which of the following best describes the buyer 2’s White background?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@check_answers_card_number = 2
end
ANSWER_OPTIONS = {
"1" => { "value" => "English, Welsh, Northern Irish, Scottish or British" },
"2" => { "value" => "Irish" },
"18" => { "value" => "Gypsy or Irish Traveller" },
"3" => { "value" => "Any other White background" },
}.freeze
end

27
app/models/form/sales/questions/buyer2_ethnic_group.rb

@ -0,0 +1,27 @@
class Form::Sales::Questions::Buyer2EthnicGroup < ::Form::Question
def initialize(id, hsh, page)
super
@id = "ethnic_group2"
@check_answer_label = "Buyer 2’s ethnic group"
@header = "What is buyer 2’s ethnic group?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@inferred_check_answers_value = [{
"condition" => {
"ethnic_group2" => 17,
},
"value" => "Prefers not to say",
}]
@check_answers_card_number = 2
end
ANSWER_OPTIONS = {
"0" => { "value" => "White" },
"1" => { "value" => "Mixed or Multiple ethnic groups" },
"2" => { "value" => "Asian or Asian British" },
"3" => { "value" => "Black, African, Caribbean or Black British" },
"4" => { "value" => "Arab or other ethnic group" },
"divider" => { "value" => true },
"17" => { "value" => "Buyer 1 prefers not to say" },
}.freeze
end

17
app/models/form/sales/questions/prevshared.rb

@ -0,0 +1,17 @@
class Form::Sales::Questions::Prevshared < ::Form::Question
def initialize(id, hsh, page)
super
@id = "prevshared"
@check_answer_label = "Previous property shared ownership?"
@header = "Was the previous property a shared ownership property?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@hint = "For any buyer"
end
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don’t know" },
}.freeze
end

16
app/models/form/sales/questions/staircase_sale.rb

@ -0,0 +1,16 @@
class Form::Sales::Questions::StaircaseSale < ::Form::Question
def initialize(id, hsh, page)
super
@id = "staircasesale"
@check_answer_label = "Part of a back-to-back staircasing transaction"
@header = "Is this transaction part of a back-to-back staircasing transaction to facilitate sale of the home on the open market?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
end
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don't know" },
}.freeze
end

14
app/models/form/sales/subsections/household_characteristics.rb

@ -32,6 +32,7 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Sales::Pages::RetirementValueCheck.new("age_2_buyer_retirement_value_check", nil, self, person_index: 2), Form::Sales::Pages::RetirementValueCheck.new("age_2_buyer_retirement_value_check", nil, self, person_index: 2),
Form::Sales::Pages::GenderIdentity2.new(nil, nil, self), Form::Sales::Pages::GenderIdentity2.new(nil, nil, self),
Form::Sales::Pages::RetirementValueCheck.new("gender_2_buyer_retirement_value_check", nil, self, person_index: 2), Form::Sales::Pages::RetirementValueCheck.new("gender_2_buyer_retirement_value_check", nil, self, person_index: 2),
ethnic_pages_for_buyer_2,
Form::Sales::Pages::Buyer2WorkingSituation.new(nil, nil, self), Form::Sales::Pages::Buyer2WorkingSituation.new(nil, nil, self),
Form::Sales::Pages::RetirementValueCheck.new("working_situation_2_retirement_value_check_joint_purchase", nil, self, person_index: 2), Form::Sales::Pages::RetirementValueCheck.new("working_situation_2_retirement_value_check_joint_purchase", nil, self, person_index: 2),
Form::Sales::Pages::Buyer2IncomeValueCheck.new("working_situation_buyer_2_income_value_check", nil, self), Form::Sales::Pages::Buyer2IncomeValueCheck.new("working_situation_buyer_2_income_value_check", nil, self),
@ -69,6 +70,17 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection
Form::Sales::Pages::RetirementValueCheck.new("gender_5_retirement_value_check", nil, self, person_index: 5), Form::Sales::Pages::RetirementValueCheck.new("gender_5_retirement_value_check", nil, self, person_index: 5),
Form::Sales::Pages::PersonWorkingSituation.new("person_5_working_situation", nil, self, person_index: 5), Form::Sales::Pages::PersonWorkingSituation.new("person_5_working_situation", nil, self, person_index: 5),
Form::Sales::Pages::RetirementValueCheck.new("working_situation_5_retirement_value_check", nil, self, person_index: 5), Form::Sales::Pages::RetirementValueCheck.new("working_situation_5_retirement_value_check", nil, self, person_index: 5),
] ].flatten.compact
end
def ethnic_pages_for_buyer_2
if form.start_date.year >= 2023
[Form::Sales::Pages::Buyer2EthnicGroup.new(nil, nil, self),
Form::Sales::Pages::Buyer2EthnicBackgroundBlack.new(nil, nil, self),
Form::Sales::Pages::Buyer2EthnicBackgroundAsian.new(nil, nil, self),
Form::Sales::Pages::Buyer2EthnicBackgroundArab.new(nil, nil, self),
Form::Sales::Pages::Buyer2EthnicBackgroundMixed.new(nil, nil, self),
Form::Sales::Pages::Buyer2EthnicBackgroundWhite.new(nil, nil, self)]
end
end end
end end

11
app/models/form/sales/subsections/income_benefits_and_savings.rb

@ -23,6 +23,15 @@ class Form::Sales::Subsections::IncomeBenefitsAndSavings < ::Form::Subsection
Form::Sales::Pages::SavingsValueCheck.new("savings_value_check", nil, self), Form::Sales::Pages::SavingsValueCheck.new("savings_value_check", nil, self),
Form::Sales::Pages::DepositValueCheck.new("savings_deposit_value_check", nil, self), Form::Sales::Pages::DepositValueCheck.new("savings_deposit_value_check", nil, self),
Form::Sales::Pages::PreviousOwnership.new(nil, nil, self), Form::Sales::Pages::PreviousOwnership.new(nil, nil, self),
] previous_shared_page,
].compact
end
private
def previous_shared_page
if form.start_date.year >= 2023
Form::Sales::Pages::PreviousShared.new(nil, nil, self)
end
end end
end end

12
app/models/lettings_log.rb

@ -70,6 +70,14 @@ class LettingsLog < Log
collection_start_year collection_start_year
end end
def lettings?
true
end
def sales?
false
end
def form_name def form_name
return unless startdate return unless startdate
@ -481,10 +489,6 @@ class LettingsLog < Log
location.type_of_unit_before_type_cast if location location.type_of_unit_before_type_cast if location
end end
def lettings?
true
end
def rent_type_detail def rent_type_detail
form.get_question("rent_type", self)&.label_from_value(rent_type) form.get_question("rent_type", self)&.label_from_value(rent_type)
end end

4
app/models/log.rb

@ -43,6 +43,10 @@ class Log < ApplicationRecord
false false
end end
def sales?
false
end
def ethnic_refused? def ethnic_refused?
ethnic_group == 17 ethnic_group == 17
end end

27
app/models/organisation.rb

@ -17,8 +17,21 @@ class Organisation < ApplicationRecord
has_many :managing_agent_relationships, foreign_key: :parent_organisation_id, class_name: "OrganisationRelationship" has_many :managing_agent_relationships, foreign_key: :parent_organisation_id, class_name: "OrganisationRelationship"
has_many :managing_agents, through: :managing_agent_relationships, source: :child_organisation has_many :managing_agents, through: :managing_agent_relationships, source: :child_organisation
def affiliated_stock_owners
ids = []
if holds_own_stock? && persisted?
ids << id
end
ids.concat(stock_owners.pluck(:id))
Organisation.where(id: ids)
end
scope :search_by_name, ->(name) { where("name ILIKE ?", "%#{name}%") } scope :search_by_name, ->(name) { where("name ILIKE ?", "%#{name}%") }
scope :search_by, ->(param) { search_by_name(param) } scope :search_by, ->(param) { search_by_name(param) }
has_paper_trail has_paper_trail
auto_strip_attributes :name auto_strip_attributes :name
@ -35,6 +48,20 @@ class Organisation < ApplicationRecord
validates :name, presence: { message: I18n.t("validations.organisation.name_missing") } validates :name, presence: { message: I18n.t("validations.organisation.name_missing") }
validates :provider_type, presence: { message: I18n.t("validations.organisation.provider_type_missing") } validates :provider_type, presence: { message: I18n.t("validations.organisation.provider_type_missing") }
def self.find_by_id_on_mulitple_fields(id)
return if id.nil?
if id.start_with?("ORG")
where(id: id[3..]).first
else
where(old_visible_id: id).first
end
end
def can_be_managed_by?(organisation:)
organisation == self || managing_agents.include?(organisation)
end
def lettings_logs def lettings_logs
LettingsLog.filter_by_organisation(self) LettingsLog.filter_by_organisation(self)
end end

12
app/models/sales_log.rb

@ -38,6 +38,14 @@ class SalesLog < Log
OPTIONAL_FIELDS = %w[saledate_check purchid monthly_charges_value_check old_persons_shared_ownership_value_check].freeze OPTIONAL_FIELDS = %w[saledate_check purchid monthly_charges_value_check old_persons_shared_ownership_value_check].freeze
RETIREMENT_AGES = { "M" => 65, "F" => 60, "X" => 65 }.freeze RETIREMENT_AGES = { "M" => 65, "F" => 60, "X" => 65 }.freeze
def lettings?
false
end
def sales?
true
end
def startdate def startdate
saledate saledate
end end
@ -227,6 +235,10 @@ class SalesLog < Log
type == 24 type == 24
end end
def is_bedsit?
proptype == 2
end
def shared_ownership_scheme? def shared_ownership_scheme?
ownershipsch == 1 ownershipsch == 1
end end

9
app/models/validations/sales/property_validations.rb

@ -7,4 +7,13 @@ module Validations::Sales::PropertyValidations
record.errors.add :ppostcode_full, I18n.t("validations.property.postcode.must_match_previous") record.errors.add :ppostcode_full, I18n.t("validations.property.postcode.must_match_previous")
end end
end end
def validate_bedsit_number_of_beds(record)
return unless record.proptype.present? && record.beds.present?
if record.is_bedsit? && record.beds > 1
record.errors.add :proptype, I18n.t("validations.property.proptype.bedsits_have_max_one_bedroom")
record.errors.add :beds, I18n.t("validations.property.beds.bedsits_have_max_one_bedroom")
end
end
end end

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

@ -10,6 +10,8 @@ class BulkUpload::Lettings::LogCreator
row_parsers.each do |row_parser| row_parsers.each do |row_parser|
row_parser.valid? row_parser.valid?
next if row_parser.blank_row?
row_parser.log.blank_invalid_non_setup_fields! row_parser.log.blank_invalid_non_setup_fields!
row_parser.log.bulk_upload = bulk_upload row_parser.log.bulk_upload = bulk_upload

144
app/services/bulk_upload/lettings/row_parser.rb

@ -3,6 +3,7 @@ class BulkUpload::Lettings::RowParser
include ActiveModel::Attributes include ActiveModel::Attributes
attribute :bulk_upload attribute :bulk_upload
attribute :block_log_creation, :boolean, default: -> { false }
attribute :field_1, :integer attribute :field_1, :integer
attribute :field_2 attribute :field_2
@ -114,9 +115,9 @@ class BulkUpload::Lettings::RowParser
attribute :field_108, :string attribute :field_108, :string
attribute :field_109, :string attribute :field_109, :string
attribute :field_110 attribute :field_110
attribute :field_111, :integer attribute :field_111, :string
attribute :field_112, :string attribute :field_112, :string
attribute :field_113, :integer attribute :field_113, :string
attribute :field_114, :integer attribute :field_114, :integer
attribute :field_115 attribute :field_115
attribute :field_116, :integer attribute :field_116, :integer
@ -142,17 +143,32 @@ class BulkUpload::Lettings::RowParser
validates :field_1, presence: { message: I18n.t("validations.not_answered", question: "letting type") }, validates :field_1, presence: { message: I18n.t("validations.not_answered", question: "letting type") },
inclusion: { in: (1..12).to_a, message: I18n.t("validations.invalid_option", question: "letting type") } inclusion: { in: (1..12).to_a, message: I18n.t("validations.invalid_option", question: "letting type") }
validates :field_4, presence: { if: proc { [2, 4, 6, 8, 10, 12].include?(field_1) } } validates :field_4, presence: { if: proc { [2, 4, 6, 8, 10, 12].include?(field_1) } }
validates :field_98, format: { with: /\A\d{2}\z/, message: I18n.t("validations.setup.startdate.year_not_two_digits") }
validate :validate_data_types validate :validate_data_types
validate :validate_nulls validate :validate_nulls
validate :validate_relevant_collection_window validate :validate_relevant_collection_window
validate :validate_la_with_local_housing_referral validate :validate_la_with_local_housing_referral
validate :validate_cannot_be_la_referral_if_general_needs validate :validate_cannot_be_la_referral_if_general_needs_and_la
validate :leaving_reason_for_renewal validate :validate_leaving_reason_for_renewal
validate :validate_lettings_type_matches_bulk_upload
validate :validate_only_one_housing_needs_type
validate :validate_no_disabled_needs_conjunction
validate :validate_dont_know_disabled_needs_conjunction
validate :validate_no_and_dont_know_disabled_needs_conjunction
validate :validate_owning_org_permitted
validate :validate_owning_org_owns_stock
validate :validate_owning_org_exists
validate :validate_managing_org_related
validate :validate_managing_org_exists
def valid? def valid?
errors.clear errors.clear
return true if blank_row?
super super
log.valid? log.valid?
@ -165,14 +181,100 @@ class BulkUpload::Lettings::RowParser
errors.blank? errors.blank?
end end
def blank_row?
attribute_set.to_hash.reject { |k, _| %w[bulk_upload block_log_creation].include?(k) }.values.compact.empty?
end
def log def log
@log ||= LettingsLog.new(attributes_for_log) @log ||= LettingsLog.new(attributes_for_log)
end end
def block_log_creation!
self.block_log_creation = true
end
def block_log_creation?
block_log_creation
end
private private
def validate_cannot_be_la_referral_if_general_needs def validate_managing_org_related
if field_78 == 4 && bulk_upload.general_needs? if owning_organisation && managing_organisation && !owning_organisation.can_be_managed_by?(organisation: managing_organisation)
block_log_creation!
errors.add(:field_113, "This managing organisation does not have a relationship with the owning organisation")
end
end
def validate_managing_org_exists
if managing_organisation.nil?
errors.delete(:field_113)
errors.add(:field_113, "The managing organisation code is incorrect")
end
end
def validate_owning_org_owns_stock
if owning_organisation && !owning_organisation.holds_own_stock?
block_log_creation!
errors.delete(:field_111)
errors.add(:field_111, "The owning organisation code provided is for an organisation that does not own stock")
end
end
def validate_owning_org_exists
if owning_organisation.nil?
errors.delete(:field_111)
errors.add(:field_111, "The owning organisation code is incorrect")
end
end
def validate_owning_org_permitted
if owning_organisation && !bulk_upload.user.organisation.affiliated_stock_owners.include?(owning_organisation)
block_log_creation!
errors.delete(:field_111)
errors.add(:field_111, "You do not have permission to add logs for this owning organisation")
end
end
def validate_no_and_dont_know_disabled_needs_conjunction
if field_59 == 1 && field_60 == 1
errors.add(:field_59, I18n.t("validations.household.housingneeds.no_and_dont_know_disabled_needs_conjunction"))
errors.add(:field_60, I18n.t("validations.household.housingneeds.no_and_dont_know_disabled_needs_conjunction"))
end
end
def validate_dont_know_disabled_needs_conjunction
if field_60 == 1 && [field_55, field_56, field_57, field_58].compact.count.positive?
errors.add(:field_60, I18n.t("validations.household.housingneeds.dont_know_disabled_needs_conjunction"))
end
end
def validate_no_disabled_needs_conjunction
if field_59 == 1 && [field_55, field_56, field_57, field_58].compact.count.positive?
errors.add(:field_59, I18n.t("validations.household.housingneeds.no_disabled_needs_conjunction"))
end
end
def validate_only_one_housing_needs_type
if [field_55, field_56, field_57].compact.count.positive?
errors.add(:field_55, I18n.t("validations.household.housingneeds_type.only_one_option_permitted"))
errors.add(:field_56, I18n.t("validations.household.housingneeds_type.only_one_option_permitted"))
errors.add(:field_57, I18n.t("validations.household.housingneeds_type.only_one_option_permitted"))
end
end
def validate_lettings_type_matches_bulk_upload
if [1, 3, 5, 7, 9, 11].include?(field_1) && !bulk_upload.general_needs?
errors.add(:field_1, I18n.t("validations.setup.lettype.supported_housing_mismatch"))
end
if [2, 4, 6, 8, 10, 12].include?(field_1) && !bulk_upload.supported_housing?
errors.add(:field_1, I18n.t("validations.setup.lettype.general_needs_mismatch"))
end
end
def validate_cannot_be_la_referral_if_general_needs_and_la
if field_78 == 4 && bulk_upload.general_needs? && owning_organisation && owning_organisation.la?
errors.add :field_78, I18n.t("validations.household.referral.la_general_needs.prp_referred_by_la") errors.add :field_78, I18n.t("validations.household.referral.la_general_needs.prp_referred_by_la")
end end
end end
@ -183,7 +285,7 @@ private
end end
end end
def leaving_reason_for_renewal def validate_leaving_reason_for_renewal
if field_134 == 1 && ![40, 42].include?(field_52) if field_134 == 1 && ![40, 42].include?(field_52)
errors.add(:field_52, I18n.t("validations.household.reason.renewal_reason_needed")) errors.add(:field_52, I18n.t("validations.household.reason.renewal_reason_needed"))
end end
@ -399,6 +501,8 @@ private
def startdate def startdate
Date.new(field_98 + 2000, field_97, field_96) if field_98.present? && field_97.present? && field_96.present? Date.new(field_98 + 2000, field_97, field_96) if field_98.present? && field_97.present? && field_96.present?
rescue Date::Error
Date.new
end end
def renttype def renttype
@ -435,15 +539,19 @@ private
end end
def owning_organisation def owning_organisation
Organisation.find_by(old_visible_id: field_111) Organisation.find_by_id_on_mulitple_fields(field_111)
end end
def owning_organisation_id def owning_organisation_id
owning_organisation&.id owning_organisation&.id
end end
def managing_organisation
Organisation.find_by_id_on_mulitple_fields(field_113)
end
def managing_organisation_id def managing_organisation_id
Organisation.find_by(old_visible_id: field_113)&.id managing_organisation&.id
end end
def attributes_for_log def attributes_for_log
@ -535,6 +643,8 @@ private
attributes["preg_occ"] = field_47 attributes["preg_occ"] = field_47
attributes["housingneeds"] = housingneeds attributes["housingneeds"] = housingneeds
attributes["housingneeds_type"] = housingneeds_type
attributes["housingneeds_other"] = housingneeds_other
attributes["illness"] = field_118 attributes["illness"] = field_118
@ -791,7 +901,7 @@ private
def housingneeds def housingneeds
if field_59 == 1 if field_59 == 1
1 2
elsif field_60 == 1 elsif field_60 == 1
3 3
else else
@ -799,6 +909,20 @@ private
end end
end end
def housingneeds_type
if field_55 == 1
0
elsif field_56 == 1
1
elsif field_57 == 1
2
end
end
def housingneeds_other
return 1 if field_58 == 1
end
def ethnic_group_from_ethnic def ethnic_group_from_ethnic
return nil if field_43.blank? return nil if field_43.blank?

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

@ -176,6 +176,7 @@ class BulkUpload::Lettings::Validator
def create_logs? def create_logs?
return false if any_setup_sections_incomplete? return false if any_setup_sections_incomplete?
return false if over_column_error_threshold? return false if over_column_error_threshold?
return false if row_parsers.any?(&:block_log_creation?)
row_parsers.all? { |row_parser| row_parser.log.valid? } row_parsers.all? { |row_parser| row_parser.log.valid? }
end end

5
app/services/bulk_upload/processor.rb

@ -13,6 +13,7 @@ class BulkUpload::Processor
validator.call validator.call
create_logs if validator.create_logs? create_logs if validator.create_logs?
send_correct_and_upload_again_mail unless validator.create_logs?
send_fix_errors_mail if created_logs_but_incompleted? send_fix_errors_mail if created_logs_but_incompleted?
send_success_mail if created_logs_and_all_completed? send_success_mail if created_logs_and_all_completed?
@ -25,6 +26,10 @@ class BulkUpload::Processor
private private
def send_correct_and_upload_again_mail
BulkUploadMailer.send_correct_and_upload_again_mail(bulk_upload:).deliver_later
end
def send_fix_errors_mail def send_fix_errors_mail
BulkUploadMailer.send_bulk_upload_with_errors_mail(bulk_upload:).deliver_later BulkUploadMailer.send_bulk_upload_with_errors_mail(bulk_upload:).deliver_later
end end

9
app/services/exports/lettings_log_export_constants.rb

@ -7,13 +7,6 @@ module Exports::LettingsLogExportConstants
csv: 2, csv: 2,
}.freeze }.freeze
QUARTERS = {
0 => "jan_mar",
1 => "apr_jun",
2 => "jul_sep",
3 => "oct_dec",
}.freeze
EXPORT_FIELDS = Set[ EXPORT_FIELDS = Set[
"armedforces", "armedforces",
"beds", "beds",
@ -30,7 +23,7 @@ module Exports::LettingsLogExportConstants
"confidential", "confidential",
"earnings", "earnings",
"ethnic", "ethnic",
"form", "formid",
"has_benefits", "has_benefits",
"hb", "hb",
"hbrentshortfall", "hbrentshortfall",

11
app/services/exports/lettings_log_export_service.rb

@ -1,6 +1,7 @@
module Exports module Exports
class LettingsLogExportService class LettingsLogExportService
include Exports::LettingsLogExportConstants include Exports::LettingsLogExportConstants
include CollectionTimeHelper
def initialize(storage_service, logger = Rails.logger) def initialize(storage_service, logger = Rails.logger)
@storage_service = storage_service @storage_service = storage_service
@ -66,11 +67,11 @@ module Exports
return unless lettings_log.startdate return unless lettings_log.startdate
collection_start = lettings_log.collection_start_year collection_start = lettings_log.collection_start_year
month = lettings_log.startdate.month start_month = collection_start_date(lettings_log.startdate).strftime("%b")
quarter = QUARTERS[(month - 1) / 3] end_month = collection_end_date(lettings_log.startdate).strftime("%b")
base_number_str = "f#{base_number.to_s.rjust(4, '0')}" base_number_str = "f#{base_number.to_s.rjust(4, '0')}"
increment_str = "inc#{increment.to_s.rjust(4, '0')}" increment_str = "inc#{increment.to_s.rjust(4, '0')}"
"core_#{collection_start}_#{collection_start + 1}_#{quarter}_#{base_number_str}_#{increment_str}" "core_#{collection_start}_#{collection_start + 1}_#{start_month}_#{end_month}_#{base_number_str}_#{increment_str}".downcase
end end
def write_export_archive(export, lettings_logs) def write_export_archive(export, lettings_logs)
@ -152,7 +153,7 @@ module Exports
def apply_cds_transformation(lettings_log, export_mode) def apply_cds_transformation(lettings_log, export_mode)
attribute_hash = lettings_log.attributes_before_type_cast attribute_hash = lettings_log.attributes_before_type_cast
attribute_hash["form"] = attribute_hash["old_form_id"] || (attribute_hash["id"] + LOG_ID_OFFSET) attribute_hash["formid"] = attribute_hash["old_form_id"] || (attribute_hash["id"] + LOG_ID_OFFSET)
# We can't have a variable number of columns in CSV # We can't have a variable number of columns in CSV
unless export_mode == EXPORT_MODE[:csv] unless export_mode == EXPORT_MODE[:csv]
@ -214,7 +215,7 @@ module Exports
attribute_hash["reghome"] = scheme.registered_under_care_act_before_type_cast attribute_hash["reghome"] = scheme.registered_under_care_act_before_type_cast
attribute_hash["schtype"] = scheme.scheme_type_before_type_cast attribute_hash["schtype"] = scheme.scheme_type_before_type_cast
attribute_hash["support"] = scheme.support_type_before_type_cast attribute_hash["support"] = scheme.support_type_before_type_cast
attribute_hash["units_scheme"] = scheme.locations.map(&:units).sum attribute_hash["units_scheme"] = scheme.locations.map(&:units).compact.sum
end end
def add_location_fields!(location, attribute_hash) def add_location_fields!(location, attribute_hash)

22
app/views/form/review.html.erb

@ -1,9 +1,18 @@
<% content_for :title, "Review lettings log" %> <% if @log.sales? %>
<% content_for :breadcrumbs, govuk_breadcrumbs(breadcrumbs: { <% content_for :title, "Review sales log" %>
<% content_for :breadcrumbs, govuk_breadcrumbs(breadcrumbs: {
"Logs" => "/logs",
"Log #{@log.id}" => "/sales-logs/#{@log.id}",
"Review sales log" => "",
}) %>
<% else %>
<% content_for :title, "Review lettings log" %>
<% content_for :breadcrumbs, govuk_breadcrumbs(breadcrumbs: {
"Logs" => "/logs", "Logs" => "/logs",
"Log #{@log.id}" => "/lettings-logs/#{@log.id}", "Log #{@log.id}" => "/lettings-logs/#{@log.id}",
"Review lettings log" => "", "Review lettings log" => "",
}) %> }) %>
<% end %>
<div class="govuk-grid-row"> <div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop"> <div class="govuk-grid-column-two-thirds-from-desktop">
@ -16,18 +25,17 @@
<% @log.form.sections.map do |section| %> <% @log.form.sections.map do |section| %>
<h2 class="govuk-heading-m"><%= section.label %></h2> <h2 class="govuk-heading-m"><%= section.label %></h2>
<% section.subsections.map do |subsection| %> <% section.subsections.map do |subsection| %>
<% if total_applicable_questions(subsection, @log, current_user).any? %>
<div class="x-govuk-summary-card govuk-!-margin-bottom-6"> <div class="x-govuk-summary-card govuk-!-margin-bottom-6">
<div class="x-govuk-summary-card__header"> <div class="x-govuk-summary-card__header">
<h3 class="x-govuk-summary-card__title"><%= subsection.label %></h3> <h3 class="x-govuk-summary-card__title"><%= subsection.label %></h3>
</div> </div>
<div class="x-govuk-summary-card__body"> <div class="x-govuk-summary-card__body">
<%= render partial: "form/check_answers_summary_list", locals: { <%= render partial: "form/check_answers_summary_list", locals: { subsection: } %>
subsection:,
lettings_log: @log,
} %>
</div> </div>
</div> </div>
<% end %> <% end %>
<% end %> <% end %>
<% end %>
</div> </div>
</div> </div>

3
app/views/logs/_log_filters.erb

@ -6,7 +6,6 @@
<div class="app-filter__content"> <div class="app-filter__content">
<%= form_with html: { method: :get } do |f| %> <%= form_with html: { method: :get } do |f| %>
<% years = { "2021": "2021/22", "2022": "2022/23" } %>
<% all_or_yours = { "all": { label: "All" }, "yours": { label: "Yours" } } %> <% all_or_yours = { "all": { label: "All" }, "yours": { label: "Yours" } } %>
<% if bulk_upload_options(@bulk_upload).present? %> <% if bulk_upload_options(@bulk_upload).present? %>
@ -23,7 +22,7 @@
<%= render partial: "filters/checkbox_filter", <%= render partial: "filters/checkbox_filter",
locals: { locals: {
f: f, f: f,
options: years, options: collection_year_options,
label: "Collection year", label: "Collection year",
category: "years", category: "years",
} %> } %>

2
config/credentials.yml.enc

@ -1 +1 @@
PWC9A1AmalyFv4A63MG9jCm2YtwE1Eyu1BJKodtbmq2nW7FD9svZZ3V4yC0JO9J8sLfCS6UrxQC4TOKKYWP5iYpOr5TKPi9MqNCVueLsoErN+nMAPhCJVpl8+eJ4BOvNYGlmle4bcwbAvHLq6IoANTBjktFh5/tgJS+IHBWK1FwPzK1eAhgQ1fE1jluWslee4iYesmh+ufHIMZAkoWuGJVky4i4uN5nKnwQfaPN5MUxBlvGiDH+s+yex4pcIaJ6hxYWAQRZXJRQVpZcf/agwU0Tk/S/fuDMm8zVeHCQkkmsCYzH5czB8b8IWYwyslqFBZNCix7YnbgwgYk8MUh7wPBuF8CFoPKVyteqic9HgUp9KY8kkt/RcWJ4zpv+Vwz3cre+iZ3S1bxFcSxXqO0MGRug2H9iwhAnBQDLl3vXLNNRYEL5LhNv0Z9Cy7at1fnYe1FcvF+3DR9kG/RAYzR8S2eEDdzBl797+DG81yhfkjP3/gfWxD+J+Mx0F4SDEOaGK5c/MqNTdiRRbnhzuRaQFMg6itoJbZybe+EOQScNft1QLqC4QwPd4Qevhj/A=--QffpsNB1u+1Lk/tD--spkCqBIGHl8g6HGwd89yCg== EZNV2LiNWzf52erbQ41Dz3Bh+2f3Uih8liEyhXp5XzHCLzAbmN6/IJqr7b9cTZiCiroFo4n/dFoG3yYrospp3frKsDXxF1K2/MTCJWjpgnn7wc+HiPQWG0W3HRtQCNkyyrHes0YKcYyDWIP6kztYv1I/Me3p0pGEx6t3CpSTg1v46eRnOlDWiUz3rVxPauwq9IYZ75gmnThqvg/Z8wcYsWLx0arago0SXtRPASCNj4uO/lbqTcAfyIXOTSiOlcAIjoPFRSQY7UqY0o2p8jRR/1L16SmGDsk8ijm+UygNmMexa3Khy5WcKctpQICakHs4NRjHNflqgXpXKL9dVBmNc9d7h+gbhbGJQ53Y0d+a35UbhPRMiv4SRH98FwB+WEsLCDdGSHvdmM6ArfOLljTrqrsmSRf0JfUrvzyVYmMCxjv4xgJwUS/TD5lQD1yPwkp2ss00kQJqzNmB7qwFhA8a3e2iNzV8qtAV/Nj+tMlr99Hb7vZZs98/38G2p5RAsE/5Xl9taKhc/ACnVc/bwJND4JWaBB7duCa08xVB8nkjlt5cCwMurzAcy1ZT+e8JepR+g6s8fpScMEWVJXE0hd8=--rZ41rY9TMXmiBUJw--QiLRVNVXZzTW446s7cec1g==

2
config/forms/2022_2023.json

@ -1,7 +1,7 @@
{ {
"form_type": "lettings", "form_type": "lettings",
"start_date": "2022-04-01T00:00:00.000+01:00", "start_date": "2022-04-01T00:00:00.000+01:00",
"end_date": "2023-07-01T00:00:00.000+01:00", "end_date": "2023-07-09T00:00:00.000+01:00",
"unresolved_log_redirect_page_id": "tenancy_start_date", "unresolved_log_redirect_page_id": "tenancy_start_date",
"sections": { "sections": {
"tenancy_and_property": { "tenancy_and_property": {

10
config/initializers/feature_toggle.rb

@ -1,14 +1,14 @@
class FeatureToggle class FeatureToggle
def self.startdate_two_week_validation_enabled? def self.startdate_two_week_validation_enabled?
Rails.env.production? || Rails.env.test? Rails.env.production? || Rails.env.test? || Rails.env.staging?
end end
def self.startdate_collection_window_validation_enabled? def self.startdate_collection_window_validation_enabled?
Rails.env.production? || Rails.env.test? Rails.env.production? || Rails.env.test? || Rails.env.staging?
end end
def self.saledate_collection_window_validation_enabled? def self.saledate_collection_window_validation_enabled?
Rails.env.production? || Rails.env.test? Rails.env.production? || Rails.env.test? || Rails.env.staging?
end end
def self.sales_log_enabled? def self.sales_log_enabled?
@ -42,4 +42,8 @@ class FeatureToggle
def self.validate_valid_radio_options? def self.validate_valid_radio_options?
!(Rails.env.production? || Rails.env.staging?) !(Rails.env.production? || Rails.env.staging?)
end end
def self.collection_2023_2024_year_enabled?
!Rails.env.production?
end
end end

13
config/locales/en.yml

@ -156,6 +156,7 @@ en:
before_scheme_end_date: "The tenancy start date must be before the end date for this supported housing scheme" before_scheme_end_date: "The tenancy start date must be before the end date for this supported housing scheme"
after_void_date: "Enter a tenancy start date that is after the void date" after_void_date: "Enter a tenancy start date that is after the void date"
after_major_repair_date: "Enter a tenancy start date that is after the major repair date" after_major_repair_date: "Enter a tenancy start date that is after the major repair date"
year_not_two_digits: Tenancy start year must be 2 digits
location: location:
deactivated: "The location %{postcode} was deactivated on %{date} and was not available on the day you entered." deactivated: "The location %{postcode} was deactivated on %{date} and was not available on the day you entered."
reactivating_soon: "The location %{postcode} is not available until %{date}. Select another location or edit the tenancy start date" reactivating_soon: "The location %{postcode} is not available until %{date}. Select another location or edit the tenancy start date"
@ -170,6 +171,9 @@ en:
invalid: "Please select owning organisation or managing organisation that you belong to" invalid: "Please select owning organisation or managing organisation that you belong to"
created_by: created_by:
invalid: "Please select owning organisation or managing organisation that you belong to" invalid: "Please select owning organisation or managing organisation that you belong to"
lettype:
general_needs_mismatch: Lettings type must be a general needs type because you selected general needs when uploading the file
supported_housing_mismatch: Lettings type must be a supported housing type because you selected supported housing when uploading the file
property: property:
mrcdate: mrcdate:
@ -199,6 +203,9 @@ en:
beds: beds:
non_positive: "Number of bedrooms has to be greater than 0" non_positive: "Number of bedrooms has to be greater than 0"
over_max: "Number of bedrooms cannot be more than 12" over_max: "Number of bedrooms cannot be more than 12"
bedsits_have_max_one_bedroom: "Number of bedrooms must be 1 if the property is a bedsit"
proptype:
bedsits_have_max_one_bedroom: "Answer cannot be 'Bedsit' if the property has 2 or more bedrooms"
postcode: postcode:
must_match_previous: "Buyer's last accommodation and discounted ownership postcodes must match" must_match_previous: "Buyer's last accommodation and discounted ownership postcodes must match"
@ -345,6 +352,12 @@ en:
must_be_child: "Answer must be ‘child’ if the person is aged 16-19 and a student" must_be_child: "Answer must be ‘child’ if the person is aged 16-19 and a student"
housingneeds_a: housingneeds_a:
one_or_two_choices: "You can only select one option or ‘other disabled access needs’ plus ‘wheelchair-accessible housing’, ‘wheelchair access to essential rooms’ or ‘level access housing’" one_or_two_choices: "You can only select one option or ‘other disabled access needs’ plus ‘wheelchair-accessible housing’, ‘wheelchair access to essential rooms’ or ‘level access housing’"
housingneeds_type:
only_one_option_permitted: "Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected"
housingneeds:
no_disabled_needs_conjunction: "No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs"
dont_know_disabled_needs_conjunction: "Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs"
no_and_dont_know_disabled_needs_conjunction: "No disabled access needs and don’t know disabled access needs cannot be selected together"
prevten: prevten:
non_temp_accommodation: "Answer cannot be non-temporary accommodation as this is a re-let to a tenant who occupied the same property as temporary accommodation" non_temp_accommodation: "Answer cannot be non-temporary accommodation as this is a re-let to a tenant who occupied the same property as temporary accommodation"
over_20_foster_care: "Answer cannot be a children’s home or foster care as the lead tenant is 20 or older" over_20_foster_care: "Answer cannot be a children’s home or foster care as the lead tenant is 20 or older"

4
config/routes.rb

@ -171,6 +171,10 @@ Rails.application.routes.draw do
resources :bulk_upload_sales_results, path: "bulk-upload-results", only: [:show] resources :bulk_upload_sales_results, path: "bulk-upload-results", only: [:show]
end end
member do
get "review", to: "form#review"
end
FormHandler.instance.sales_forms.each do |_key, form| FormHandler.instance.sales_forms.each do |_key, form|
form.pages.map do |page| form.pages.map do |page|
get page.id.to_s.dasherize, to: "form#show_page" get page.id.to_s.dasherize, to: "form#show_page"

1185
config/sale_range_data/2023.csv

File diff suppressed because it is too large Load Diff

7
db/migrate/20230203153051_add_prev_shared_to_sales.rb

@ -0,0 +1,7 @@
class AddPrevSharedToSales < ActiveRecord::Migration[7.0]
def change
change_table :sales_logs, bulk: true do |t|
t.column :prevshared, :integer
end
end
end

5
db/migrate/20230210122037_add_staircasesale_to_sales_logs.rb

@ -0,0 +1,5 @@
class AddStaircasesaleToSalesLogs < ActiveRecord::Migration[7.0]
def change
add_column :sales_logs, :staircasesale, :integer
end
end

8
db/migrate/20230210143120_add_ethnic_fields_for_buyer2.rb

@ -0,0 +1,8 @@
class AddEthnicFieldsForBuyer2 < ActiveRecord::Migration[7.0]
def change
change_table :sales_logs, bulk: true do |t|
t.column :ethnic_group2, :integer
t.column :ethnicbuy2, :integer
end
end
end

8
db/schema.rb

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2023_02_03_174815) do ActiveRecord::Schema[7.0].define(version: 2023_02_10_143120) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -522,9 +522,13 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_03_174815) do
t.integer "staircase_bought_value_check" t.integer "staircase_bought_value_check"
t.integer "income2_value_check" t.integer "income2_value_check"
t.integer "monthly_charges_value_check" t.integer "monthly_charges_value_check"
t.integer "saledate_check"
t.integer "details_known_5" t.integer "details_known_5"
t.integer "details_known_6" t.integer "details_known_6"
t.integer "saledate_check"
t.integer "prevshared"
t.integer "staircasesale"
t.integer "ethnic_group2"
t.integer "ethnicbuy2"
t.index ["bulk_upload_id"], name: "index_sales_logs_on_bulk_upload_id" t.index ["bulk_upload_id"], name: "index_sales_logs_on_bulk_upload_id"
t.index ["created_by_id"], name: "index_sales_logs_on_created_by_id" t.index ["created_by_id"], name: "index_sales_logs_on_created_by_id"
t.index ["owning_organisation_id"], name: "index_sales_logs_on_owning_organisation_id" t.index ["owning_organisation_id"], name: "index_sales_logs_on_owning_organisation_id"

63
docs/Gemfile.lock

@ -1,12 +1,11 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
activesupport (6.0.6) activesupport (7.0.4.2)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2) i18n (>= 1.6, < 2)
minitest (~> 5.1) minitest (>= 5.1)
tzinfo (~> 1.1) tzinfo (~> 2.0)
zeitwerk (~> 2.2, >= 2.2.2)
addressable (2.8.1) addressable (2.8.1)
public_suffix (>= 2.0.2, < 6.0) public_suffix (>= 2.0.2, < 6.0)
coffee-script (2.4.1) coffee-script (2.4.1)
@ -14,30 +13,30 @@ GEM
execjs execjs
coffee-script-source (1.11.1) coffee-script-source (1.11.1)
colorator (1.1.0) colorator (1.1.0)
commonmarker (0.23.6) commonmarker (0.23.8)
concurrent-ruby (1.1.10) concurrent-ruby (1.2.0)
dnsruby (1.61.9) dnsruby (1.61.9)
simpleidn (~> 0.1) simpleidn (~> 0.1)
em-websocket (0.5.3) em-websocket (0.5.3)
eventmachine (>= 0.12.9) eventmachine (>= 0.12.9)
http_parser.rb (~> 0) http_parser.rb (~> 0)
ethon (0.15.0) ethon (0.16.0)
ffi (>= 1.15.0) ffi (>= 1.15.0)
eventmachine (1.2.7) eventmachine (1.2.7)
execjs (2.8.1) execjs (2.8.1)
faraday (2.5.2) faraday (2.7.4)
faraday-net_http (>= 2.0, < 3.1) faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4) ruby2_keywords (>= 0.0.4)
faraday-net_http (3.0.0) faraday-net_http (3.0.2)
ffi (1.15.5) ffi (1.15.5)
forwardable-extended (2.6.0) forwardable-extended (2.6.0)
gemoji (3.0.1) gemoji (3.0.1)
github-pages (227) github-pages (228)
github-pages-health-check (= 1.17.9) github-pages-health-check (= 1.17.9)
jekyll (= 3.9.2) jekyll (= 3.9.3)
jekyll-avatar (= 0.7.0) jekyll-avatar (= 0.7.0)
jekyll-coffeescript (= 1.1.1) jekyll-coffeescript (= 1.1.1)
jekyll-commonmark-ghpages (= 0.2.0) jekyll-commonmark-ghpages (= 0.4.0)
jekyll-default-layout (= 0.1.4) jekyll-default-layout (= 0.1.4)
jekyll-feed (= 0.15.1) jekyll-feed (= 0.15.1)
jekyll-gist (= 1.5.0) jekyll-gist (= 1.5.0)
@ -71,7 +70,7 @@ GEM
jemoji (= 0.12.0) jemoji (= 0.12.0)
kramdown (= 2.3.2) kramdown (= 2.3.2)
kramdown-parser-gfm (= 1.1.0) kramdown-parser-gfm (= 1.1.0)
liquid (= 4.0.3) liquid (= 4.0.4)
mercenary (~> 0.3) mercenary (~> 0.3)
minima (= 2.5.1) minima (= 2.5.1)
nokogiri (>= 1.13.6, < 2.0) nokogiri (>= 1.13.6, < 2.0)
@ -83,17 +82,17 @@ GEM
octokit (~> 4.0) octokit (~> 4.0)
public_suffix (>= 3.0, < 5.0) public_suffix (>= 3.0, < 5.0)
typhoeus (~> 1.3) typhoeus (~> 1.3)
html-pipeline (2.14.2) html-pipeline (2.14.3)
activesupport (>= 2) activesupport (>= 2)
nokogiri (>= 1.4) nokogiri (>= 1.4)
http_parser.rb (0.8.0) http_parser.rb (0.8.0)
i18n (0.9.5) i18n (1.12.0)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
jekyll (3.9.2) jekyll (3.9.3)
addressable (~> 2.4) addressable (~> 2.4)
colorator (~> 1.0) colorator (~> 1.0)
em-websocket (~> 0.5) em-websocket (~> 0.5)
i18n (~> 0.7) i18n (>= 0.7, < 2)
jekyll-sass-converter (~> 1.0) jekyll-sass-converter (~> 1.0)
jekyll-watch (~> 2.0) jekyll-watch (~> 2.0)
kramdown (>= 1.17, < 3) kramdown (>= 1.17, < 3)
@ -109,11 +108,11 @@ GEM
coffee-script-source (~> 1.11.1) coffee-script-source (~> 1.11.1)
jekyll-commonmark (1.4.0) jekyll-commonmark (1.4.0)
commonmarker (~> 0.22) commonmarker (~> 0.22)
jekyll-commonmark-ghpages (0.2.0) jekyll-commonmark-ghpages (0.4.0)
commonmarker (~> 0.23.4) commonmarker (~> 0.23.7)
jekyll (~> 3.9.0) jekyll (~> 3.9.0)
jekyll-commonmark (~> 1.4.0) jekyll-commonmark (~> 1.4.0)
rouge (>= 2.0, < 4.0) rouge (>= 2.0, < 5.0)
jekyll-default-layout (0.1.4) jekyll-default-layout (0.1.4)
jekyll (~> 3.0) jekyll (~> 3.0)
jekyll-feed (0.15.1) jekyll-feed (0.15.1)
@ -201,8 +200,8 @@ GEM
rexml rexml
kramdown-parser-gfm (1.1.0) kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0) kramdown (~> 2.0)
liquid (4.0.3) liquid (4.0.4)
listen (3.7.1) listen (3.8.0)
rb-fsevent (~> 0.10, >= 0.10.3) rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10) rb-inotify (~> 0.9, >= 0.9.10)
mercenary (0.3.6) mercenary (0.3.6)
@ -210,12 +209,12 @@ GEM
jekyll (>= 3.5, < 5.0) jekyll (>= 3.5, < 5.0)
jekyll-feed (~> 0.9) jekyll-feed (~> 0.9)
jekyll-seo-tag (~> 2.1) jekyll-seo-tag (~> 2.1)
minitest (5.16.3) minitest (5.17.0)
nokogiri (1.13.10-arm64-darwin) nokogiri (1.14.2-arm64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.13.10-x86_64-darwin) nokogiri (1.14.2-x86_64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.13.10-x86_64-linux) nokogiri (1.14.2-x86_64-linux)
racc (~> 1.4) racc (~> 1.4)
octokit (4.25.1) octokit (4.25.1)
faraday (>= 1, < 3) faraday (>= 1, < 3)
@ -223,7 +222,7 @@ GEM
pathutil (0.16.2) pathutil (0.16.2)
forwardable-extended (~> 2.6) forwardable-extended (~> 2.6)
public_suffix (4.0.7) public_suffix (4.0.7)
racc (1.6.1) racc (1.6.2)
rb-fsevent (0.11.2) rb-fsevent (0.11.2)
rb-inotify (0.10.1) rb-inotify (0.10.1)
ffi (~> 1.0) ffi (~> 1.0)
@ -244,17 +243,15 @@ GEM
unf (~> 0.1.4) unf (~> 0.1.4)
terminal-table (1.8.0) terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1) unicode-display_width (~> 1.1, >= 1.1.1)
thread_safe (0.3.6)
typhoeus (1.4.0) typhoeus (1.4.0)
ethon (>= 0.9.0) ethon (>= 0.9.0)
tzinfo (1.2.10) tzinfo (2.0.6)
thread_safe (~> 0.1) concurrent-ruby (~> 1.0)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.8.2) unf_ext (0.0.8.2)
unicode-display_width (1.8.0) unicode-display_width (1.8.0)
webrick (1.7.0) webrick (1.8.1)
zeitwerk (2.6.0)
PLATFORMS PLATFORMS
arm64-darwin-21 arm64-darwin-21

18
lib/tasks/data_export.rake

@ -11,21 +11,3 @@ namespace :core do
DataExportXmlJob.perform_later(full_update:) DataExportXmlJob.perform_later(full_update:)
end end
end end
namespace :illness_type_0 do
desc "Export log data where illness_type_0 == 1"
task export: :environment do |_task|
logs = LettingsLog.where(illness_type_0: 1, status: "completed").includes(created_by: :organisation)
puts "log_id,created_by_id,organisation_id,organisation_name,startdate"
logs.each do |log|
puts [
log.id,
log.created_by_id,
log.created_by.organisation.id,
log.created_by.organisation.name,
log.startdate&.strftime("%d/%m/%Y"),
].join(",")
end
end
end

8
spec/factories/organisation.rb

@ -13,6 +13,14 @@ FactoryBot.define do
trait :with_old_visible_id do trait :with_old_visible_id do
old_visible_id { rand(9_999_999).to_s } old_visible_id { rand(9_999_999).to_s }
end end
trait :prp do
provider_type { "PRP" }
end
trait :does_not_own_stock do
holds_own_stock { false }
end
end end
factory :organisation_rent_period do factory :organisation_rent_period do

1
spec/factories/sales_log.rb

@ -60,6 +60,7 @@ FactoryBot.define do
la { "E09000003" } la { "E09000003" }
savingsnk { 1 } savingsnk { 1 }
prevown { 1 } prevown { 1 }
prevshared { 2 }
sex3 { "X" } sex3 { "X" }
sex4 { "X" } sex4 { "X" }
sex5 { "X" } sex5 { "X" }

2
spec/fixtures/exports/general_needs_log.csv vendored

@ -1,2 +1,2 @@
status,tenancycode,age1,sex1,ethnic,national,prevten,ecstat1,hhmemb,age2,sex2,ecstat2,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,leftreg,reservist,illness,preg_occ,startertenancy,tenancylength,tenancy,ppostcode_full,rsnvac,unittype_gn,beds,offered,wchair,earnings,incfreq,benefits,period,layear,waityear,postcode_full,reasonpref,cbl,chr,cap,reasonother,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_f,housingneeds_g,housingneeds_h,illness_type_1,illness_type_2,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,irproduct_other,reason,propcode,la,prevloc,hb,hbrentshortfall,mrcdate,incref,startdate,armedforces,unitletas,builtype,voiddate,renttype,needstype,lettype,totchild,totelder,totadult,nocharge,referral,brent,scharge,pscharge,supcharg,tcharge,tshortfall,chcharge,ppcodenk,has_benefits,renewal,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat2,relat3,relat4,relat5,relat6,relat7,relat8,lar,irproduct,joint,sheltered,hhtype,new_old,vacdays,form,owningorgid,owningorgname,hcnum,maningorgid,maningorgname,manhcnum,createddate,uploaddate status,tenancycode,age1,sex1,ethnic,national,prevten,ecstat1,hhmemb,age2,sex2,ecstat2,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,leftreg,reservist,illness,preg_occ,startertenancy,tenancylength,tenancy,ppostcode_full,rsnvac,unittype_gn,beds,offered,wchair,earnings,incfreq,benefits,period,layear,waityear,postcode_full,reasonpref,cbl,chr,cap,reasonother,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_f,housingneeds_g,housingneeds_h,illness_type_1,illness_type_2,illness_type_3,illness_type_4,illness_type_8,illness_type_5,illness_type_6,illness_type_7,illness_type_9,illness_type_10,rp_homeless,rp_insan_unsat,rp_medwel,rp_hardship,rp_dontknow,tenancyother,irproduct_other,reason,propcode,la,prevloc,hb,hbrentshortfall,mrcdate,incref,startdate,armedforces,unitletas,builtype,voiddate,renttype,needstype,lettype,totchild,totelder,totadult,nocharge,referral,brent,scharge,pscharge,supcharg,tcharge,tshortfall,chcharge,ppcodenk,has_benefits,renewal,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat2,relat3,relat4,relat5,relat6,relat7,relat8,lar,irproduct,joint,sheltered,hhtype,new_old,vacdays,formid,owningorgid,owningorgname,hcnum,maningorgid,maningorgname,manhcnum,createddate,uploaddate
2,BZ737,35,F,2,4,6,0,2,32,M,6,,,,,,,,,,,,,,,,,,,1,4,1,1,1,2,1,5,1,SE2 6RT,6,7,3,2,1,68,1,1,2,2,1,NW1 5TY,1,2,1,2,,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,,,4,123,E09000003,E07000105,6,1,2020-05-05T10:36:49+01:00,0,2022-02-02T10:36:49+00:00,1,2,1,2019-11-03T00:00:00+00:00,2,1,7,0,0,2,0,2,200.0,50.0,40.0,35.0,325.0,12.0,,1,1,0,100.0,25.0,20.0,17.5,162.5,6.0,0,1,,2,P,,,,,,,,,,,4,2,638,{id},{owning_org_id},DLUHC,1234,{managing_org_id},DLUHC,1234,2022-02-08T16:52:15+00:00,2022-02-08T16:52:15+00:00 2,BZ737,35,F,2,4,6,0,2,32,M,6,,,,,,,,,,,,,,,,,,,1,4,1,1,1,2,1,5,1,SE2 6RT,6,7,3,2,1,68,1,1,2,2,1,NW1 5TY,1,2,1,2,,1,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,,,4,123,E09000003,E07000105,6,1,2020-05-05T10:36:49+01:00,0,2022-02-02T10:36:49+00:00,1,2,1,2019-11-03T00:00:00+00:00,2,1,7,0,0,2,0,2,200.0,50.0,40.0,35.0,325.0,12.0,,1,1,0,100.0,25.0,20.0,17.5,162.5,6.0,0,1,,2,P,,,,,,,,,,,4,2,638,{id},{owning_org_id},DLUHC,1234,{managing_org_id},DLUHC,1234,2022-02-08T16:52:15+00:00,2022-02-08T16:52:15+00:00

1 status tenancycode age1 sex1 ethnic national prevten ecstat1 hhmemb age2 sex2 ecstat2 age3 sex3 ecstat3 age4 sex4 ecstat4 age5 sex5 ecstat5 age6 sex6 ecstat6 age7 sex7 ecstat7 age8 sex8 ecstat8 homeless underoccupation_benefitcap leftreg reservist illness preg_occ startertenancy tenancylength tenancy ppostcode_full rsnvac unittype_gn beds offered wchair earnings incfreq benefits period layear waityear postcode_full reasonpref cbl chr cap reasonother housingneeds_a housingneeds_b housingneeds_c housingneeds_f housingneeds_g housingneeds_h illness_type_1 illness_type_2 illness_type_3 illness_type_4 illness_type_8 illness_type_5 illness_type_6 illness_type_7 illness_type_9 illness_type_10 rp_homeless rp_insan_unsat rp_medwel rp_hardship rp_dontknow tenancyother irproduct_other reason propcode la prevloc hb hbrentshortfall mrcdate incref startdate armedforces unitletas builtype voiddate renttype needstype lettype totchild totelder totadult nocharge referral brent scharge pscharge supcharg tcharge tshortfall chcharge ppcodenk has_benefits renewal wrent wscharge wpschrge wsupchrg wtcharge wtshortfall refused housingneeds wchchrg newprop relat2 relat3 relat4 relat5 relat6 relat7 relat8 lar irproduct joint sheltered hhtype new_old vacdays form formid owningorgid owningorgname hcnum maningorgid maningorgname manhcnum createddate uploaddate
2 2 BZ737 35 F 2 4 6 0 2 32 M 6 1 4 1 1 1 2 1 5 1 SE2 6RT 6 7 3 2 1 68 1 1 2 2 1 NW1 5TY 1 2 1 2 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 4 123 E09000003 E07000105 6 1 2020-05-05T10:36:49+01:00 0 2022-02-02T10:36:49+00:00 1 2 1 2019-11-03T00:00:00+00:00 2 1 7 0 0 2 0 2 200.0 50.0 40.0 35.0 325.0 12.0 1 1 0 100.0 25.0 20.0 17.5 162.5 6.0 0 1 2 P 4 2 638 {id} {id} {owning_org_id} DLUHC 1234 {managing_org_id} DLUHC 1234 2022-02-08T16:52:15+00:00 2022-02-08T16:52:15+00:00

2
spec/fixtures/exports/general_needs_log.xml vendored

@ -135,7 +135,7 @@
<hhtype>4</hhtype> <hhtype>4</hhtype>
<new_old>2</new_old> <new_old>2</new_old>
<vacdays>638</vacdays> <vacdays>638</vacdays>
<form>{id}</form> <formid>{id}</formid>
<owningorgid>{owning_org_id}</owningorgid> <owningorgid>{owning_org_id}</owningorgid>
<owningorgname>DLUHC</owningorgname> <owningorgname>DLUHC</owningorgname>
<hcnum>1234</hcnum> <hcnum>1234</hcnum>

2
spec/fixtures/exports/supported_housing_logs.xml vendored

@ -134,7 +134,7 @@
<hhtype>4</hhtype> <hhtype>4</hhtype>
<new_old>2</new_old> <new_old>2</new_old>
<vacdays>638</vacdays> <vacdays>638</vacdays>
<form>{id}</form> <formid>{id}</formid>
<owningorgid>{owning_org_id}</owningorgid> <owningorgid>{owning_org_id}</owningorgid>
<owningorgname>DLUHC</owningorgname> <owningorgname>DLUHC</owningorgname>
<hcnum>1234</hcnum> <hcnum>1234</hcnum>

46
spec/helpers/collection_time_helper_spec.rb

@ -21,6 +21,10 @@ RSpec.describe CollectionTimeHelper do
it "returns the correct current start date" do it "returns the correct current start date" do
expect(current_collection_start_date).to eq(Time.zone.local(2022, 4, 1)) expect(current_collection_start_date).to eq(Time.zone.local(2022, 4, 1))
end end
it "returns the correct current end date" do
expect(current_collection_end_date).to eq(Time.zone.local(2023, 3, 31))
end
end end
context "with the date before 1st of April" do context "with the date before 1st of April" do
@ -29,6 +33,48 @@ RSpec.describe CollectionTimeHelper do
it "returns the previous year as the current start year" do it "returns the previous year as the current start year" do
expect(current_collection_start_year).to eq(2021) expect(current_collection_start_year).to eq(2021)
end end
it "returns the correct current start date" do
expect(current_collection_start_date).to eq(Time.zone.local(2021, 4, 1))
end
it "returns the correct current end date" do
expect(current_collection_end_date).to eq(Time.zone.local(2022, 3, 31))
end
end
end
describe "Any collection year" do
context "when the date is after 1st of April" do
let(:now) { Time.utc(2022, 8, 3) }
it "returns the same year as the current start year" do
expect(collection_start_year(now)).to eq(2022)
end
it "returns the correct current start date" do
expect(collection_start_date(now)).to eq(Time.zone.local(2022, 4, 1))
end
it "returns the correct current end date" do
expect(collection_end_date(now)).to eq(Time.zone.local(2023, 3, 31))
end
end
context "with the date before 1st of April" do
let(:now) { Time.utc(2022, 2, 3) }
it "returns the previous year as the current start year" do
expect(collection_start_year(now)).to eq(2021)
end
it "returns the correct current start date" do
expect(collection_start_date(now)).to eq(Time.zone.local(2021, 4, 1))
end
it "returns the correct current end date" do
expect(collection_end_date(now)).to eq(Time.zone.local(2022, 3, 31))
end
end end
end end
end end

26
spec/helpers/filters_helper_spec.rb

@ -116,4 +116,30 @@ RSpec.describe FiltersHelper do
end end
end end
end end
describe "#collection_year_options" do
context "when not production" do
it "includes 2023/2024 option" do
expect(collection_year_options).to eq(
{
"2021": "2021/22", "2022": "2022/23", "2023": "2023/24"
},
)
end
end
context "when production" do
before do
allow(Rails.env).to receive(:production?).and_return(true)
end
it "includes 2023/2024 option" do
expect(collection_year_options).to eq(
{
"2021": "2021/22", "2022": "2022/23"
},
)
end
end
end
end end

4
spec/helpers/tasklist_helper_spec.rb

@ -113,7 +113,7 @@ RSpec.describe TasklistHelper do
it "returns relevant text" do it "returns relevant text" do
expect(review_log_text(lettings_log)).to eq( expect(review_log_text(lettings_log)).to eq(
"You can #{govuk_link_to 'review and make changes to this log', review_lettings_log_path(lettings_log)} until 1 July 2024.".html_safe, "You can #{govuk_link_to 'review and make changes to this log', review_lettings_log_path(lettings_log)} until 9 July 2024.".html_safe,
) )
end end
end end
@ -136,7 +136,7 @@ RSpec.describe TasklistHelper do
it "returns relevant text" do it "returns relevant text" do
expect(review_log_text(sales_log)).to eq( expect(review_log_text(sales_log)).to eq(
"You can #{govuk_link_to 'review and make changes to this log', review_lettings_log_path(sales_log)} until 1 July 2023.".html_safe, "You can #{govuk_link_to 'review and make changes to this log', review_sales_log_path(id: sales_log, sales_log: true)} until 7 July 2023.".html_safe,
) )
end end
end end

54
spec/mailers/bulk_upload_mailer_spec.rb

@ -5,7 +5,7 @@ RSpec.describe BulkUploadMailer do
let(:notify_client) { instance_double(Notifications::Client) } let(:notify_client) { instance_double(Notifications::Client) }
let(:user) { create(:user, email: "user@example.com") } let(:user) { create(:user, email: "user@example.com") }
let(:bulk_upload) { build(:bulk_upload, :lettings, user:) } let(:bulk_upload) { create(:bulk_upload, :lettings, user:) }
before do before do
allow(Notifications::Client).to receive(:new).and_return(notify_client) allow(Notifications::Client).to receive(:new).and_return(notify_client)
@ -75,4 +75,56 @@ RSpec.describe BulkUploadMailer do
end end
end end
end end
describe "#send_correct_and_upload_again_mail" do
context "when 2 columns with errors" do
before do
create(:bulk_upload_error, bulk_upload:, col: "A")
create(:bulk_upload_error, bulk_upload:, col: "B")
end
it "sends correctly formed email with A, B" do
expect(notify_client).to receive(:send_email).with(
email_address: user.email,
template_id: described_class::BULK_UPLOAD_FAILED_CSV_ERRORS_TEMPLATE_ID,
personalisation: {
filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
year_combo: bulk_upload.year_combo,
lettings_or_sales: bulk_upload.log_type,
error_description: "We noticed that you have a lot of similar errors in column A, B. Please correct your data export and upload again.",
summary_report_link: "http://localhost:3000/lettings-logs/bulk-upload-results/#{bulk_upload.id}/summary",
},
)
mailer.send_correct_and_upload_again_mail(bulk_upload:)
end
end
context "when 4 columns with errors" do
before do
create(:bulk_upload_error, bulk_upload:, col: "A")
create(:bulk_upload_error, bulk_upload:, col: "B")
create(:bulk_upload_error, bulk_upload:, col: "C")
create(:bulk_upload_error, bulk_upload:, col: "D")
end
it "sends correctly formed email with A, B, C and more" do
expect(notify_client).to receive(:send_email).with(
email_address: user.email,
template_id: described_class::BULK_UPLOAD_FAILED_CSV_ERRORS_TEMPLATE_ID,
personalisation: {
filename: bulk_upload.filename,
upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time),
year_combo: bulk_upload.year_combo,
lettings_or_sales: bulk_upload.log_type,
error_description: "We noticed that you have a lot of similar errors in column A, B, C and more. Please correct your data export and upload again.",
summary_report_link: "http://localhost:3000/lettings-logs/bulk-upload-results/#{bulk_upload.id}/summary",
},
)
mailer.send_correct_and_upload_again_mail(bulk_upload:)
end
end
end
end end

16
spec/models/form/sales/pages/about_staircase_spec.rb

@ -11,9 +11,25 @@ RSpec.describe Form::Sales::Pages::AboutStaircase, type: :model do
expect(page.subsection).to eq(subsection) expect(page.subsection).to eq(subsection)
end end
describe "questions" do
let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date:)) }
context "when 2022" do
let(:start_date) { Time.utc(2022, 2, 8) }
it "has correct questions" do it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[stairbought stairowned]) expect(page.questions.map(&:id)).to eq(%w[stairbought stairowned])
end end
end
context "when 2023" do
let(:start_date) { Time.utc(2023, 2, 8) }
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[stairbought stairowned staircasesale])
end
end
end
it "has the correct id" do it "has the correct id" do
expect(page.id).to eq("about_staircasing") expect(page.id).to eq("about_staircasing")

33
spec/models/form/sales/pages/buyer2_ethnic_background_arab_spec.rb

@ -0,0 +1,33 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::Buyer2EthnicBackgroundArab, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[ethnicbuy2])
end
it "has the correct id" do
expect(page.id).to eq("buyer_2_ethnic_background_arab")
end
it "has the correct header" do
expect(page.header).to be_nil
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([{ "ethnic_group2" => 4 }])
end
end

33
spec/models/form/sales/pages/buyer2_ethnic_background_asian_spec.rb

@ -0,0 +1,33 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::Buyer2EthnicBackgroundAsian, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[ethnicbuy2])
end
it "has the correct id" do
expect(page.id).to eq("buyer_2_ethnic_background_asian")
end
it "has the correct header" do
expect(page.header).to be_nil
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([{ "ethnic_group2" => 2 }])
end
end

33
spec/models/form/sales/pages/buyer2_ethnic_background_black_spec.rb

@ -0,0 +1,33 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::Buyer2EthnicBackgroundBlack, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[ethnicbuy2])
end
it "has the correct id" do
expect(page.id).to eq("buyer_2_ethnic_background_black")
end
it "has the correct header" do
expect(page.header).to be_nil
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([{ "ethnic_group2" => 3 }])
end
end

33
spec/models/form/sales/pages/buyer2_ethnic_background_mixed_spec.rb

@ -0,0 +1,33 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::Buyer2EthnicBackgroundMixed, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[ethnicbuy2])
end
it "has the correct id" do
expect(page.id).to eq("buyer_2_ethnic_background_mixed")
end
it "has the correct header" do
expect(page.header).to be_nil
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([{ "ethnic_group2" => 1 }])
end
end

33
spec/models/form/sales/pages/buyer2_ethnic_background_white_spec.rb

@ -0,0 +1,33 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::Buyer2EthnicBackgroundWhite, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[ethnicbuy2])
end
it "has the correct id" do
expect(page.id).to eq("buyer_2_ethnic_background_white")
end
it "has the correct header" do
expect(page.header).to be_nil
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([{ "ethnic_group2" => 0 }])
end
end

42
spec/models/form/sales/pages/buyer2_ethnic_group_spec.rb

@ -0,0 +1,42 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::Buyer2EthnicGroup, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[ethnic_group2])
end
it "has the correct id" do
expect(page.id).to eq("buyer_2_ethnic_group")
end
it "has the correct header" do
expect(page.header).to be_nil
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([
{
"jointpur" => 1,
"privacynotice" => 1,
},
{
"jointpur" => 1,
"noint" => 1,
},
])
end
end

29
spec/models/form/sales/pages/previous_shared_spec.rb

@ -0,0 +1,29 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::PreviousShared, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection) }
it "has correct subsection" do
expect(page.subsection).to eq(subsection)
end
it "has correct questions" do
expect(page.questions.map(&:id)).to eq(%w[prevshared])
end
it "has the correct id" do
expect(page.id).to eq("previous_shared")
end
it "has the correct header" do
expect(page.header).to be_nil
end
it "has the correct description" do
expect(page.description).to be_nil
end
end

48
spec/models/form/sales/questions/buyer2_ethnic_background_arab_spec.rb

@ -0,0 +1,48 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::Buyer2EthnicBackgroundArab, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { 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("ethnicbuy2")
end
it "has the correct header" do
expect(question.header).to eq("Which of the following best describes the buyer 2’s Arab background?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Buyer 2’s ethnic background")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct hint_text" do
expect(question.hint_text).to be nil
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"16" => { "value" => "Other ethnic group" },
"19" => { "value" => "Arab" },
})
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(2)
end
end

51
spec/models/form/sales/questions/buyer2_ethnic_background_asian_spec.rb

@ -0,0 +1,51 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::Buyer2EthnicBackgroundAsian, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { 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("ethnicbuy2")
end
it "has the correct header" do
expect(question.header).to eq("Which of the following best describes the buyer 2’s Asian or Asian British background?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Buyer 2’s ethnic background")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct hint_text" do
expect(question.hint_text).to be nil
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"10" => { "value" => "Bangladeshi" },
"11" => { "value" => "Any other Asian or Asian British background" },
"15" => { "value" => "Chinese" },
"8" => { "value" => "Indian" },
"9" => { "value" => "Pakistani" },
})
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(2)
end
end

49
spec/models/form/sales/questions/buyer2_ethnic_background_black_spec.rb

@ -0,0 +1,49 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::Buyer2EthnicBackgroundBlack, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { 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("ethnicbuy2")
end
it "has the correct header" do
expect(question.header).to eq("Which of the following best describes the buyer 2’s Black, African, Caribbean or Black British background?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Buyer 2’s ethnic background")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct hint_text" do
expect(question.hint_text).to be nil
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"12" => { "value" => "Caribbean" },
"13" => { "value" => "African" },
"14" => { "value" => "Any other Black, African or Caribbean background" },
})
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(2)
end
end

50
spec/models/form/sales/questions/buyer2_ethnic_background_mixed_spec.rb

@ -0,0 +1,50 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::Buyer2EthnicBackgroundMixed, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { 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("ethnicbuy2")
end
it "has the correct header" do
expect(question.header).to eq("Which of the following best describes the buyer 2’s Mixed or Multiple ethnic groups background?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Buyer 2’s ethnic background")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct hint_text" do
expect(question.hint_text).to be nil
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"4" => { "value" => "White and Black Caribbean" },
"5" => { "value" => "White and Black African" },
"6" => { "value" => "White and Asian" },
"7" => { "value" => "Any other Mixed or Multiple ethnic background" },
})
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(2)
end
end

50
spec/models/form/sales/questions/buyer2_ethnic_background_white_spec.rb

@ -0,0 +1,50 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::Buyer2EthnicBackgroundWhite, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { 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("ethnicbuy2")
end
it "has the correct header" do
expect(question.header).to eq("Which of the following best describes the buyer 2’s White background?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Buyer 2’s ethnic background")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct hint_text" do
expect(question.hint_text).to be nil
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"1" => { "value" => "English, Welsh, Northern Irish, Scottish or British" },
"18" => { "value" => "Gypsy or Irish Traveller" },
"2" => { "value" => "Irish" },
"3" => { "value" => "Any other White background" },
})
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(2)
end
end

62
spec/models/form/sales/questions/buyer2_ethnic_group_spec.rb

@ -0,0 +1,62 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::Buyer2EthnicGroup, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { 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("ethnic_group2")
end
it "has the correct header" do
expect(question.header).to eq("What is buyer 2’s ethnic group?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Buyer 2’s ethnic group")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct hint_text" do
expect(question.hint_text).to be nil
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"0" => { "value" => "White" },
"1" => { "value" => "Mixed or Multiple ethnic groups" },
"17" => { "value" => "Buyer 1 prefers not to say" },
"2" => { "value" => "Asian or Asian British" },
"3" => { "value" => "Black, African, Caribbean or Black British" },
"4" => { "value" => "Arab or other ethnic group" },
"divider" => { "value" => true },
})
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to eq(2)
end
it "has the correct inferred_check_answers_value" do
expect(question.inferred_check_answers_value).to eq([{
"condition" => {
"ethnic_group2" => 17,
},
"value" => "Prefers not to say",
}])
end
end

49
spec/models/form/sales/questions/prevshared_spec.rb

@ -0,0 +1,49 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::Prevshared, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { 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("prevshared")
end
it "has the correct header" do
expect(question.header).to eq("Was the previous property a shared ownership property?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Previous property shared ownership?")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don’t know" },
})
end
it "has correct conditional for" do
expect(question.conditional_for).to eq(nil)
end
it "has the correct hint" do
expect(question.hint_text).to be_nil
end
end

49
spec/models/form/sales/questions/staircase_sale_spec.rb

@ -0,0 +1,49 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::StaircaseSale, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { 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("staircasesale")
end
it "has the correct header" do
expect(question.header).to eq("Is this transaction part of a back-to-back staircasing transaction to facilitate sale of the home on the open market?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Part of a back-to-back staircasing transaction")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don't know" },
})
end
it "has correct conditional for" do
expect(question.conditional_for).to eq(nil)
end
it "has the correct hint" do
expect(question.hint_text).to be_nil
end
end

1
spec/models/form/sales/subsections/discounted_ownership_scheme_spec.rb

@ -12,7 +12,6 @@ RSpec.describe Form::Sales::Subsections::DiscountedOwnershipScheme, type: :model
end end
it "has correct pages" do it "has correct pages" do
puts discounted_ownership_scheme.pages.map(&:id)
expect(discounted_ownership_scheme.pages.map(&:id)).to eq( expect(discounted_ownership_scheme.pages.map(&:id)).to eq(
%w[ %w[
living_before_purchase_discounted_ownership living_before_purchase_discounted_ownership

88
spec/models/form/sales/subsections/household_characteristics_spec.rb

@ -6,11 +6,18 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
let(:subsection_id) { nil } let(:subsection_id) { nil }
let(:subsection_definition) { nil } let(:subsection_definition) { nil }
let(:section) { instance_double(Form::Sales::Sections::Household) } let(:section) { instance_double(Form::Sales::Sections::Household) }
let(:form) { instance_double(Form) }
it "has correct section" do it "has correct section" do
expect(household_characteristics.section).to eq(section) expect(household_characteristics.section).to eq(section)
end end
context "with 2022/23 form" do
before do
allow(form).to receive(:start_date).and_return(Time.zone.local(2022, 4, 1))
allow(section).to receive(:form).and_return(form)
end
it "has correct pages" do it "has correct pages" do
expect(household_characteristics.pages.map(&:id)).to eq( expect(household_characteristics.pages.map(&:id)).to eq(
%w[ %w[
@ -39,8 +46,88 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
buyer_2_gender_identity buyer_2_gender_identity
gender_2_buyer_retirement_value_check gender_2_buyer_retirement_value_check
buyer_2_working_situation buyer_2_working_situation
working_situation_2_buyer_retirement_value_check
buyer_2_live_in_property
number_of_others_in_property
person_2_known
person_2_relationship_to_buyer_1
person_2_age
age_2_retirement_value_check
person_2_gender_identity
gender_2_retirement_value_check
person_2_working_situation
working_situation_2_retirement_value_check_joint_purchase working_situation_2_retirement_value_check_joint_purchase
working_situation_buyer_2_income_value_check working_situation_buyer_2_income_value_check
person_3_known
person_3_relationship_to_buyer_1
person_3_age
age_3_retirement_value_check
person_3_gender_identity
gender_3_retirement_value_check
person_3_working_situation
working_situation_3_retirement_value_check
person_4_known
person_4_relationship_to_buyer_1
person_4_age
age_4_retirement_value_check
person_4_gender_identity
gender_4_retirement_value_check
person_4_working_situation
working_situation_4_retirement_value_check
person_5_known
person_5_relationship_to_buyer_1
person_5_age
age_5_retirement_value_check
person_5_gender_identity
gender_5_retirement_value_check
person_5_working_situation
working_situation_5_retirement_value_check
],
)
end
end
context "with 2023/24 form" do
before do
allow(form).to receive(:start_date).and_return(Time.zone.local(2023, 4, 1))
allow(section).to receive(:form).and_return(form)
end
it "has correct pages" do
expect(household_characteristics.pages.map(&:id)).to eq(
%w[
buyer_interview
privacy_notice
buyer_1_age
age_1_retirement_value_check
age_1_old_persons_shared_ownership_value_check
buyer_1_gender_identity
gender_1_retirement_value_check
buyer_1_ethnic_group
buyer_1_ethnic_background_black
buyer_1_ethnic_background_asian
buyer_1_ethnic_background_arab
buyer_1_ethnic_background_mixed
buyer_1_ethnic_background_white
buyer_1_nationality
buyer_1_working_situation
working_situation_1_retirement_value_check
working_situation_buyer_1_income_value_check
buyer_1_live_in_property
buyer_2_relationship_to_buyer_1
buyer_2_age
age_2_old_persons_shared_ownership_value_check
age_2_buyer_retirement_value_check
buyer_2_gender_identity
gender_2_buyer_retirement_value_check
buyer_2_ethnic_group
buyer_2_ethnic_background_black
buyer_2_ethnic_background_asian
buyer_2_ethnic_background_arab
buyer_2_ethnic_background_mixed
buyer_2_ethnic_background_white
buyer_2_working_situation
working_situation_2_buyer_retirement_value_check
buyer_2_live_in_property buyer_2_live_in_property
number_of_others_in_property number_of_others_in_property
person_2_known person_2_known
@ -78,6 +165,7 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model
], ],
) )
end end
end
it "has the correct id" do it "has the correct id" do
expect(household_characteristics.id).to eq("household_characteristics") expect(household_characteristics.id).to eq("household_characteristics")

35
spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb

@ -11,6 +11,38 @@ RSpec.describe Form::Sales::Subsections::IncomeBenefitsAndSavings, type: :model
expect(subsection.section).to eq(section) expect(subsection.section).to eq(section)
end end
describe "pages" do
let(:section) { instance_double(Form::Sales::Sections::Household, form: instance_double(Form, start_date:)) }
context "when 2022" do
let(:start_date) { Time.utc(2022, 2, 8) }
it "has correct pages" do
expect(subsection.pages.compact.map(&:id)).to eq(
%w[
buyer_1_income
buyer_1_income_value_check
buyer_1_income_mortgage_value_check
buyer_1_mortgage
buyer_1_mortgage_value_check
buyer_2_income
buyer_2_income_mortgage_value_check
buyer_2_income_value_check
buyer_2_mortgage
buyer_2_mortgage_value_check
housing_benefits
savings
savings_value_check
savings_deposit_value_check
previous_ownership
],
)
end
end
context "when 2023" do
let(:start_date) { Time.utc(2023, 2, 8) }
it "has correct pages" do it "has correct pages" do
expect(subsection.pages.map(&:id)).to eq( expect(subsection.pages.map(&:id)).to eq(
%w[ %w[
@ -29,9 +61,12 @@ RSpec.describe Form::Sales::Subsections::IncomeBenefitsAndSavings, type: :model
savings_value_check savings_value_check
savings_deposit_value_check savings_deposit_value_check
previous_ownership previous_ownership
previous_shared
], ],
) )
end end
end
end
it "has the correct id" do it "has the correct id" do
expect(subsection.id).to eq("income_benefits_and_savings") expect(subsection.id).to eq("income_benefits_and_savings")

2
spec/models/form_handler_spec.rb

@ -41,12 +41,14 @@ RSpec.describe FormHandler do
form = form_handler.get_form("current_lettings") form = form_handler.get_form("current_lettings")
expect(form).to be_a(Form) expect(form).to be_a(Form)
expect(form.pages.count).to eq(13) expect(form.pages.count).to eq(13)
expect(form.name).to eq("2022_2023_lettings")
end end
it "is able to load a previous lettings form" do it "is able to load a previous lettings form" do
form = form_handler.get_form("previous_lettings") form = form_handler.get_form("previous_lettings")
expect(form).to be_a(Form) expect(form).to be_a(Form)
expect(form.pages.count).to eq(46) expect(form.pages.count).to eq(46)
expect(form.name).to eq("2021_2022_lettings")
end end
it "is able to load a current sales form" do it "is able to load a current sales form" do

2
spec/models/form_spec.rb

@ -223,7 +223,7 @@ RSpec.describe Form, type: :model do
expect(form.questions.count).to eq(16) expect(form.questions.count).to eq(16)
expect(form.questions.first.id).to eq("owning_organisation_id") expect(form.questions.first.id).to eq("owning_organisation_id")
expect(form.start_date).to eq(Time.zone.parse("2022-04-01")) expect(form.start_date).to eq(Time.zone.parse("2022-04-01"))
expect(form.end_date).to eq(Time.zone.parse("2023-07-01")) expect(form.end_date).to eq(Time.zone.parse("2023-07-07"))
expect(form.unresolved_log_redirect_page_id).to eq(nil) expect(form.unresolved_log_redirect_page_id).to eq(nil)
end end

20
spec/models/lettings_log_spec.rb

@ -18,6 +18,11 @@ RSpec.describe LettingsLog do
expect(described_class).to be < ApplicationRecord expect(described_class).to be < ApplicationRecord
end end
it "is a not a sales log" do
lettings_log = FactoryBot.build(:lettings_log, created_by: created_by_user)
expect(lettings_log.sales?).to be false
end
it "is a lettings log" do it "is a lettings log" do
lettings_log = FactoryBot.build(:lettings_log, created_by: created_by_user) lettings_log = FactoryBot.build(:lettings_log, created_by: created_by_user)
expect(lettings_log).to be_lettings expect(lettings_log).to be_lettings
@ -1454,6 +1459,15 @@ RSpec.describe LettingsLog do
expect(record_from_db["vacdays"]).to eq(0) expect(record_from_db["vacdays"]).to eq(0)
expect(lettings_log["vacdays"]).to eq(0) expect(lettings_log["vacdays"]).to eq(0)
end end
it "correctly derives and saves first_time_property_let_as_social_housing" do
record_from_db = ActiveRecord::Base.connection.execute(
"select first_time_property_let_as_social_housing" \
" from lettings_logs where id=#{lettings_log.id}",
).to_a[0]
expect(record_from_db["first_time_property_let_as_social_housing"]).to eq(0)
expect(lettings_log["first_time_property_let_as_social_housing"]).to eq(0)
end
end end
context "when answering the household characteristics questions" do context "when answering the household characteristics questions" do
@ -1946,12 +1960,12 @@ RSpec.describe LettingsLog do
end end
context "when a non select question associated with several pages is routed to" do context "when a non select question associated with several pages is routed to" do
let(:lettings_log) { FactoryBot.create(:lettings_log, :in_progress, period: 2) } let(:lettings_log) { FactoryBot.create(:lettings_log, :in_progress, period: 2, needstype: 1) }
it "does not clear the answer value" do it "does not clear the answer value" do
lettings_log.update!({ offered: 4 }) lettings_log.update!({ unitletas: 1 })
lettings_log.reload lettings_log.reload
expect(lettings_log.offered).to eq(4) expect(lettings_log.unitletas).to eq(1)
end end
end end

7
spec/models/sales_log_spec.rb

@ -12,11 +12,16 @@ RSpec.describe SalesLog, type: :model do
expect(described_class).to be < ApplicationRecord expect(described_class).to be < ApplicationRecord
end end
it "is a sales log" do it "is a not a lettings log" do
sales_log = build(:sales_log, created_by: created_by_user) sales_log = build(:sales_log, created_by: created_by_user)
expect(sales_log.lettings?).to be false expect(sales_log.lettings?).to be false
end end
it "is a sales log" do
sales_log = build(:sales_log, created_by: created_by_user)
expect(sales_log.sales?).to be true
end
describe "#new" do describe "#new" do
context "when creating a record" do context "when creating a record" do
let(:sales_log) do let(:sales_log) do

36
spec/models/validations/sales/property_validations_spec.rb

@ -48,4 +48,40 @@ RSpec.describe Validations::Sales::PropertyValidations do
end end
end end
end end
describe "#validate_property_unit_type" do
context "when number of bedrooms is 1" do
let(:record) { FactoryBot.build(:sales_log, beds: 1, proptype: 2) }
it "does not add an error if it's a bedsit" do
property_validator.validate_bedsit_number_of_beds(record)
expect(record.errors).not_to be_present
end
end
context "when number of bedrooms is > 1" do
let(:record) { FactoryBot.build(:sales_log, beds: 2, proptype: 2) }
it "does add an error if it's a bedsit" do
property_validator.validate_bedsit_number_of_beds(record)
expect(record.errors.added?(:proptype, "Answer cannot be 'Bedsit' if the property has 2 or more bedrooms")).to be true
expect(record.errors.added?(:beds, "Number of bedrooms must be 1 if the property is a bedsit")).to be true
end
it "does not add an error if proptype is undefined" do
record.update!(proptype: nil)
property_validator.validate_bedsit_number_of_beds(record)
expect(record.errors).not_to be_present
end
end
context "when number of bedrooms is undefined" do
let(:record) { FactoryBot.build(:sales_log, beds: nil, proptype: 2) }
it "does not add an error if it's a bedsit" do
property_validator.validate_bedsit_number_of_beds(record)
expect(record.errors).not_to be_present
end
end
end
end end

6
spec/requests/form_controller_spec.rb

@ -314,6 +314,12 @@ RSpec.describe FormController, type: :request do
get "/lettings-logs/#{setup_complete_lettings_log.id}/review", headers: headers, params: {} get "/lettings-logs/#{setup_complete_lettings_log.id}/review", headers: headers, params: {}
expect(response.body).to match("Review lettings log") expect(response.body).to match("Review lettings log")
end end
it "renders the review page for the sales log" do
log = create(:sales_log, :completed, created_by: user)
get "/sales-logs/#{log.id}/review", headers: headers, params: { sales_log: true }
expect(response.body).to match("Review sales log")
end
end end
context "when viewing a user dependent page" do context "when viewing a user dependent page" do

25
spec/requests/sales_logs_controller_spec.rb

@ -61,6 +61,31 @@ RSpec.describe SalesLogsController, type: :request do
expect(response).to have_http_status(:unauthorized) expect(response).to have_http_status(:unauthorized)
end end
end end
context "with a request containing invalid json parameters" do
let(:params) do
{
"saledate": Date.new(2022, 4, 1),
"purchid": "1",
"ownershipsch": 1,
"type": 2,
"jointpur": 1,
"jointmore": 1,
"beds": 2,
"proptype": 2,
}
end
before do
post "/sales-logs", headers:, params: params.to_json
end
it "validates sales log parameters" do
json_response = JSON.parse(response.body)
expect(response).to have_http_status(:unprocessable_entity)
expect(json_response["errors"]).to match_array([["beds", ["Number of bedrooms must be 1 if the property is a bedsit"]], ["proptype", ["Answer cannot be 'Bedsit' if the property has 2 or more bedrooms"]]])
end
end
end end
context "when UI" do context "when UI" do

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

@ -24,6 +24,23 @@ RSpec.describe BulkUpload::Lettings::LogCreator do
end end
end end
context "when a valid csv with several blank rows" do
let(:file) { Tempfile.new }
let(:path) { file.path }
let(:log) { LettingsLog.new }
before do
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_csv_row)
file.write(BulkUpload::LogToCsv.new(log:, col_offset: 0).to_csv_row)
file.rewind
end
it "ignores them and does not create the logs" do
expect { service.call }.not_to change(LettingsLog, :count)
end
end
context "when a valid csv with row with one invalid non setup field" do context "when a valid csv with row with one invalid non setup field" do
let(:file) { Tempfile.new } let(:file) { Tempfile.new }
let(:path) { file.path } let(:path) { file.path }

287
spec/services/bulk_upload/lettings/row_parser_spec.rb

@ -8,8 +8,10 @@ RSpec.describe BulkUpload::Lettings::RowParser do
let(:attributes) { { bulk_upload: } } let(:attributes) { { bulk_upload: } }
let(:bulk_upload) { create(:bulk_upload, :lettings, user:) } let(:bulk_upload) { create(:bulk_upload, :lettings, user:) }
let(:user) { create(:user, organisation: owning_org) } let(:user) { create(:user, organisation: owning_org) }
let(:owning_org) { create(:organisation, :with_old_visible_id) } let(:owning_org) { create(:organisation, :with_old_visible_id) }
let(:managing_org) { create(:organisation, :with_old_visible_id) } let(:managing_org) { create(:organisation, :with_old_visible_id) }
let(:setup_section_params) do let(:setup_section_params) do
{ {
bulk_upload:, bulk_upload:,
@ -23,6 +25,10 @@ RSpec.describe BulkUpload::Lettings::RowParser do
} }
end end
before do
create(:organisation_relationship, parent_organisation: owning_org, child_organisation: managing_org)
end
around do |example| around do |example|
FormHandler.instance.use_real_forms! FormHandler.instance.use_real_forms!
@ -31,6 +37,24 @@ RSpec.describe BulkUpload::Lettings::RowParser do
FormHandler.instance.use_fake_forms! FormHandler.instance.use_fake_forms!
end end
describe "#blank_row?" do
context "when a new object" do
it "returns true" do
expect(parser).to be_blank_row
end
end
context "when any field is populated" do
before do
parser.field_1 = "1"
end
it "returns false" do
expect(parser).not_to be_blank_row
end
end
end
describe "validations" do describe "validations" do
before do before do
stub_request(:get, /api.postcodes.io/) stub_request(:get, /api.postcodes.io/)
@ -40,6 +64,14 @@ RSpec.describe BulkUpload::Lettings::RowParser do
end end
describe "#valid?" do describe "#valid?" do
context "when the row is blank" do
let(:attributes) { { bulk_upload: } }
it "returns true" do
expect(parser).to be_valid
end
end
context "when calling the method multiple times" do context "when calling the method multiple times" do
let(:attributes) { { bulk_upload:, field_134: 2 } } let(:attributes) { { bulk_upload:, field_134: 2 } }
@ -172,7 +204,7 @@ RSpec.describe BulkUpload::Lettings::RowParser do
describe "#field_1" do describe "#field_1" do
context "when null" do context "when null" do
let(:attributes) { { bulk_upload:, field_1: nil } } let(:attributes) { { bulk_upload:, field_1: nil, field_4: "1" } }
it "returns an error" do it "returns an error" do
expect(parser.errors[:field_1]).to be_present expect(parser.errors[:field_1]).to be_present
@ -202,6 +234,46 @@ RSpec.describe BulkUpload::Lettings::RowParser do
expect(parser.errors[:field_1]).to be_blank expect(parser.errors[:field_1]).to be_blank
end end
end end
context "when bulk upload is for general needs" do
let(:bulk_upload) { create(:bulk_upload, :lettings, user:, needstype: "1") }
context "when general needs option selected" do
let(:attributes) { { bulk_upload:, field_1: "1" } }
it "is permitted" do
expect(parser.errors[:field_1]).to be_blank
end
end
context "when supported housing option selected" do
let(:attributes) { { bulk_upload:, field_1: "2" } }
it "is not permitted" do
expect(parser.errors[:field_1]).to include("Lettings type must be a general needs type because you selected general needs when uploading the file")
end
end
end
context "when bulk upload is for supported housing" do
let(:bulk_upload) { create(:bulk_upload, :lettings, user:, needstype: "2") }
context "when general needs option selected" do
let(:attributes) { { bulk_upload:, field_1: "1" } }
it "is not permitted" do
expect(parser.errors[:field_1]).to include("Lettings type must be a supported housing type because you selected supported housing when uploading the file")
end
end
context "when supported housing option selected" do
let(:attributes) { { bulk_upload:, field_1: "2" } }
it "is permitted" do
expect(parser.errors[:field_1]).to be_blank
end
end
end
end end
describe "#field_4" do describe "#field_4" do
@ -278,6 +350,49 @@ RSpec.describe BulkUpload::Lettings::RowParser do
end end
end end
describe "#field_55, #field_56, #field_57" do
context "when more than one item selected" do
let(:attributes) { { bulk_upload:, field_55: "1", field_56: "1" } }
it "is not permitted" do
expect(parser.errors[:field_55]).to be_present
expect(parser.errors[:field_56]).to be_present
expect(parser.errors[:field_57]).to be_present
end
end
end
describe "#field_59" do
context "when 1 and another disability field selected" do
let(:attributes) { { bulk_upload:, field_59: "1", field_58: "1" } }
it "is not permitted" do
expect(parser.errors[:field_59]).to be_present
end
end
end
describe "#field_60" do
context "when 1 and another disability field selected" do
let(:attributes) { { bulk_upload:, field_60: "1", field_58: "1" } }
it "is not permitted" do
expect(parser.errors[:field_60]).to be_present
end
end
end
describe "#field_59, #field_60" do
context "when both 1" do
let(:attributes) { { bulk_upload:, field_59: "1", field_60: "1" } }
it "is not permitted" do
expect(parser.errors[:field_59]).to be_present
expect(parser.errors[:field_60]).to be_present
end
end
end
describe "#field_78" do # referral describe "#field_78" do # referral
context "when 3 ie PRP nominated by LA and owning org is LA" do context "when 3 ie PRP nominated by LA and owning org is LA" do
let(:attributes) { { bulk_upload:, field_78: "3", field_111: owning_org.old_visible_id } } let(:attributes) { { bulk_upload:, field_78: "3", field_111: owning_org.old_visible_id } }
@ -287,14 +402,24 @@ RSpec.describe BulkUpload::Lettings::RowParser do
end end
end end
context "when 4 ie referred by LA and is general needs" do context "when 4 ie referred by LA and is general needs and owning org is LA" do
let(:attributes) { { bulk_upload:, field_78: "4" } } let(:attributes) { { bulk_upload:, field_78: "4", field_111: owning_org.old_visible_id.to_s } }
it "is not permitted" do it "is not permitted" do
expect(parser.errors[:field_78]).to be_present expect(parser.errors[:field_78]).to be_present
end end
end end
context "when 4 ie referred by LA and is general needs and owning org is PRP" do
let(:owning_org) { create(:organisation, :prp, :with_old_visible_id) }
let(:attributes) { { bulk_upload:, field_78: "4", field_111: owning_org.old_visible_id.to_s } }
it "is permitted" do
expect(parser.errors[:field_78]).to be_blank
end
end
context "when 4 ie referred by LA and is not general needs" do context "when 4 ie referred by LA and is not general needs" do
let(:bulk_upload) { create(:bulk_upload, :lettings, user:, needstype: 2) } let(:bulk_upload) { create(:bulk_upload, :lettings, user:, needstype: 2) }
let(:attributes) { { bulk_upload:, field_78: "4" } } let(:attributes) { { bulk_upload:, field_78: "4" } }
@ -307,7 +432,7 @@ RSpec.describe BulkUpload::Lettings::RowParser do
describe "fields 96, 97, 98 => startdate" do describe "fields 96, 97, 98 => startdate" do
context "when any one of these fields is blank" do context "when any one of these fields is blank" do
let(:attributes) { { bulk_upload:, field_96: nil, field_97: nil, field_98: nil } } let(:attributes) { { bulk_upload:, field_1: "1", field_96: nil, field_97: nil, field_98: nil } }
it "returns an error" do it "returns an error" do
parser.valid? parser.valid?
@ -318,6 +443,24 @@ RSpec.describe BulkUpload::Lettings::RowParser do
end end
end end
context "when field 98 is 4 digits instead of 2" do
let(:attributes) { { bulk_upload:, field_98: "2022" } }
it "returns an error" do
parser.valid?
expect(parser.errors[:field_98]).to include("Tenancy start year must be 2 digits")
end
end
context "when invalid date given" do
let(:attributes) { { bulk_upload:, field_1: "1", field_96: "a", field_97: "12", field_98: "2022" } }
it "does not raise an error" do
expect { parser.valid? }.not_to raise_error
end
end
context "when inside of collection year" do context "when inside of collection year" do
let(:attributes) { { bulk_upload:, field_96: "1", field_97: "10", field_98: "22" } } let(:attributes) { { bulk_upload:, field_96: "1", field_97: "10", field_98: "22" } }
@ -353,6 +496,68 @@ RSpec.describe BulkUpload::Lettings::RowParser do
end end
end end
describe "#field_111" do # owning org
context "when cannot find owning org" do
let(:attributes) { { bulk_upload:, field_111: "donotexist" } }
it "is not permitted" do
expect(parser.errors[:field_111]).to eql(["The owning organisation code is incorrect"])
end
end
context "when org is not stock owning" do
let(:owning_org) { create(:organisation, :with_old_visible_id, :does_not_own_stock) }
let(:attributes) { { bulk_upload:, field_111: owning_org.old_visible_id } }
it "is not permitted" do
expect(parser.errors[:field_111]).to eql(["The owning organisation code provided is for an organisation that does not own stock"])
end
it "blocks log creation" do
expect(parser).to be_block_log_creation
end
end
context "when not affiliated with owning org" do
let(:unaffiliated_org) { create(:organisation, :with_old_visible_id) }
let(:attributes) { { bulk_upload:, field_111: unaffiliated_org.old_visible_id } }
it "is not permitted" do
expect(parser.errors[:field_111]).to eql(["You do not have permission to add logs for this owning organisation"])
end
it "blocks log creation" do
expect(parser).to be_block_log_creation
end
end
end
describe "#field_113" do # managing org
context "when cannot find managing org" do
let(:attributes) { { bulk_upload:, field_113: "donotexist" } }
it "is not permitted" do
expect(parser.errors[:field_113]).to eql(["The managing organisation code is incorrect"])
end
end
context "when not affiliated with managing org" do
let(:unaffiliated_org) { create(:organisation, :with_old_visible_id) }
let(:attributes) { { bulk_upload:, field_111: owning_org.old_visible_id, field_113: unaffiliated_org.old_visible_id } }
it "is not permitted" do
expect(parser.errors[:field_113]).to eql(["This managing organisation does not have a relationship with the owning organisation"])
end
it "blocks log creation" do
expect(parser).to be_block_log_creation
end
end
end
describe "#field_134" do describe "#field_134" do
context "when an unpermitted value" do context "when an unpermitted value" do
let(:attributes) { { bulk_upload:, field_134: 3 } } let(:attributes) { { bulk_upload:, field_134: 3 } }
@ -387,6 +592,26 @@ RSpec.describe BulkUpload::Lettings::RowParser do
end end
describe "#log" do describe "#log" do
describe "#owning_organisation" do
context "when lookup is via id prefixed with ORG" do
let(:attributes) { { bulk_upload:, field_111: "ORG#{owning_org.id}" } }
it "assigns the correct org" do
expect(parser.log.owning_organisation).to eql(owning_org)
end
end
end
describe "#managing_organisation" do
context "when lookup is via id prefixed with ORG" do
let(:attributes) { { bulk_upload:, field_113: "ORG#{managing_org.id}" } }
it "assigns the correct org" do
expect(parser.log.managing_organisation).to eql(managing_org)
end
end
end
describe "#cbl" do describe "#cbl" do
context "when field_75 is yes ie 1" do context "when field_75 is yes ie 1" do
let(:attributes) { { bulk_upload:, field_75: 1 } } let(:attributes) { { bulk_upload:, field_75: 1 } }
@ -828,6 +1053,60 @@ RSpec.describe BulkUpload::Lettings::RowParser do
end end
end end
end end
describe "#housingneeds" do
context "when no disabled needs" do
let(:attributes) { { bulk_upload:, field_59: "1" } }
it "sets to 2" do
expect(parser.log.housingneeds).to eq(2)
end
end
context "when dont know about disabled needs" do
let(:attributes) { { bulk_upload:, field_60: "1" } }
it "sets to 3" do
expect(parser.log.housingneeds).to eq(3)
end
end
end
describe "#housingneeds_type" do
context "when field_55 is 1" do
let(:attributes) { { bulk_upload:, field_55: "1" } }
it "set to 0" do
expect(parser.log.housingneeds_type).to eq(0)
end
end
context "when field_56 is 1" do
let(:attributes) { { bulk_upload:, field_56: "1" } }
it "set to 1" do
expect(parser.log.housingneeds_type).to eq(1)
end
end
context "when field_57 is 1" do
let(:attributes) { { bulk_upload:, field_57: "1" } }
it "set to 2" do
expect(parser.log.housingneeds_type).to eq(2)
end
end
end
describe "#housingneeds_other" do
context "when field_58 is 1" do
let(:attributes) { { bulk_upload:, field_58: "1" } }
it "sets to 1" do
expect(parser.log.housingneeds_other).to eq(1)
end
end
end
end end
describe "#start_date" do describe "#start_date" do

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

@ -87,7 +87,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
end end
end end
describe "#should_create_logs?" do describe "#create_logs?" do
context "when all logs are valid" do context "when all logs are valid" do
let(:target_path) { file_fixture("2022_23_lettings_bulk_upload.csv") } let(:target_path) { file_fixture("2022_23_lettings_bulk_upload.csv") }
@ -111,9 +111,7 @@ RSpec.describe BulkUpload::Lettings::Validator do
expect(validator).not_to be_create_logs expect(validator).not_to be_create_logs
end end
end end
end
describe "#create_logs?" do
context "when a log is not valid?" do context "when a log is not valid?" do
let(:log_1) { build(:lettings_log, :completed, created_by: user) } let(:log_1) { build(:lettings_log, :completed, created_by: user) }
let(:log_2) { build(:lettings_log, :completed, created_by: user) } let(:log_2) { build(:lettings_log, :completed, created_by: user) }
@ -146,6 +144,22 @@ RSpec.describe BulkUpload::Lettings::Validator do
end end
end end
context "when a single log wants to block log creation" do
let(:unaffiliated_org) { create(:organisation) }
let(:log_1) { build(:lettings_log, :completed, renttype: 1, created_by: user, owning_organisation: unaffiliated_org) }
before do
file.write(BulkUpload::LogToCsv.new(log: log_1, line_ending: "\r\n", col_offset: 0).to_csv_row)
file.close
end
it "will not create logs" do
validator.call
expect(validator).not_to be_create_logs
end
end
context "when a log has incomplete setup secion" do context "when a log has incomplete setup secion" do
let(:log) { build(:lettings_log, :in_progress, created_by: user, startdate: Time.zone.local(2022, 5, 1)) } let(:log) { build(:lettings_log, :in_progress, created_by: user, startdate: Time.zone.local(2022, 5, 1)) }

11
spec/services/bulk_upload/processor_spec.rb

@ -171,6 +171,17 @@ RSpec.describe BulkUpload::Processor do
expect(mock_downloader).to have_received(:delete_local_file!) expect(mock_downloader).to have_received(:delete_local_file!)
end end
it "sends correct and upload again mail" do
mail_double = instance_double("ActionMailer::MessageDelivery", deliver_later: nil)
allow(BulkUploadMailer).to receive(:send_correct_and_upload_again_mail).and_return(mail_double)
processor.call
expect(BulkUploadMailer).to have_received(:send_correct_and_upload_again_mail)
expect(mail_double).to have_received(:deliver_later)
end
it "does not send fix errors email" do it "does not send fix errors email" do
allow(BulkUploadMailer).to receive(:send_bulk_upload_with_errors_mail).and_call_original allow(BulkUploadMailer).to receive(:send_bulk_upload_with_errors_mail).and_call_original

12
spec/services/exports/lettings_log_export_service_spec.rb

@ -13,8 +13,8 @@ RSpec.describe Exports::LettingsLogExportService do
let(:expected_master_manifest_filename) { "Manifest_2022_05_01_0001.csv" } let(:expected_master_manifest_filename) { "Manifest_2022_05_01_0001.csv" }
let(:expected_master_manifest_rerun) { "Manifest_2022_05_01_0002.csv" } let(:expected_master_manifest_rerun) { "Manifest_2022_05_01_0002.csv" }
let(:expected_zip_filename) { "core_2021_2022_jan_mar_f0001_inc0001.zip" } let(:expected_zip_filename) { "core_2021_2022_apr_mar_f0001_inc0001.zip" }
let(:expected_data_filename) { "core_2021_2022_jan_mar_f0001_inc0001_pt001.xml" } let(:expected_data_filename) { "core_2021_2022_apr_mar_f0001_inc0001_pt001.xml" }
let(:expected_manifest_filename) { "manifest.xml" } let(:expected_manifest_filename) { "manifest.xml" }
let(:start_time) { Time.zone.local(2022, 5, 1) } let(:start_time) { Time.zone.local(2022, 5, 1) }
@ -108,7 +108,7 @@ RSpec.describe Exports::LettingsLogExportService do
end end
context "and multiple lettings logs are available for export on different periods" do context "and multiple lettings logs are available for export on different periods" do
let(:expected_zip_filename2) { "core_2022_2023_apr_jun_f0001_inc0001.zip" } let(:expected_zip_filename2) { "core_2022_2023_apr_mar_f0001_inc0001.zip" }
before do before do
FactoryBot.create(:lettings_log, startdate: Time.zone.local(2022, 2, 1)) FactoryBot.create(:lettings_log, startdate: Time.zone.local(2022, 2, 1))
@ -206,7 +206,7 @@ RSpec.describe Exports::LettingsLogExportService do
end end
it "generates a ZIP export file with the expected filename" do it "generates a ZIP export file with the expected filename" do
expect(storage_service).to receive(:write_file).with("core_2021_2022_jan_mar_f0002_inc0001.zip", any_args) expect(storage_service).to receive(:write_file).with("core_2021_2022_apr_mar_f0002_inc0001.zip", any_args)
export_service.export_xml_lettings_logs(full_update: true) export_service.export_xml_lettings_logs(full_update: true)
end end
end end
@ -258,6 +258,10 @@ RSpec.describe Exports::LettingsLogExportService do
let(:lettings_log) { FactoryBot.create(:lettings_log, :completed, :export, :sh, scheme:, location:, created_by: user, owning_organisation: organisation, startdate: Time.utc(2022, 2, 2, 10, 36, 49), underoccupation_benefitcap: 4, sheltered: 1) } let(:lettings_log) { FactoryBot.create(:lettings_log, :completed, :export, :sh, scheme:, location:, created_by: user, owning_organisation: organisation, startdate: Time.utc(2022, 2, 2, 10, 36, 49), underoccupation_benefitcap: 4, sheltered: 1) }
before do
FactoryBot.create(:location, scheme:, startdate: Time.zone.local(2021, 4, 1), units: nil)
end
it "generates an XML export file with the expected content" do it "generates an XML export file with the expected content" do
expected_content = replace_entity_ids(lettings_log, export_file.read) expected_content = replace_entity_ids(lettings_log, export_file.read)
expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content| expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content|

Loading…
Cancel
Save