Browse Source

CLDC-2068 request, validate UPRN and retrieve address (#1370)

* Add OS_DATA_KEY env var

* Add new UPRN and address columns to logs

* Bugfix: use dynamic optional fields

* Update optional fields

* Add UPRN validation

* Add UPRN Client

* Add UPRN Presenter

* UPRN questions and flows

* Skip to non addresss questions if UPRN unknown

* Address PO review comments and add missing specs

* Display LA correctly
pull/1378/head
Jack 2 years ago committed by GitHub
parent
commit
cff4cc45bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .env.example
  2. 2
      .env.test
  3. 2
      .github/workflows/production_pipeline.yml
  4. 2
      .github/workflows/review_pipeline.yml
  5. 2
      .github/workflows/staging_pipeline.yml
  6. 2
      app/controllers/form_controller.rb
  7. 6
      app/helpers/question_view_helper.rb
  8. 9
      app/models/derived_variables/sales_log_variables.rb
  9. 2
      app/models/form.rb
  10. 6
      app/models/form/page.rb
  11. 5
      app/models/form/question.rb
  12. 23
      app/models/form/sales/pages/address.rb
  13. 16
      app/models/form/sales/pages/property_local_authority.rb
  14. 26
      app/models/form/sales/pages/uprn.rb
  15. 17
      app/models/form/sales/pages/uprn_confirmation.rb
  16. 12
      app/models/form/sales/pages/uprn_known.rb
  17. 37
      app/models/form/sales/questions/address_line1.rb
  18. 13
      app/models/form/sales/questions/address_line2.rb
  19. 13
      app/models/form/sales/questions/county.rb
  20. 25
      app/models/form/sales/questions/postcode_for_full_address.rb
  21. 4
      app/models/form/sales/questions/property_local_authority.rb
  22. 13
      app/models/form/sales/questions/town_or_city.rb
  23. 34
      app/models/form/sales/questions/uprn.rb
  24. 34
      app/models/form/sales/questions/uprn_confirmation.rb
  25. 21
      app/models/form/sales/questions/uprn_known.rb
  26. 25
      app/models/form/sales/subsections/property_information.rb
  27. 2
      app/models/lettings_log.rb
  28. 18
      app/models/log.rb
  29. 7
      app/models/sales_log.rb
  30. 8
      app/models/validations/property_validations.rb
  31. 8
      app/models/validations/sales/property_validations.rb
  32. 50
      app/services/uprn_client.rb
  33. 41
      app/services/uprn_data_presenter.rb
  34. 15
      app/views/form/_check_answers_summary_list.html.erb
  35. 2
      app/views/form/_numeric_output_question.html.erb
  36. 9
      app/views/form/_radio_question.html.erb
  37. 5
      app/views/form/page.html.erb
  38. 4
      config/locales/en.yml
  39. 15
      db/migrate/20230301170338_add_uprn_to_logs.rb
  40. 15
      db/migrate/20230306110210_add_address_to_logs.rb
  41. 14
      db/schema.rb
  42. 21
      spec/features/lettings_log_spec.rb
  43. 4
      spec/fixtures/files/lettings_logs_download.csv
  44. 4
      spec/fixtures/files/lettings_logs_download_codes_only.csv
  45. 4
      spec/fixtures/files/lettings_logs_download_non_support.csv
  46. 26
      spec/helpers/question_view_helper_spec.rb
  47. 73
      spec/models/form/sales/pages/address_spec.rb
  48. 47
      spec/models/form/sales/pages/property_local_authority_spec.rb
  49. 59
      spec/models/form/sales/pages/uprn_confirmation_spec.rb
  50. 33
      spec/models/form/sales/pages/uprn_known_spec.rb
  51. 73
      spec/models/form/sales/pages/uprn_spec.rb
  52. 75
      spec/models/form/sales/questions/address_line1_spec.rb
  53. 49
      spec/models/form/sales/questions/address_line2_spec.rb
  54. 49
      spec/models/form/sales/questions/county_spec.rb
  55. 62
      spec/models/form/sales/questions/postcode_for_full_address_spec.rb
  56. 30
      spec/models/form/sales/questions/property_local_authority_spec.rb
  57. 49
      spec/models/form/sales/questions/town_or_city_spec.rb
  58. 90
      spec/models/form/sales/questions/uprn_confirmation_spec.rb
  59. 59
      spec/models/form/sales/questions/uprn_known_spec.rb
  60. 88
      spec/models/form/sales/questions/uprn_spec.rb
  61. 33
      spec/models/form/sales/subsections/property_information_spec.rb
  62. 165
      spec/models/lettings_log_spec.rb
  63. 87
      spec/models/sales_log_spec.rb
  64. 29
      spec/models/validations/property_validations_spec.rb
  65. 39
      spec/models/validations/sales/property_validations_spec.rb
  66. 9
      spec/services/csv/lettings_log_csv_service_spec.rb
  67. 68
      spec/services/uprn_client_spec.rb
  68. 66
      spec/services/uprn_data_presenter_spec.rb

1
.env.example

@ -5,3 +5,4 @@ GOVUK_NOTIFY_API_KEY=<notify-key-here-if-testing-emails-or-admin-users>
OTP_SECRET_ENCRYPTION_KEY="<Generate this using bundle exec rake secret>" OTP_SECRET_ENCRYPTION_KEY="<Generate this using bundle exec rake secret>"
APP_HOST="http://localhost:3000" APP_HOST="http://localhost:3000"
OS_DATA_KEY=OS_DATA_KEY

2
.env.test

@ -0,0 +1,2 @@
APP_HOST="http://localhost:3000"
OS_DATA_KEY=OS_DATA_KEY

2
.github/workflows/production_pipeline.yml

@ -237,6 +237,7 @@ jobs:
GOVUK_NOTIFY_API_KEY: ${{ secrets.GOVUK_NOTIFY_API_KEY }} GOVUK_NOTIFY_API_KEY: ${{ secrets.GOVUK_NOTIFY_API_KEY }}
APP_HOST: ${{ secrets.APP_HOST }} APP_HOST: ${{ secrets.APP_HOST }}
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
OS_DATA_KEY: ${{ secrets.OS_DATA_KEY }}
IMPORT_PAAS_INSTANCE: ${{ secrets.IMPORT_PAAS_INSTANCE }} IMPORT_PAAS_INSTANCE: ${{ secrets.IMPORT_PAAS_INSTANCE }}
EXPORT_PAAS_INSTANCE: ${{ secrets.EXPORT_PAAS_INSTANCE }} EXPORT_PAAS_INSTANCE: ${{ secrets.EXPORT_PAAS_INSTANCE }}
S3_CONFIG: ${{ secrets.S3_CONFIG }} S3_CONFIG: ${{ secrets.S3_CONFIG }}
@ -249,6 +250,7 @@ jobs:
cf set-env $APP_NAME GOVUK_NOTIFY_API_KEY $GOVUK_NOTIFY_API_KEY cf set-env $APP_NAME GOVUK_NOTIFY_API_KEY $GOVUK_NOTIFY_API_KEY
cf set-env $APP_NAME APP_HOST $APP_HOST cf set-env $APP_NAME APP_HOST $APP_HOST
cf set-env $APP_NAME RAILS_MASTER_KEY $RAILS_MASTER_KEY cf set-env $APP_NAME RAILS_MASTER_KEY $RAILS_MASTER_KEY
cf set-env $APP_NAME OS_DATA_KEY $OS_DATA_KEY
cf set-env $APP_NAME IMPORT_PAAS_INSTANCE $IMPORT_PAAS_INSTANCE cf set-env $APP_NAME IMPORT_PAAS_INSTANCE $IMPORT_PAAS_INSTANCE
cf set-env $APP_NAME EXPORT_PAAS_INSTANCE $EXPORT_PAAS_INSTANCE cf set-env $APP_NAME EXPORT_PAAS_INSTANCE $EXPORT_PAAS_INSTANCE
cf set-env $APP_NAME S3_CONFIG $S3_CONFIG cf set-env $APP_NAME S3_CONFIG $S3_CONFIG

2
.github/workflows/review_pipeline.yml

@ -110,6 +110,7 @@ jobs:
API_KEY: ${{ secrets.API_KEY }} API_KEY: ${{ secrets.API_KEY }}
GOVUK_NOTIFY_API_KEY: ${{ secrets.GOVUK_NOTIFY_API_KEY }} GOVUK_NOTIFY_API_KEY: ${{ secrets.GOVUK_NOTIFY_API_KEY }}
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
OS_DATA_KEY: ${{ secrets.OS_DATA_KEY }}
IMPORT_PAAS_INSTANCE: ${{ secrets.IMPORT_PAAS_INSTANCE }} IMPORT_PAAS_INSTANCE: ${{ secrets.IMPORT_PAAS_INSTANCE }}
EXPORT_PAAS_INSTANCE: ${{ secrets.EXPORT_PAAS_INSTANCE }} EXPORT_PAAS_INSTANCE: ${{ secrets.EXPORT_PAAS_INSTANCE }}
S3_CONFIG: ${{ secrets.S3_CONFIG }} S3_CONFIG: ${{ secrets.S3_CONFIG }}
@ -120,6 +121,7 @@ jobs:
cf set-env $APP_NAME API_KEY $API_KEY cf set-env $APP_NAME API_KEY $API_KEY
cf set-env $APP_NAME GOVUK_NOTIFY_API_KEY $GOVUK_NOTIFY_API_KEY cf set-env $APP_NAME GOVUK_NOTIFY_API_KEY $GOVUK_NOTIFY_API_KEY
cf set-env $APP_NAME RAILS_MASTER_KEY $RAILS_MASTER_KEY cf set-env $APP_NAME RAILS_MASTER_KEY $RAILS_MASTER_KEY
cf set-env $APP_NAME OS_DATA_KEY $OS_DATA_KEY
cf set-env $APP_NAME IMPORT_PAAS_INSTANCE $IMPORT_PAAS_INSTANCE cf set-env $APP_NAME IMPORT_PAAS_INSTANCE $IMPORT_PAAS_INSTANCE
cf set-env $APP_NAME EXPORT_PAAS_INSTANCE "dluhc-core-review-export-bucket" cf set-env $APP_NAME EXPORT_PAAS_INSTANCE "dluhc-core-review-export-bucket"
cf set-env $APP_NAME S3_CONFIG $S3_CONFIG cf set-env $APP_NAME S3_CONFIG $S3_CONFIG

2
.github/workflows/staging_pipeline.yml

@ -208,6 +208,7 @@ jobs:
GOVUK_NOTIFY_API_KEY: ${{ secrets.GOVUK_NOTIFY_API_KEY }} GOVUK_NOTIFY_API_KEY: ${{ secrets.GOVUK_NOTIFY_API_KEY }}
APP_HOST: ${{ secrets.APP_HOST }} APP_HOST: ${{ secrets.APP_HOST }}
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
OS_DATA_KEY: ${{ secrets.OS_DATA_KEY }}
IMPORT_PAAS_INSTANCE: ${{ secrets.IMPORT_PAAS_INSTANCE }} IMPORT_PAAS_INSTANCE: ${{ secrets.IMPORT_PAAS_INSTANCE }}
EXPORT_PAAS_INSTANCE: ${{ secrets.EXPORT_PAAS_INSTANCE }} EXPORT_PAAS_INSTANCE: ${{ secrets.EXPORT_PAAS_INSTANCE }}
S3_CONFIG: ${{ secrets.S3_CONFIG }} S3_CONFIG: ${{ secrets.S3_CONFIG }}
@ -222,6 +223,7 @@ jobs:
cf set-env $APP_NAME GOVUK_NOTIFY_API_KEY $GOVUK_NOTIFY_API_KEY cf set-env $APP_NAME GOVUK_NOTIFY_API_KEY $GOVUK_NOTIFY_API_KEY
cf set-env $APP_NAME APP_HOST $APP_HOST cf set-env $APP_NAME APP_HOST $APP_HOST
cf set-env $APP_NAME RAILS_MASTER_KEY $RAILS_MASTER_KEY cf set-env $APP_NAME RAILS_MASTER_KEY $RAILS_MASTER_KEY
cf set-env $APP_NAME OS_DATA_KEY $OS_DATA_KEY
cf set-env $APP_NAME IMPORT_PAAS_INSTANCE $IMPORT_PAAS_INSTANCE cf set-env $APP_NAME IMPORT_PAAS_INSTANCE $IMPORT_PAAS_INSTANCE
cf set-env $APP_NAME EXPORT_PAAS_INSTANCE $EXPORT_PAAS_INSTANCE cf set-env $APP_NAME EXPORT_PAAS_INSTANCE $EXPORT_PAAS_INSTANCE
cf set-env $APP_NAME S3_CONFIG $S3_CONFIG cf set-env $APP_NAME S3_CONFIG $S3_CONFIG

2
app/controllers/form_controller.rb

@ -160,7 +160,7 @@ private
end end
def question_is_required?(question) def question_is_required?(question)
@log.class::OPTIONAL_FIELDS.exclude?(question.id) && required_questions.include?(question.id) @log.optional_fields.exclude?(question.id) && required_questions.include?(question.id)
end end
def required_questions def required_questions

6
app/helpers/question_view_helper.rb

@ -8,14 +8,16 @@ module QuestionViewHelper
def legend(question, page_header, conditional) def legend(question, page_header, conditional)
{ {
text: [question.question_number_string(conditional:), question.header.html_safe].compact.join(" - "), text: [question.question_number_string(conditional:), question.header.html_safe].compact.join(" - "),
size: label_size(page_header, conditional), size: label_size(page_header, conditional, question),
tag: label_tag(page_header, conditional), tag: label_tag(page_header, conditional),
} }
end end
private private
def label_size(page_header, conditional) def label_size(page_header, conditional, question)
return if question.plain_label.present?
page_header.blank? && !conditional ? "l" : "m" page_header.blank? && !conditional ? "l" : "m"
end end

9
app/models/derived_variables/sales_log_variables.rb

@ -23,6 +23,15 @@ module DerivedVariables::SalesLogVariables
self.totadult = total_adult + total_elder self.totadult = total_adult + total_elder
self.hhmemb = number_of_household_members self.hhmemb = number_of_household_members
self.hhtype = household_type self.hhtype = household_type
if uprn_known&.zero?
self.uprn = nil
end
if uprn_confirmed&.zero?
self.uprn = nil
self.uprn_known = 0
end
end end
private private

2
app/models/form.rb

@ -160,7 +160,7 @@ class Form
def invalidated_page_questions(log, current_user = nil) def invalidated_page_questions(log, current_user = nil)
# we're already treating these fields as a special case and reset their values upon saving a log # we're already treating these fields as a special case and reset their values upon saving a log
callback_questions = %w[postcode_known la ppcodenk previous_la_known prevloc postcode_full ppostcode_full location_id] callback_questions = %w[postcode_known la ppcodenk previous_la_known prevloc postcode_full ppostcode_full location_id address_line1 address_line2 town_or_city county]
questions.reject { |q| q.page.routed_to?(log, current_user) || q.derived? || callback_questions.include?(q.id) } || [] questions.reject { |q| q.page.routed_to?(log, current_user) || q.derived? || callback_questions.include?(q.id) } || []
end end

6
app/models/form/page.rb

@ -1,6 +1,7 @@
class Form::Page class Form::Page
attr_accessor :id, :header, :header_partial, :description, :questions, :depends_on, :title_text, attr_accessor :id, :header, :header_partial, :description, :questions, :depends_on, :title_text,
:informative_text, :subsection, :hide_subsection_label, :next_unresolved_page_id :informative_text, :subsection, :hide_subsection_label, :next_unresolved_page_id,
:skip_text
def initialize(id, hsh, subsection) def initialize(id, hsh, subsection)
@id = id @id = id
@ -15,6 +16,7 @@ class Form::Page
@informative_text = hsh["informative_text"] @informative_text = hsh["informative_text"]
@hide_subsection_label = hsh["hide_subsection_label"] @hide_subsection_label = hsh["hide_subsection_label"]
@next_unresolved_page_id = hsh["next_unresolved_page_id"] @next_unresolved_page_id = hsh["next_unresolved_page_id"]
@skip_text = hsh["skip_text"]
end end
end end
@ -36,6 +38,8 @@ class Form::Page
questions.all? { |question| question.type == "interruption_screen" } questions.all? { |question| question.type == "interruption_screen" }
end end
def skip_href(log = nil); end
private private
def conditional_question_ids def conditional_question_ids

5
app/models/form/question.rb

@ -4,7 +4,7 @@ class Form::Question
:conditional_for, :readonly, :answer_options, :page, :check_answer_label, :conditional_for, :readonly, :answer_options, :page, :check_answer_label,
:inferred_answers, :hidden_in_check_answers, :inferred_check_answers_value, :inferred_answers, :hidden_in_check_answers, :inferred_check_answers_value,
:guidance_partial, :prefix, :suffix, :requires_js, :fields_added, :derived, :guidance_partial, :prefix, :suffix, :requires_js, :fields_added, :derived,
:check_answers_card_number, :unresolved_hint_text, :question_number :check_answers_card_number, :unresolved_hint_text, :question_number, :plain_label
module GuidancePosition module GuidancePosition
TOP = 1 TOP = 1
@ -41,6 +41,7 @@ class Form::Question
@check_answers_card_number = hsh["check_answers_card_number"] || 0 @check_answers_card_number = hsh["check_answers_card_number"] || 0
@unresolved_hint_text = hsh["unresolved_hint_text"] @unresolved_hint_text = hsh["unresolved_hint_text"]
@question_number = hsh["question_number"] @question_number = hsh["question_number"]
@plain_label = hsh["plain_label"]
end end
end end
@ -57,6 +58,8 @@ class Form::Question
inferred_answer_value(log) || answer_label inferred_answer_value(log) || answer_label
end end
def notification_banner(_log = nil); end
def get_inferred_answers(log) def get_inferred_answers(log)
return [] unless inferred_answers return [] unless inferred_answers

23
app/models/form/sales/pages/address.rb

@ -0,0 +1,23 @@
class Form::Sales::Pages::Address < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "address"
@header = "What is the property's address?"
end
def questions
@questions ||= [
Form::Sales::Questions::AddressLine1.new(nil, nil, self),
Form::Sales::Questions::AddressLine2.new(nil, nil, self),
Form::Sales::Questions::TownOrCity.new(nil, nil, self),
Form::Sales::Questions::County.new(nil, nil, self),
Form::Sales::Questions::PostcodeForFullAddress.new(nil, nil, self),
]
end
def routed_to?(log, _current_user = nil)
return false if log.uprn_known.nil?
log.uprn_confirmed != 1 || log.uprn_known.zero?
end
end

16
app/models/form/sales/pages/property_local_authority.rb

@ -9,8 +9,20 @@ class Form::Sales::Pages::PropertyLocalAuthority < ::Form::Page
def questions def questions
@questions ||= [ @questions ||= [
Form::Sales::Questions::PropertyLocalAuthorityKnown.new(nil, nil, self), la_known_question,
Form::Sales::Questions::PropertyLocalAuthority.new(nil, nil, self), Form::Sales::Questions::PropertyLocalAuthority.new(nil, nil, self),
] ].compact
end
def routed_to?(log, _current_user = nil)
return false if log.uprn_known.nil? && form.start_date.year >= 2023
true
end
def la_known_question
if form.start_date.year < 2023
Form::Sales::Questions::PropertyLocalAuthorityKnown.new(nil, nil, self)
end
end end
end end

26
app/models/form/sales/pages/uprn.rb

@ -0,0 +1,26 @@
class Form::Sales::Pages::Uprn < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "uprn"
end
def questions
@questions ||= [
Form::Sales::Questions::Uprn.new(nil, nil, self),
]
end
def routed_to?(log, _current_user = nil)
log.uprn_known == 1
end
def skip_text
"Enter address instead"
end
def skip_href(log = nil)
return unless log
"/#{log.model_name.param_key.dasherize}s/#{log.id}/address"
end
end

17
app/models/form/sales/pages/uprn_confirmation.rb

@ -0,0 +1,17 @@
class Form::Sales::Pages::UprnConfirmation < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "uprn_confirmation"
@header = "We found an address that might be this property"
end
def questions
@questions ||= [
Form::Sales::Questions::UprnConfirmation.new(nil, nil, self),
]
end
def routed_to?(log, _current_user = nil)
log.uprn.present? && log.uprn_known == 1
end
end

12
app/models/form/sales/pages/uprn_known.rb

@ -0,0 +1,12 @@
class Form::Sales::Pages::UprnKnown < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "uprn_known"
end
def questions
@questions ||= [
Form::Sales::Questions::UprnKnown.new(nil, nil, self),
]
end
end

37
app/models/form/sales/questions/address_line1.rb

@ -0,0 +1,37 @@
class Form::Sales::Questions::AddressLine1 < ::Form::Question
def initialize(id, hsh, page)
super
@id = "address_line1"
@check_answer_label = "Address"
@header = "Address line 1"
@type = "text"
@plain_label = true
end
def hidden_in_check_answers?(log, _current_user = nil)
return true if log.uprn_known.nil?
return false if log.uprn_known&.zero?
return true if log.uprn_confirmed.nil? && log.uprn.present?
return true if log.uprn_known == 1 && log.uprn.blank?
log.uprn_confirmed == 1
end
def answer_label(log, _current_user = nil)
[
log.address_line1,
log.address_line2,
log.postcode_full,
log.town_or_city,
log.county,
].select(&:present?).join("\n")
end
def get_extra_check_answer_value(log)
return unless log.is_la_inferred?
la = LocalAuthority.find_by(code: log.la)&.name
la.presence
end
end

13
app/models/form/sales/questions/address_line2.rb

@ -0,0 +1,13 @@
class Form::Sales::Questions::AddressLine2 < ::Form::Question
def initialize(id, hsh, page)
super
@id = "address_line2"
@header = "Address line 2 (optional)"
@type = "text"
@plain_label = true
end
def hidden_in_check_answers?(_log = nil, _current_user = nil)
true
end
end

13
app/models/form/sales/questions/county.rb

@ -0,0 +1,13 @@
class Form::Sales::Questions::County < ::Form::Question
def initialize(id, hsh, page)
super
@id = "county"
@header = "County (optional)"
@type = "text"
@plain_label = true
end
def hidden_in_check_answers?(_log = nil, _current_user = nil)
true
end
end

25
app/models/form/sales/questions/postcode_for_full_address.rb

@ -0,0 +1,25 @@
class Form::Sales::Questions::PostcodeForFullAddress < ::Form::Question
def initialize(id, hsh, page)
super
@id = "postcode_full"
@header = "Postcode"
@type = "text"
@width = 5
@inferred_check_answers_value = [{
"condition" => {
"pcodenk" => 1,
},
"value" => "Not known",
}]
@inferred_answers = {
"la" => {
"is_la_inferred" => true,
},
}
@plain_label = true
end
def hidden_in_check_answers?(_log = nil, _current_user = nil)
true
end
end

4
app/models/form/sales/questions/property_local_authority.rb

@ -11,4 +11,8 @@ class Form::Sales::Questions::PropertyLocalAuthority < ::Form::Question
def answer_options def answer_options
{ "" => "Select an option" }.merge(LocalAuthority.active(form.start_date).england.map { |la| [la.code, la.name] }.to_h) { "" => "Select an option" }.merge(LocalAuthority.active(form.start_date).england.map { |la| [la.code, la.name] }.to_h)
end end
def hidden_in_check_answers?(log, _current_user = nil)
log.saledate && log.saledate.year >= 2023 && log.is_la_inferred?
end
end end

13
app/models/form/sales/questions/town_or_city.rb

@ -0,0 +1,13 @@
class Form::Sales::Questions::TownOrCity < ::Form::Question
def initialize(id, hsh, page)
super
@id = "town_or_city"
@header = "Town or city"
@type = "text"
@plain_label = true
end
def hidden_in_check_answers?(_log = nil, _current_user = nil)
true
end
end

34
app/models/form/sales/questions/uprn.rb

@ -0,0 +1,34 @@
class Form::Sales::Questions::Uprn < ::Form::Question
def initialize(id, hsh, page)
super
@id = "uprn"
@check_answer_label = "UPRN"
@header = "What is the property's UPRN"
@type = "text"
@hint_text = "The Unique Property Reference Number (UPRN) is a unique number system created by Ordnance Survey and used by housing providers and sectors UK-wide. For example 10010457355."
@width = 10
end
def unanswered_error_message
I18n.t("validations.property.uprn.invalid")
end
def get_extra_check_answer_value(log)
value = [
log.address_line1,
log.address_line2,
log.town_or_city,
log.county,
log.postcode_full,
(LocalAuthority.find_by(code: log.la)&.name if log.la.present?),
].select(&:present?)
return unless value.any?
"\n\n#{value.join("\n")}"
end
def hidden_in_check_answers?(log, _current_user = nil)
log.uprn_known != 1
end
end

34
app/models/form/sales/questions/uprn_confirmation.rb

@ -0,0 +1,34 @@
class Form::Sales::Questions::UprnConfirmation < ::Form::Question
def initialize(id, hsh, page)
super
@id = "uprn_confirmed"
@header = "Is this the property address?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@check_answer_label = "Is this the right address?"
end
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"0" => { "value" => "No, I want to enter the address manually" },
}.freeze
def notification_banner(log = nil)
return unless log&.uprn
{
title: "UPRN: #{log.uprn}",
heading: [
log.address_line1,
log.address_line2,
log.postcode_full,
log.town_or_city,
log.county,
].select(&:present?).join("\n"),
}
end
def hidden_in_check_answers?(log, _current_user = nil)
log.uprn_known != 1 || log.uprn_confirmed.present?
end
end

21
app/models/form/sales/questions/uprn_known.rb

@ -0,0 +1,21 @@
class Form::Sales::Questions::UprnKnown < ::Form::Question
def initialize(id, hsh, page)
super
@id = "uprn_known"
@check_answer_label = "UPRN known?"
@header = "Do you know the property UPRN?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@hint_text = "The Unique Property Reference Number (UPRN) is a unique number system created by Ordnance Survey and used by housing providers and sectors UK-wide. For example 10010457355.<br><br>
You can continue without the UPRN, but it means we will need you to enter the address of the property."
end
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"0" => { "value" => "No" },
}.freeze
def unanswered_error_message
I18n.t("validations.property.uprn_known.invalid")
end
end

25
app/models/form/sales/subsections/property_information.rb

@ -8,15 +8,36 @@ class Form::Sales::Subsections::PropertyInformation < ::Form::Subsection
def pages def pages
@pages ||= [ @pages ||= [
uprn_questions,
Form::Sales::Pages::PropertyNumberOfBedrooms.new(nil, nil, self), Form::Sales::Pages::PropertyNumberOfBedrooms.new(nil, nil, self),
Form::Sales::Pages::AboutPriceValueCheck.new("about_price_bedrooms_value_check", nil, self), Form::Sales::Pages::AboutPriceValueCheck.new("about_price_bedrooms_value_check", nil, self),
Form::Sales::Pages::PropertyUnitType.new(nil, nil, self), Form::Sales::Pages::PropertyUnitType.new(nil, nil, self),
Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_property_type_value_check", nil, self), Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_property_type_value_check", nil, self),
Form::Sales::Pages::PropertyBuildingType.new(nil, nil, self), Form::Sales::Pages::PropertyBuildingType.new(nil, nil, self),
Form::Sales::Pages::Postcode.new(nil, nil, self), postcode_and_la_questions,
Form::Sales::Pages::PropertyLocalAuthority.new(nil, nil, self),
Form::Sales::Pages::AboutPriceValueCheck.new("about_price_la_value_check", nil, self), Form::Sales::Pages::AboutPriceValueCheck.new("about_price_la_value_check", nil, self),
Form::Sales::Pages::PropertyWheelchairAccessible.new(nil, nil, self), Form::Sales::Pages::PropertyWheelchairAccessible.new(nil, nil, self),
].flatten.compact
end
def uprn_questions
if form.start_date.year >= 2023
[
Form::Sales::Pages::UprnKnown.new(nil, nil, self),
Form::Sales::Pages::Uprn.new(nil, nil, self),
Form::Sales::Pages::UprnConfirmation.new(nil, nil, self),
Form::Sales::Pages::Address.new(nil, nil, self),
Form::Sales::Pages::PropertyLocalAuthority.new(nil, nil, self),
]
end
end
def postcode_and_la_questions
if form.start_date.year < 2023
[
Form::Sales::Pages::Postcode.new(nil, nil, self),
Form::Sales::Pages::PropertyLocalAuthority.new(nil, nil, self),
] ]
end end
end
end end

2
app/models/lettings_log.rb

@ -562,6 +562,8 @@ private
not_required << "tshortfall" if tshortfall_unknown? not_required << "tshortfall" if tshortfall_unknown?
not_required << "tenancylength" if tenancylength_optional? not_required << "tenancylength" if tenancylength_optional?
not_required |= %w[address_line2 county postcode_full] if startdate && startdate.year >= 2023
not_required not_required
end end

18
app/models/log.rb

@ -31,6 +31,24 @@ class Log < ApplicationRecord
} }
scope :created_by, ->(user) { where(created_by: user) } scope :created_by, ->(user) { where(created_by: user) }
def process_uprn_change!
if uprn.present?
service = UprnClient.new(uprn)
service.call
return errors.add(:uprn, service.error) if service.error.present?
presenter = UprnDataPresenter.new(service.result)
self.uprn_confirmed = nil
self.address_line1 = presenter.address_line1
self.address_line2 = presenter.address_line2
self.town_or_city = presenter.town_or_city
self.postcode_full = presenter.postcode
process_postcode_changes!
end
end
def collection_start_year def collection_start_year
return @start_year if @start_year return @start_year if @start_year

7
app/models/sales_log.rb

@ -31,6 +31,7 @@ class SalesLog < Log
before_validation :reset_location_fields!, unless: :postcode_known? before_validation :reset_location_fields!, unless: :postcode_known?
before_validation :reset_previous_location_fields!, unless: :previous_postcode_known? before_validation :reset_previous_location_fields!, unless: :previous_postcode_known?
before_validation :set_derived_fields! before_validation :set_derived_fields!
after_validation :process_uprn_change!, if: :should_process_uprn_change?
scope :filter_by_year, ->(year) { where(saledate: Time.zone.local(year.to_i, 4, 1)...Time.zone.local(year.to_i + 1, 4, 1)) } scope :filter_by_year, ->(year) { where(saledate: Time.zone.local(year.to_i, 4, 1)...Time.zone.local(year.to_i + 1, 4, 1)) }
scope :filter_by_purchaser_code, ->(purchid) { where("purchid ILIKE ?", "%#{purchid}%") } scope :filter_by_purchaser_code, ->(purchid) { where("purchid ILIKE ?", "%#{purchid}%") }
@ -78,6 +79,8 @@ class SalesLog < Log
not_required = [] not_required = []
not_required << "proplen" if proplen_optional? not_required << "proplen" if proplen_optional?
not_required |= %w[address_line2 county postcode_full] if saledate && saledate.year >= 2023
not_required not_required
end end
@ -309,4 +312,8 @@ class SalesLog < Log
field_value = public_send(field_name) field_value = public_send(field_name)
format_as_currency(field_value) format_as_currency(field_value)
end end
def should_process_uprn_change?
uprn_changed? && saledate && saledate.year >= 2023
end
end end

8
app/models/validations/property_validations.rb

@ -75,4 +75,12 @@ module Validations::PropertyValidations
record.errors.add :beds, I18n.t("validations.property.beds.over_max") record.errors.add :beds, I18n.t("validations.property.beds.over_max")
end end
end end
def validate_uprn(record)
return unless record.uprn
return if record.uprn.match?(/^[0-9]{1,12}$/)
record.errors.add :uprn, I18n.t("validations.property.uprn.invalid")
end
end end

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

@ -16,4 +16,12 @@ module Validations::Sales::PropertyValidations
record.errors.add :beds, I18n.t("validations.property.beds.bedsits_have_max_one_bedroom") record.errors.add :beds, I18n.t("validations.property.beds.bedsits_have_max_one_bedroom")
end end
end end
def validate_uprn(record)
return unless record.uprn
return if record.uprn.match?(/^[0-9]{1,12}$/)
record.errors.add :uprn, I18n.t("validations.property.uprn.invalid")
end
end end

50
app/services/uprn_client.rb

@ -0,0 +1,50 @@
require "net/http"
class UprnClient
attr_reader :uprn
attr_accessor :error
ADDRESS = "api.os.uk".freeze
PATH = "/search/places/v1/uprn".freeze
def initialize(uprn)
@uprn = uprn
end
def call
unless response.is_a?(Net::HTTPSuccess) && result.present?
@error = "UPRN is not recognised. Check the number, or enter the address"
end
rescue JSON::ParserError
@error = "UPRN is not recognised. Check the number, or enter the address"
end
def result
@result ||= JSON.parse(response.body).dig("results", 0, "DPA")
end
private
def http_client
client = Net::HTTP.new(ADDRESS, 443)
client.use_ssl = true
client.verify_mode = OpenSSL::SSL::VERIFY_PEER
client.max_retries = 3
client.read_timeout = 10 # seconds
client
end
def endpoint_uri
uri = URI(PATH)
params = {
uprn:,
key: ENV["OS_DATA_KEY"],
}
uri.query = URI.encode_www_form(params)
uri.to_s
end
def response
@response ||= http_client.request_get(endpoint_uri)
end
end

41
app/services/uprn_data_presenter.rb

@ -0,0 +1,41 @@
require "net/http"
class UprnDataPresenter
attr_reader :data
def initialize(data)
@data = data
end
def postcode
data["POSTCODE"]
end
def address_line1
data.values_at(
"PO_BOX_NUMBER",
"ORGANISATION_NAME",
"DEPARTMENT_NAME",
"SUB_BUILDING_NAME",
"BUILDING_NAME",
"BUILDING_NUMBER",
"DEPENDENT_THOROUGHFARE_NAME",
"THOROUGHFARE_NAME",
).compact
.join(", ")
.titleize
end
def address_line2
data.values_at(
"DOUBLE_DEPENDENT_LOCALITY", "DEPENDENT_LOCALITY"
).compact
.join(", ")
.titleize
.presence
end
def town_or_city
data["POST_TOWN"].titleize
end
end

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

@ -3,12 +3,19 @@
<% summary_list.row do |row| %> <% summary_list.row do |row| %>
<% row.key { get_question_label(question) } %> <% row.key { get_question_label(question) } %>
<% row.value do %> <% row.value do %>
<span class="govuk-!-margin-right-4"><%= get_answer_label(question, @log) %></span> <%= simple_format(
get_answer_label(question, @log),
wrapper_tag: "span",
class: "govuk-!-margin-right-4",
) %>
<% extra_value = question.get_extra_check_answer_value(@log) %> <% extra_value = question.get_extra_check_answer_value(@log) %>
<% if extra_value %> <% if extra_value && question.answer_label(lettings_log, current_user).present? %>
<span class="govuk-!-font-weight-regular app-!-colour-muted"><%= extra_value %></span> <%= simple_format(
extra_value,
wrapper_tag: "span",
class: "govuk-!-font-weight-regular app-!-colour-muted",
) %>
<% end %> <% end %>
<br>
<% question.get_inferred_answers(@log).each do |inferred_answer| %> <% question.get_inferred_answers(@log).each do |inferred_answer| %>
<span class="govuk-!-font-weight-regular app-!-colour-muted"><%= inferred_answer %></span> <span class="govuk-!-font-weight-regular app-!-colour-muted"><%= inferred_answer %></span>
<% end %> <% end %>

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

@ -1,7 +1,7 @@
<%= render partial: "form/guidance/#{question.guidance_partial}" if question.top_guidance? %> <%= render partial: "form/guidance/#{question.guidance_partial}" if question.top_guidance? %>
<div class="govuk-form-group"> <div class="govuk-form-group">
<label class="govuk-label govuk-label--<%= label_size(page_header, conditional) %>" for="lettings-log-<%= question.id %>-field"> <label class="govuk-label govuk-label--<%= label_size(page_header, conditional, question) %>" for="lettings-log-<%= question.id %>-field">
<%= question.header.html_safe %> <%= question.header.html_safe %>
</label> </label>
<div class="govuk-hint"> <div class="govuk-hint">

9
app/views/form/_radio_question.html.erb

@ -1,4 +1,13 @@
<%= render partial: "form/guidance/#{question.guidance_partial}" if question.top_guidance? %> <%= render partial: "form/guidance/#{question.guidance_partial}" if question.top_guidance? %>
<% banner = question.notification_banner(@log) %>
<% if banner %>
<%= govuk_notification_banner(
title_text: banner[:title],
title_heading_level: 3,
) do
simple_format(banner[:heading])
end %>
<% end %>
<%= f.govuk_radio_buttons_fieldset question.id.to_sym, <%= f.govuk_radio_buttons_fieldset question.id.to_sym,
caption: caption(caption_text, page_header, conditional), caption: caption(caption_text, page_header, conditional),

5
app/views/form/page.html.erb

@ -63,7 +63,10 @@
<div class="govuk-button-group"> <div class="govuk-button-group">
<% if !@page.interruption_screen? && if request.query_parameters["referrer"] != "check_answers" %> <% if !@page.interruption_screen? && if request.query_parameters["referrer"] != "check_answers" %>
<%= f.govuk_submit "Save and continue" %> <%= f.govuk_submit "Save and continue" %>
<%= govuk_link_to "Skip for now", send(@log.form.next_page_redirect_path(@page, @log, current_user), @log) %> <%= govuk_link_to(
(@page.skip_text || "Skip for now"),
(@page.skip_href(@log) || send(@log.form.next_page_redirect_path(@page, @log, current_user), @log)),
) %>
<% else %> <% else %>
<%= f.govuk_submit "Save changes" %> <%= f.govuk_submit "Save changes" %>
<%= govuk_link_to "Cancel", send(@log.form.cancel_path(@page, @log), @log) %> <%= govuk_link_to "Cancel", send(@log.form.cancel_path(@page, @log), @log) %>

4
config/locales/en.yml

@ -187,6 +187,10 @@ en:
supported_housing_mismatch: Lettings type must be a supported housing type because you selected supported housing 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:
uprn:
invalid: "UPRN must be 12 digits or less"
uprn_known:
invalid: "You must answer UPRN known?"
mrcdate: mrcdate:
before_tenancy_start: "Enter a major repairs date that is before the tenancy start date" before_tenancy_start: "Enter a major repairs date that is before the tenancy start date"
not_first_let: "Major repairs date must not be completed if the tenancy is a first let" not_first_let: "Major repairs date must not be completed if the tenancy is a first let"

15
db/migrate/20230301170338_add_uprn_to_logs.rb

@ -0,0 +1,15 @@
class AddUprnToLogs < ActiveRecord::Migration[7.0]
def change
change_table :sales_logs, bulk: true do |t|
t.column :uprn, :string
t.column :uprn_known, :integer
t.column :uprn_confirmed, :integer
end
change_table :lettings_logs, bulk: true do |t|
t.column :uprn, :string
t.column :uprn_known, :integer
t.column :uprn_confirmed, :integer
end
end
end

15
db/migrate/20230306110210_add_address_to_logs.rb

@ -0,0 +1,15 @@
class AddAddressToLogs < ActiveRecord::Migration[7.0]
change_table :sales_logs, bulk: true do |t|
t.column :address_line1, :string
t.column :address_line2, :string
t.column :town_or_city, :string
t.column :county, :string
end
change_table :lettings_logs, bulk: true do |t|
t.column :address_line1, :string
t.column :address_line2, :string
t.column :town_or_city, :string
t.column :county, :string
end
end

14
db/schema.rb

@ -279,6 +279,13 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_08_101826) do
t.boolean "unresolved" t.boolean "unresolved"
t.bigint "updated_by_id" t.bigint "updated_by_id"
t.bigint "bulk_upload_id" t.bigint "bulk_upload_id"
t.string "uprn"
t.integer "uprn_known"
t.integer "uprn_confirmed"
t.string "address_line1"
t.string "address_line2"
t.string "town_or_city"
t.string "county"
t.index ["bulk_upload_id"], name: "index_lettings_logs_on_bulk_upload_id" t.index ["bulk_upload_id"], name: "index_lettings_logs_on_bulk_upload_id"
t.index ["created_by_id"], name: "index_lettings_logs_on_created_by_id" t.index ["created_by_id"], name: "index_lettings_logs_on_created_by_id"
t.index ["location_id"], name: "index_lettings_logs_on_location_id" t.index ["location_id"], name: "index_lettings_logs_on_location_id"
@ -545,6 +552,13 @@ ActiveRecord::Schema[7.0].define(version: 2023_03_08_101826) do
t.integer "buy2living" t.integer "buy2living"
t.integer "prevtenbuy2" t.integer "prevtenbuy2"
t.integer "nationalbuy2" t.integer "nationalbuy2"
t.string "uprn"
t.integer "uprn_known"
t.integer "uprn_confirmed"
t.string "address_line1"
t.string "address_line2"
t.string "town_or_city"
t.string "county"
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 ["old_id"], name: "index_sales_logs_on_old_id", unique: true t.index ["old_id"], name: "index_sales_logs_on_old_id", unique: true

21
spec/features/lettings_log_spec.rb

@ -89,7 +89,7 @@ RSpec.describe "Lettings Log Features" do
click_button("Save and continue") click_button("Save and continue")
log_id = page.current_path.scan(/\d/).join log_id = page.current_path.scan(/\d/).join
visit("lettings-logs/#{log_id}/setup/check-answers") visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).to have_content("Stock owner User org") expect(page).to have_content("Stock owner User org", normalize_ws: true)
expect(page).to have_content("You have answered 2 of 8 questions") expect(page).to have_content("You have answered 2 of 8 questions")
end end
end end
@ -125,7 +125,7 @@ RSpec.describe "Lettings Log Features" do
select(managing_org.name, from: "lettings-log-managing-organisation-id-field") select(managing_org.name, from: "lettings-log-managing-organisation-id-field")
click_button("Save and continue") click_button("Save and continue")
visit("lettings-logs/#{log_id}/setup/check-answers") visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).to have_content("Managing agent Managing org") expect(page).to have_content("Managing agent Managing org", normalize_ws: true)
expect(support_user.organisation.managing_agents).to eq([org_rel.child_organisation]) expect(support_user.organisation.managing_agents).to eq([org_rel.child_organisation])
end end
end end
@ -164,7 +164,7 @@ RSpec.describe "Lettings Log Features" do
select(managing_org1.name, from: "lettings-log-managing-organisation-id-field") select(managing_org1.name, from: "lettings-log-managing-organisation-id-field")
click_button("Save and continue") click_button("Save and continue")
visit("lettings-logs/#{log_id}/setup/check-answers") visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).to have_content("Managing agent Managing org 1") expect(page).to have_content("Managing agent Managing org 1", normalize_ws: true)
end end
context "and the owning organisation has 2 or more managing agents" do context "and the owning organisation has 2 or more managing agents" do
@ -183,10 +183,10 @@ RSpec.describe "Lettings Log Features" do
select(managing_org1.name, from: "lettings-log-managing-organisation-id-field") select(managing_org1.name, from: "lettings-log-managing-organisation-id-field")
click_button("Save and continue") click_button("Save and continue")
visit("lettings-logs/#{log_id}/setup/check-answers") visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).to have_content("Managing agent Managing org 1") expect(page).to have_content("Managing agent Managing org 1", normalize_ws: true)
org_rel1.destroy! org_rel1.destroy!
visit("lettings-logs/#{log_id}/setup/check-answers") visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).to have_content("Managing agent Managing org 1") expect(page).to have_content("Managing agent Managing org 1", normalize_ws: true)
expect(support_user.organisation.managing_agents).to eq([org_rel2.child_organisation]) expect(support_user.organisation.managing_agents).to eq([org_rel2.child_organisation])
end end
end end
@ -237,7 +237,7 @@ RSpec.describe "Lettings Log Features" do
log_id = page.current_path.scan(/\d/).join log_id = page.current_path.scan(/\d/).join
expect(page).to have_current_path("/lettings-logs/#{log_id}/stock-owner") expect(page).to have_current_path("/lettings-logs/#{log_id}/stock-owner")
visit("lettings-logs/#{log_id}/setup/check-answers") visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).to have_content("Stock owner User org") expect(page).to have_content("Stock owner User org", normalize_ws: true)
end end
context "and there are 3 or more potential stock owners" do context "and there are 3 or more potential stock owners" do
@ -254,10 +254,10 @@ RSpec.describe "Lettings Log Features" do
select(owning_org1.name, from: "lettings-log-owning-organisation-id-field") select(owning_org1.name, from: "lettings-log-owning-organisation-id-field")
click_button("Save and continue") click_button("Save and continue")
visit("lettings-logs/#{log_id}/setup/check-answers") visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).to have_content("Stock owner Owning org 1") expect(page).to have_content("Stock owner Owning org 1", normalize_ws: true)
org_rel1.destroy! org_rel1.destroy!
visit("lettings-logs/#{log_id}/setup/check-answers") visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).to have_content("Stock owner Owning org 1") expect(page).to have_content("Stock owner Owning org 1", normalize_ws: true)
expect(user.organisation.stock_owners).to eq([org_rel2.parent_organisation]) expect(user.organisation.stock_owners).to eq([org_rel2.parent_organisation])
end end
end end
@ -283,7 +283,8 @@ RSpec.describe "Lettings Log Features" do
select(user.organisation.name, from: "lettings-log-managing-organisation-id-field") select(user.organisation.name, from: "lettings-log-managing-organisation-id-field")
click_button("Save and continue") click_button("Save and continue")
visit("lettings-logs/#{log_id}/setup/check-answers") visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).to have_content("Managing agent User org")
expect(page).to have_content("Managing agent User org", normalize_ws: true)
expect(user.organisation.stock_owners).to eq([org_rel1.parent_organisation, org_rel2.parent_organisation]) expect(user.organisation.stock_owners).to eq([org_rel1.parent_organisation, org_rel2.parent_organisation])
end end
end end
@ -328,7 +329,7 @@ RSpec.describe "Lettings Log Features" do
select(managing_org.name, from: "lettings-log-managing-organisation-id-field") select(managing_org.name, from: "lettings-log-managing-organisation-id-field")
click_button("Save and continue") click_button("Save and continue")
visit("lettings-logs/#{log_id}/setup/check-answers") visit("lettings-logs/#{log_id}/setup/check-answers")
expect(page).to have_content("Managing agent Managing org") expect(page).to have_content("Managing agent Managing org", normalize_ws: true)
expect(user.organisation.managing_agents).to eq([org_rel2.child_organisation]) expect(user.organisation.managing_agents).to eq([org_rel2.child_organisation])
end end
end end

4
spec/fixtures/files/lettings_logs_download.csv vendored

@ -1,2 +1,2 @@
id,status,created_at,updated_at,created_by_name,is_dpo,owning_organisation_name,managing_organisation_name,collection_start_year,needstype,renewal,startdate,rent_type_detail,irproduct_other,tenancycode,propcode,age1,sex1,ecstat1,hhmemb,relat2,age2,sex2,retirement_value_check,ecstat2,armedforces,leftreg,illness,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_h,is_previous_la_inferred,prevloc_label,prevloc,illness_type_1,illness_type_2,is_la_inferred,la_label,la,postcode_known,postcode_full,previous_la_known,wchair,preg_occ,cbl,earnings,incfreq,net_income_value_check,benefits,hb,period,brent,scharge,pscharge,supcharg,tcharge,offered,layear,ppostcode_full,mrcdate,declaration,ethnic,national,prevten,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,reservist,startertenancy,tenancylength,tenancy,rsnvac,unittype_gn,beds,waityear,reasonpref,chr,cap,reasonother,housingneeds_f,housingneeds_g,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,property_owner_organisation,property_manager_organisation,purchaser_code,reason,majorrepairs,hbrentshortfall,property_relet,incref,first_time_property_let_as_social_housing,unitletas,builtype,voiddate,renttype,lettype,totchild,totelder,totadult,net_income_known,nocharge,is_carehome,household_charge,referral,tshortfall,chcharge,ppcodenk,age1_known,age2_known,age3_known,age4_known,age5_known,age6_known,age7_known,age8_known,ethnic_group,letting_allocation_unknown,details_known_2,details_known_3,details_known_4,details_known_5,details_known_6,details_known_7,details_known_8,has_benefits,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat3,relat4,relat5,relat6,relat7,relat8,rent_value_check,old_form_id,lar,irproduct,old_id,joint,tshortfall_known,sheltered,pregnancy_value_check,hhtype,new_old,vacdays,major_repairs_date_value_check,void_date_value_check,housingneeds_type,housingneeds_other,unresolved,updated_by_id,unittype_sh,scheme_code,scheme_service_name,scheme_sensitive,scheme_type,scheme_registered_under_care_act,scheme_owning_organisation_name,scheme_primary_client_group,scheme_has_other_client_group,scheme_secondary_client_group,scheme_support_type,scheme_intended_stay,scheme_created_at,location_code,location_postcode,location_name,location_units,location_type_of_unit,location_mobility_type,location_admin_district,location_startdate id,status,created_at,updated_at,created_by_name,is_dpo,owning_organisation_name,managing_organisation_name,collection_start_year,needstype,renewal,startdate,rent_type_detail,irproduct_other,tenancycode,propcode,age1,sex1,ecstat1,hhmemb,relat2,age2,sex2,retirement_value_check,ecstat2,armedforces,leftreg,illness,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_h,is_previous_la_inferred,prevloc_label,prevloc,illness_type_1,illness_type_2,is_la_inferred,la_label,la,postcode_known,postcode_full,previous_la_known,wchair,preg_occ,cbl,earnings,incfreq,net_income_value_check,benefits,hb,period,brent,scharge,pscharge,supcharg,tcharge,offered,layear,ppostcode_full,mrcdate,declaration,ethnic,national,prevten,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,reservist,startertenancy,tenancylength,tenancy,rsnvac,unittype_gn,beds,waityear,reasonpref,chr,cap,reasonother,housingneeds_f,housingneeds_g,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,property_owner_organisation,property_manager_organisation,purchaser_code,reason,majorrepairs,hbrentshortfall,property_relet,incref,first_time_property_let_as_social_housing,unitletas,builtype,voiddate,renttype,lettype,totchild,totelder,totadult,net_income_known,nocharge,is_carehome,household_charge,referral,tshortfall,chcharge,ppcodenk,age1_known,age2_known,age3_known,age4_known,age5_known,age6_known,age7_known,age8_known,ethnic_group,letting_allocation_unknown,details_known_2,details_known_3,details_known_4,details_known_5,details_known_6,details_known_7,details_known_8,has_benefits,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat3,relat4,relat5,relat6,relat7,relat8,rent_value_check,old_form_id,lar,irproduct,old_id,joint,tshortfall_known,sheltered,pregnancy_value_check,hhtype,new_old,vacdays,major_repairs_date_value_check,void_date_value_check,housingneeds_type,housingneeds_other,unresolved,updated_by_id,uprn,uprn_known,uprn_confirmed,address_line1,address_line2,town_or_city,county,unittype_sh,scheme_code,scheme_service_name,scheme_sensitive,scheme_type,scheme_registered_under_care_act,scheme_owning_organisation_name,scheme_primary_client_group,scheme_has_other_client_group,scheme_secondary_client_group,scheme_support_type,scheme_intended_stay,scheme_created_at,location_code,location_postcode,location_name,location_units,location_type_of_unit,location_mobility_type,location_admin_district,location_startdate
{id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,Danny Rojas,No,DLUHC,DLUHC,2021,Supported housing,,2 October 2021,London Affordable Rent,,,,,,,,,,,,,,,,,,,,No,,,,,No,Westminster,E09000033,,SE1 1TE,,No,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,8,0,0,0,,0,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,0,,,,,,,,,,,,,,,,,,,9,1,,,,,,,,6,{scheme_code},{scheme_service_name},{scheme_sensitive},Missing,No,DLUHC,{scheme_primary_client_group},,{scheme_secondary_client_group},{scheme_support_type},{scheme_intended_stay},2021-04-01 00:00:00 +0100,{location_code},SE1 1TE,Downing Street,20,Bungalow,Fitted with equipment and adaptations,Westminster,{location_startdate} {id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,Danny Rojas,No,DLUHC,DLUHC,2021,Supported housing,,2 October 2021,London Affordable Rent,,,,,,,,,,,,,,,,,,,,No,,,,,No,Westminster,E09000033,,SE1 1TE,,No,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,8,0,0,0,,0,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,0,,,,,,,,,,,,,,,,,,,9,1,,,,,,,,,,,,,,,6,{scheme_code},{scheme_service_name},{scheme_sensitive},Missing,No,DLUHC,{scheme_primary_client_group},,{scheme_secondary_client_group},{scheme_support_type},{scheme_intended_stay},2021-04-01 00:00:00 +0100,{location_code},SE1 1TE,Downing Street,20,Bungalow,Fitted with equipment and adaptations,Westminster,{location_startdate}

1 id status created_at updated_at created_by_name is_dpo owning_organisation_name managing_organisation_name collection_start_year needstype renewal startdate rent_type_detail irproduct_other tenancycode propcode age1 sex1 ecstat1 hhmemb relat2 age2 sex2 retirement_value_check ecstat2 armedforces leftreg illness housingneeds_a housingneeds_b housingneeds_c housingneeds_h is_previous_la_inferred prevloc_label prevloc illness_type_1 illness_type_2 is_la_inferred la_label la postcode_known postcode_full previous_la_known wchair preg_occ cbl earnings incfreq net_income_value_check benefits hb period brent scharge pscharge supcharg tcharge offered layear ppostcode_full mrcdate declaration ethnic national prevten age3 sex3 ecstat3 age4 sex4 ecstat4 age5 sex5 ecstat5 age6 sex6 ecstat6 age7 sex7 ecstat7 age8 sex8 ecstat8 homeless underoccupation_benefitcap reservist startertenancy tenancylength tenancy rsnvac unittype_gn beds waityear reasonpref chr cap reasonother housingneeds_f housingneeds_g 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 property_owner_organisation property_manager_organisation purchaser_code reason majorrepairs hbrentshortfall property_relet incref first_time_property_let_as_social_housing unitletas builtype voiddate renttype lettype totchild totelder totadult net_income_known nocharge is_carehome household_charge referral tshortfall chcharge ppcodenk age1_known age2_known age3_known age4_known age5_known age6_known age7_known age8_known ethnic_group letting_allocation_unknown details_known_2 details_known_3 details_known_4 details_known_5 details_known_6 details_known_7 details_known_8 has_benefits wrent wscharge wpschrge wsupchrg wtcharge wtshortfall refused housingneeds wchchrg newprop relat3 relat4 relat5 relat6 relat7 relat8 rent_value_check old_form_id lar irproduct old_id joint tshortfall_known sheltered pregnancy_value_check hhtype new_old vacdays major_repairs_date_value_check void_date_value_check housingneeds_type housingneeds_other unresolved updated_by_id uprn uprn_known uprn_confirmed address_line1 address_line2 town_or_city county unittype_sh scheme_code scheme_service_name scheme_sensitive scheme_type scheme_registered_under_care_act scheme_owning_organisation_name scheme_primary_client_group scheme_has_other_client_group scheme_secondary_client_group scheme_support_type scheme_intended_stay scheme_created_at location_code location_postcode location_name location_units location_type_of_unit location_mobility_type location_admin_district location_startdate
2 {id} in_progress 2022-02-08 16:52:15 +0000 2022-02-08 16:52:15 +0000 Danny Rojas No DLUHC DLUHC 2021 Supported housing 2 October 2021 London Affordable Rent No No Westminster E09000033 SE1 1TE No 2 8 0 0 0 0 0 0 9 1 6 {scheme_code} {scheme_service_name} {scheme_sensitive} Missing No DLUHC {scheme_primary_client_group} {scheme_secondary_client_group} {scheme_support_type} {scheme_intended_stay} 2021-04-01 00:00:00 +0100 {location_code} SE1 1TE Downing Street 20 Bungalow Fitted with equipment and adaptations Westminster {location_startdate}

4
spec/fixtures/files/lettings_logs_download_codes_only.csv vendored

@ -1,2 +1,2 @@
id,status,created_at,updated_at,created_by_name,is_dpo,owning_organisation_name,managing_organisation_name,collection_start_year,needstype,renewal,startdate,rent_type_detail,irproduct_other,tenancycode,propcode,age1,sex1,ecstat1,hhmemb,relat2,age2,sex2,retirement_value_check,ecstat2,armedforces,leftreg,illness,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_h,is_previous_la_inferred,prevloc_label,prevloc,illness_type_1,illness_type_2,is_la_inferred,la_label,la,postcode_known,postcode_full,previous_la_known,wchair,preg_occ,cbl,earnings,incfreq,net_income_value_check,benefits,hb,period,brent,scharge,pscharge,supcharg,tcharge,offered,layear,ppostcode_full,mrcdate,declaration,ethnic,national,prevten,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,reservist,startertenancy,tenancylength,tenancy,rsnvac,unittype_gn,beds,waityear,reasonpref,chr,cap,reasonother,housingneeds_f,housingneeds_g,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,property_owner_organisation,property_manager_organisation,purchaser_code,reason,majorrepairs,hbrentshortfall,property_relet,incref,first_time_property_let_as_social_housing,unitletas,builtype,voiddate,renttype,lettype,totchild,totelder,totadult,net_income_known,nocharge,is_carehome,household_charge,referral,tshortfall,chcharge,ppcodenk,age1_known,age2_known,age3_known,age4_known,age5_known,age6_known,age7_known,age8_known,ethnic_group,letting_allocation_unknown,details_known_2,details_known_3,details_known_4,details_known_5,details_known_6,details_known_7,details_known_8,has_benefits,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat3,relat4,relat5,relat6,relat7,relat8,rent_value_check,old_form_id,lar,irproduct,old_id,joint,tshortfall_known,sheltered,pregnancy_value_check,hhtype,new_old,vacdays,major_repairs_date_value_check,void_date_value_check,housingneeds_type,housingneeds_other,unresolved,updated_by_id,unittype_sh,scheme_code,scheme_service_name,scheme_sensitive,scheme_type,scheme_registered_under_care_act,scheme_owning_organisation_name,scheme_primary_client_group,scheme_has_other_client_group,scheme_secondary_client_group,scheme_support_type,scheme_intended_stay,scheme_created_at,location_code,location_postcode,location_name,location_units,location_type_of_unit,location_mobility_type,location_admin_district,location_startdate id,status,created_at,updated_at,created_by_name,is_dpo,owning_organisation_name,managing_organisation_name,collection_start_year,needstype,renewal,startdate,rent_type_detail,irproduct_other,tenancycode,propcode,age1,sex1,ecstat1,hhmemb,relat2,age2,sex2,retirement_value_check,ecstat2,armedforces,leftreg,illness,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_h,is_previous_la_inferred,prevloc_label,prevloc,illness_type_1,illness_type_2,is_la_inferred,la_label,la,postcode_known,postcode_full,previous_la_known,wchair,preg_occ,cbl,earnings,incfreq,net_income_value_check,benefits,hb,period,brent,scharge,pscharge,supcharg,tcharge,offered,layear,ppostcode_full,mrcdate,declaration,ethnic,national,prevten,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,reservist,startertenancy,tenancylength,tenancy,rsnvac,unittype_gn,beds,waityear,reasonpref,chr,cap,reasonother,housingneeds_f,housingneeds_g,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,property_owner_organisation,property_manager_organisation,purchaser_code,reason,majorrepairs,hbrentshortfall,property_relet,incref,first_time_property_let_as_social_housing,unitletas,builtype,voiddate,renttype,lettype,totchild,totelder,totadult,net_income_known,nocharge,is_carehome,household_charge,referral,tshortfall,chcharge,ppcodenk,age1_known,age2_known,age3_known,age4_known,age5_known,age6_known,age7_known,age8_known,ethnic_group,letting_allocation_unknown,details_known_2,details_known_3,details_known_4,details_known_5,details_known_6,details_known_7,details_known_8,has_benefits,wrent,wscharge,wpschrge,wsupchrg,wtcharge,wtshortfall,refused,housingneeds,wchchrg,newprop,relat3,relat4,relat5,relat6,relat7,relat8,rent_value_check,old_form_id,lar,irproduct,old_id,joint,tshortfall_known,sheltered,pregnancy_value_check,hhtype,new_old,vacdays,major_repairs_date_value_check,void_date_value_check,housingneeds_type,housingneeds_other,unresolved,updated_by_id,uprn,uprn_known,uprn_confirmed,address_line1,address_line2,town_or_city,county,unittype_sh,scheme_code,scheme_service_name,scheme_sensitive,scheme_type,scheme_registered_under_care_act,scheme_owning_organisation_name,scheme_primary_client_group,scheme_has_other_client_group,scheme_secondary_client_group,scheme_support_type,scheme_intended_stay,scheme_created_at,location_code,location_postcode,location_name,location_units,location_type_of_unit,location_mobility_type,location_admin_district,location_startdate
{id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,Danny Rojas,false,DLUHC,DLUHC,2021,2,,2 October 2021,2,,,,,,,,,,,,,,,,,,,,false,,,,,false,Westminster,E09000033,,SE1 1TE,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,8,0,0,0,,0,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,0,,,,,,,,,,,,,,,,,,,9,1,,,,,,,,6,{scheme_code},{scheme_service_name},{scheme_sensitive},0,1,DLUHC,{scheme_primary_client_group},,{scheme_secondary_client_group},{scheme_support_type},{scheme_intended_stay},2021-04-01 00:00:00 +0100,{location_code},SE1 1TE,Downing Street,20,6,A,Westminster,{location_startdate} {id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,Danny Rojas,false,DLUHC,DLUHC,2021,2,,2 October 2021,2,,,,,,,,,,,,,,,,,,,,false,,,,,false,Westminster,E09000033,,SE1 1TE,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,8,0,0,0,,0,,,,,,,,,,,,,,,,,,,,,,,,0,,,,,,,0,,,,,,,,,,,,,,,,,,,9,1,,,,,,,,,,,,,,,6,{scheme_code},{scheme_service_name},{scheme_sensitive},0,1,DLUHC,{scheme_primary_client_group},,{scheme_secondary_client_group},{scheme_support_type},{scheme_intended_stay},2021-04-01 00:00:00 +0100,{location_code},SE1 1TE,Downing Street,20,6,A,Westminster,{location_startdate}

1 id status created_at updated_at created_by_name is_dpo owning_organisation_name managing_organisation_name collection_start_year needstype renewal startdate rent_type_detail irproduct_other tenancycode propcode age1 sex1 ecstat1 hhmemb relat2 age2 sex2 retirement_value_check ecstat2 armedforces leftreg illness housingneeds_a housingneeds_b housingneeds_c housingneeds_h is_previous_la_inferred prevloc_label prevloc illness_type_1 illness_type_2 is_la_inferred la_label la postcode_known postcode_full previous_la_known wchair preg_occ cbl earnings incfreq net_income_value_check benefits hb period brent scharge pscharge supcharg tcharge offered layear ppostcode_full mrcdate declaration ethnic national prevten age3 sex3 ecstat3 age4 sex4 ecstat4 age5 sex5 ecstat5 age6 sex6 ecstat6 age7 sex7 ecstat7 age8 sex8 ecstat8 homeless underoccupation_benefitcap reservist startertenancy tenancylength tenancy rsnvac unittype_gn beds waityear reasonpref chr cap reasonother housingneeds_f housingneeds_g 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 property_owner_organisation property_manager_organisation purchaser_code reason majorrepairs hbrentshortfall property_relet incref first_time_property_let_as_social_housing unitletas builtype voiddate renttype lettype totchild totelder totadult net_income_known nocharge is_carehome household_charge referral tshortfall chcharge ppcodenk age1_known age2_known age3_known age4_known age5_known age6_known age7_known age8_known ethnic_group letting_allocation_unknown details_known_2 details_known_3 details_known_4 details_known_5 details_known_6 details_known_7 details_known_8 has_benefits wrent wscharge wpschrge wsupchrg wtcharge wtshortfall refused housingneeds wchchrg newprop relat3 relat4 relat5 relat6 relat7 relat8 rent_value_check old_form_id lar irproduct old_id joint tshortfall_known sheltered pregnancy_value_check hhtype new_old vacdays major_repairs_date_value_check void_date_value_check housingneeds_type housingneeds_other unresolved updated_by_id uprn uprn_known uprn_confirmed address_line1 address_line2 town_or_city county unittype_sh scheme_code scheme_service_name scheme_sensitive scheme_type scheme_registered_under_care_act scheme_owning_organisation_name scheme_primary_client_group scheme_has_other_client_group scheme_secondary_client_group scheme_support_type scheme_intended_stay scheme_created_at location_code location_postcode location_name location_units location_type_of_unit location_mobility_type location_admin_district location_startdate
2 {id} in_progress 2022-02-08 16:52:15 +0000 2022-02-08 16:52:15 +0000 Danny Rojas false DLUHC DLUHC 2021 2 2 October 2021 2 false false Westminster E09000033 SE1 1TE 2 2 8 0 0 0 0 0 0 9 1 6 {scheme_code} {scheme_service_name} {scheme_sensitive} 0 1 DLUHC {scheme_primary_client_group} {scheme_secondary_client_group} {scheme_support_type} {scheme_intended_stay} 2021-04-01 00:00:00 +0100 {location_code} SE1 1TE Downing Street 20 6 A Westminster {location_startdate}

4
spec/fixtures/files/lettings_logs_download_non_support.csv vendored

@ -1,2 +1,2 @@
id,status,created_at,updated_at,created_by_name,is_dpo,owning_organisation_name,managing_organisation_name,collection_start_year,renewal,startdate,irproduct_other,tenancycode,propcode,age1,sex1,ecstat1,relat2,age2,sex2,ecstat2,armedforces,leftreg,illness,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_h,prevloc_label,illness_type_1,illness_type_2,la_label,postcode_full,wchair,preg_occ,cbl,earnings,incfreq,benefits,hb,period,brent,scharge,pscharge,supcharg,tcharge,offered,layear,ppostcode_full,mrcdate,declaration,ethnic,national,prevten,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,reservist,startertenancy,tenancylength,tenancy,rsnvac,unittype_gn,beds,waityear,reasonpref,chr,cap,reasonother,housingneeds_f,housingneeds_g,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,property_owner_organisation,property_manager_organisation,purchaser_code,reason,majorrepairs,hbrentshortfall,property_relet,incref,unitletas,builtype,voiddate,lettype,nocharge,household_charge,referral,tshortfall,chcharge,ppcodenk,ethnic_group,has_benefits,refused,housingneeds,wchchrg,newprop,relat3,relat4,relat5,relat6,relat7,relat8,lar,irproduct,joint,sheltered,major_repairs_date_value_check,void_date_value_check,housingneeds_type,housingneeds_other,unittype_sh,scheme_code,scheme_service_name,scheme_sensitive,scheme_type,scheme_registered_under_care_act,scheme_owning_organisation_name,scheme_primary_client_group,scheme_has_other_client_group,scheme_secondary_client_group,scheme_support_type,scheme_intended_stay,scheme_created_at,location_code,location_postcode,location_name,location_units,location_type_of_unit,location_mobility_type,location_admin_district,location_startdate id,status,created_at,updated_at,created_by_name,is_dpo,owning_organisation_name,managing_organisation_name,collection_start_year,renewal,startdate,irproduct_other,tenancycode,propcode,age1,sex1,ecstat1,relat2,age2,sex2,ecstat2,armedforces,leftreg,illness,housingneeds_a,housingneeds_b,housingneeds_c,housingneeds_h,prevloc_label,illness_type_1,illness_type_2,la_label,postcode_full,wchair,preg_occ,cbl,earnings,incfreq,benefits,hb,period,brent,scharge,pscharge,supcharg,tcharge,offered,layear,ppostcode_full,mrcdate,declaration,ethnic,national,prevten,age3,sex3,ecstat3,age4,sex4,ecstat4,age5,sex5,ecstat5,age6,sex6,ecstat6,age7,sex7,ecstat7,age8,sex8,ecstat8,homeless,underoccupation_benefitcap,reservist,startertenancy,tenancylength,tenancy,rsnvac,unittype_gn,beds,waityear,reasonpref,chr,cap,reasonother,housingneeds_f,housingneeds_g,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,property_owner_organisation,property_manager_organisation,purchaser_code,reason,majorrepairs,hbrentshortfall,property_relet,incref,unitletas,builtype,voiddate,lettype,nocharge,household_charge,referral,tshortfall,chcharge,ppcodenk,ethnic_group,has_benefits,refused,housingneeds,wchchrg,newprop,relat3,relat4,relat5,relat6,relat7,relat8,lar,irproduct,joint,sheltered,major_repairs_date_value_check,void_date_value_check,housingneeds_type,housingneeds_other,uprn,uprn_known,uprn_confirmed,address_line1,address_line2,town_or_city,county,unittype_sh,scheme_code,scheme_service_name,scheme_sensitive,scheme_type,scheme_registered_under_care_act,scheme_owning_organisation_name,scheme_primary_client_group,scheme_has_other_client_group,scheme_secondary_client_group,scheme_support_type,scheme_intended_stay,scheme_created_at,location_code,location_postcode,location_name,location_units,location_type_of_unit,location_mobility_type,location_admin_district,location_startdate
{id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,Danny Rojas,No,DLUHC,DLUHC,2021,,2 October 2021,,,,,,,,,,,,,,,,,,,,,Westminster,SE1 1TE,No,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,0,,,,,,,0,0,,,,,,,,,,,,,,,,,,6,{scheme_code},{scheme_service_name},{scheme_sensitive},Missing,No,DLUHC,{scheme_primary_client_group},,{scheme_secondary_client_group},{scheme_support_type},{scheme_intended_stay},2021-04-01 00:00:00 +0100,{location_code},SE1 1TE,Downing Street,20,Bungalow,Fitted with equipment and adaptations,Westminster,{location_startdate} {id},in_progress,2022-02-08 16:52:15 +0000,2022-02-08 16:52:15 +0000,Danny Rojas,No,DLUHC,DLUHC,2021,,2 October 2021,,,,,,,,,,,,,,,,,,,,,Westminster,SE1 1TE,No,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,0,,,,,,,0,0,,,,,,,,,,,,,,,,,,,,,,,,,6,{scheme_code},{scheme_service_name},{scheme_sensitive},Missing,No,DLUHC,{scheme_primary_client_group},,{scheme_secondary_client_group},{scheme_support_type},{scheme_intended_stay},2021-04-01 00:00:00 +0100,{location_code},SE1 1TE,Downing Street,20,Bungalow,Fitted with equipment and adaptations,Westminster,{location_startdate}

1 id status created_at updated_at created_by_name is_dpo owning_organisation_name managing_organisation_name collection_start_year renewal startdate irproduct_other tenancycode propcode age1 sex1 ecstat1 relat2 age2 sex2 ecstat2 armedforces leftreg illness housingneeds_a housingneeds_b housingneeds_c housingneeds_h prevloc_label illness_type_1 illness_type_2 la_label postcode_full wchair preg_occ cbl earnings incfreq benefits hb period brent scharge pscharge supcharg tcharge offered layear ppostcode_full mrcdate declaration ethnic national prevten age3 sex3 ecstat3 age4 sex4 ecstat4 age5 sex5 ecstat5 age6 sex6 ecstat6 age7 sex7 ecstat7 age8 sex8 ecstat8 homeless underoccupation_benefitcap reservist startertenancy tenancylength tenancy rsnvac unittype_gn beds waityear reasonpref chr cap reasonother housingneeds_f housingneeds_g 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 property_owner_organisation property_manager_organisation purchaser_code reason majorrepairs hbrentshortfall property_relet incref unitletas builtype voiddate lettype nocharge household_charge referral tshortfall chcharge ppcodenk ethnic_group has_benefits refused housingneeds wchchrg newprop relat3 relat4 relat5 relat6 relat7 relat8 lar irproduct joint sheltered major_repairs_date_value_check void_date_value_check housingneeds_type housingneeds_other uprn uprn_known uprn_confirmed address_line1 address_line2 town_or_city county unittype_sh scheme_code scheme_service_name scheme_sensitive scheme_type scheme_registered_under_care_act scheme_owning_organisation_name scheme_primary_client_group scheme_has_other_client_group scheme_secondary_client_group scheme_support_type scheme_intended_stay scheme_created_at location_code location_postcode location_name location_units location_type_of_unit location_mobility_type location_admin_district location_startdate
2 {id} in_progress 2022-02-08 16:52:15 +0000 2022-02-08 16:52:15 +0000 Danny Rojas No DLUHC DLUHC 2021 2 October 2021 Westminster SE1 1TE No 8 0 0 0 6 {scheme_code} {scheme_service_name} {scheme_sensitive} Missing No DLUHC {scheme_primary_client_group} {scheme_secondary_client_group} {scheme_support_type} {scheme_intended_stay} 2021-04-01 00:00:00 +0100 {location_code} SE1 1TE Downing Street 20 Bungalow Fitted with equipment and adaptations Westminster {location_startdate}

26
spec/helpers/question_view_helper_spec.rb

@ -44,10 +44,16 @@ RSpec.describe QuestionViewHelper do
describe "legend" do describe "legend" do
subject(:question_view_helper) { legend(question, page_header, conditional) } subject(:question_view_helper) { legend(question, page_header, conditional) }
question_stub = Struct.new(:header) do let(:question_stub) do
Struct.new(:header) do
def question_number_string(_conditional) def question_number_string(_conditional)
nil nil
end end
def plain_label
nil
end
end
end end
let(:question) { question_stub.new("Some question header") } let(:question) { question_stub.new("Some question header") }
@ -81,5 +87,23 @@ RSpec.describe QuestionViewHelper do
expect(question_view_helper).to eq(legend_options_hash) expect(question_view_helper).to eq(legend_options_hash)
end end
end end
context "when viewing a question with a plain label" do
let(:question_stub) do
Struct.new(:header) do
def question_number_string(_conditional)
nil
end
def plain_label
true
end
end
end
it "returns an options hash with nil size" do
expect(question_view_helper).to eq({ size: nil, tag: "div", text: "Some question header" })
end
end
end end
end end

73
spec/models/form/sales/pages/address_spec.rb

@ -0,0 +1,73 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::Address, 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[address_line1 address_line2 town_or_city county postcode_full])
end
it "has the correct id" do
expect(page.id).to eq("address")
end
it "has the correct header" do
expect(page.header).to eq("What is the property's address?")
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 be_nil
end
describe "has correct routed_to?" do
context "when uprn_known == nil" do
let(:log) { create(:sales_log, uprn_known: nil) }
it "returns false" do
expect(page.routed_to?(log)).to eq(false)
end
end
context "when uprn_confirmed != 1" do
let(:log) do
create(:sales_log, uprn_known: 1, uprn_confirmed: 0)
end
it "returns true" do
expect(page.routed_to?(log)).to eq(true)
end
end
context "when uprn_known == 0" do
let(:log) do
create(:sales_log, uprn_known: 0, uprn_confirmed: 0)
end
it "returns true" do
expect(page.routed_to?(log)).to eq(true)
end
end
context "when uprn_confirmed == 1 && uprn_known != 0" do
let(:log) do
create(:sales_log, uprn_known: 1, uprn_confirmed: 1)
end
it "returns true" do
expect(page.routed_to?(log)).to eq(false)
end
end
end
end

47
spec/models/form/sales/pages/property_local_authority_spec.rb

@ -12,6 +12,10 @@ RSpec.describe Form::Sales::Pages::PropertyLocalAuthority, type: :model do
expect(page.subsection).to eq(subsection) expect(page.subsection).to eq(subsection)
end end
describe "has correct questions" do
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( expect(page.questions.map(&:id)).to eq(
%w[ %w[
@ -20,6 +24,20 @@ RSpec.describe Form::Sales::Pages::PropertyLocalAuthority, type: :model do
], ],
) )
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[
la
],
)
end
end
end
it "has the correct id" do it "has the correct id" do
expect(page.id).to eq("property_local_authority") expect(page.id).to eq("property_local_authority")
@ -38,4 +56,33 @@ RSpec.describe Form::Sales::Pages::PropertyLocalAuthority, type: :model do
"is_la_inferred" => false, "is_la_inferred" => false,
}]) }])
end end
describe "has correct routed_to?" do
context "when start_date < 2023" do
let(:log) { create(:sales_log, uprn_known: 1) }
let(:start_date) { Time.utc(2022, 2, 8) }
it "returns false" do
expect(page.routed_to?(log)).to eq(true)
end
end
context "when start_date >= 2023" do
let(:log) { create(:sales_log, uprn_known: 1) }
let(:start_date) { Time.utc(2023, 2, 8) }
it "returns true" do
expect(page.routed_to?(log)).to eq(true)
end
end
context "when start_date < 2023 and uprn_known: nil" do
let(:log) { create(:sales_log, uprn_known: nil) }
let(:start_date) { Time.utc(2023, 2, 8) }
it "returns true" do
expect(page.routed_to?(log)).to eq(false)
end
end
end
end end

59
spec/models/form/sales/pages/uprn_confirmation_spec.rb

@ -0,0 +1,59 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::UprnConfirmation, 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[uprn_confirmed])
end
it "has the correct id" do
expect(page.id).to eq("uprn_confirmation")
end
it "has the correct header" do
expect(page.header).to eq("We found an address that might be this property")
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 be_nil
end
describe "has correct routed_to?" do
context "when uprn present && uprn_known == 1 " do
let(:log) { create(:sales_log, uprn_known: 1, uprn: "123456789") }
it "returns true" do
expect(page.routed_to?(log)).to eq(true)
end
end
context "when uprn = nil" do
let(:log) { create(:sales_log, uprn_known: 1, uprn: nil) }
it "returns false" do
expect(page.routed_to?(log)).to eq(false)
end
end
context "when uprn_known == 0" do
let(:log) { create(:sales_log, uprn_known: 0, uprn: "123456789") }
it "returns false" do
expect(page.routed_to?(log)).to eq(false)
end
end
end
end

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

@ -0,0 +1,33 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::UprnKnown, 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[uprn_known])
end
it "has the correct id" do
expect(page.id).to eq("uprn_known")
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 be_nil
end
end

73
spec/models/form/sales/pages/uprn_spec.rb

@ -0,0 +1,73 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::Uprn, 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[uprn])
end
it "has the correct id" do
expect(page.id).to eq("uprn")
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 be_nil
end
it "has correct skip_text" do
expect(page.skip_text).to eq("Enter address instead")
end
describe "has correct routed_to?" do
context "when uprn_known != 1" do
let(:log) { create(:sales_log, uprn_known: 0) }
it "returns false" do
expect(page.routed_to?(log)).to eq(false)
end
end
context "when uprn_known == 1" do
let(:log) { create(:sales_log, uprn_known: 1) }
it "returns true" do
expect(page.routed_to?(log)).to eq(true)
end
end
end
describe "has correct skip_href" do
context "when log is nil" do
it "is nil" do
expect(page.skip_href).to be_nil
end
end
context "when log is present" do
let(:log) { create(:sales_log) }
it "points to address page" do
expect(page.skip_href(log)).to eq(
"/sales-logs/#{log.id}/address",
)
end
end
end
end

75
spec/models/form/sales/questions/address_line1_spec.rb

@ -0,0 +1,75 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::AddressLine1, 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("address_line1")
end
it "has the correct header" do
expect(question.header).to eq("Address line 1")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Address")
end
it "has the correct type" do
expect(question.type).to eq("text")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct hint" do
expect(question.hint_text).to be_nil
end
it "has the correct inferred check answers value" do
expect(question.inferred_check_answers_value).to be_nil
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to be_nil
end
describe "has the correct get_extra_check_answer_value" do
context "when la is not present" do
let(:log) { create(:sales_log, la: nil) }
it "returns nil" do
expect(question.get_extra_check_answer_value(log)).to be_nil
end
end
context "when la is present but not inferred" do
let(:log) { create(:sales_log, la: "E09000003", is_la_inferred: false) }
it "returns nil" do
expect(question.get_extra_check_answer_value(log)).to be_nil
end
end
context "when la is present but inferred" do
let(:log) { create(:sales_log, la: "E09000003") }
before do
allow(log).to receive(:is_la_inferred?).and_return(true)
end
it "returns the la" do
expect(question.get_extra_check_answer_value(log)).to eq("Barnet")
end
end
end
end

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

@ -0,0 +1,49 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::AddressLine2, 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("address_line2")
end
it "has the correct header" do
expect(question.header).to eq("Address line 2 (optional)")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to be_nil
end
it "has the correct type" do
expect(question.type).to eq("text")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct hint" do
expect(question.hint_text).to be_nil
end
it "has the correct inferred check answers value" do
expect(question.inferred_check_answers_value).to be_nil
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to be_nil
end
it "has the correct hidden_in_check_answers" do
expect(question.hidden_in_check_answers?).to eq(true)
end
end

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

@ -0,0 +1,49 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::County, 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("county")
end
it "has the correct header" do
expect(question.header).to eq("County (optional)")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to be_nil
end
it "has the correct type" do
expect(question.type).to eq("text")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct hint" do
expect(question.hint_text).to be_nil
end
it "has the correct inferred check answers value" do
expect(question.inferred_check_answers_value).to be_nil
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to be_nil
end
it "has the correct hidden_in_check_answers" do
expect(question.hidden_in_check_answers?).to eq(true)
end
end

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

@ -0,0 +1,62 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::PostcodeForFullAddress, 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("postcode_full")
end
it "has the correct header" do
expect(question.header).to eq("Postcode")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to be_nil
end
it "has the correct type" do
expect(question.type).to eq("text")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct hint" do
expect(question.hint_text).to be_nil
end
it "has the correct width" do
expect(question.width).to eq(5)
end
it "has the correct inferred_answers" do
expect(question.inferred_answers).to eq({
"la" => {
"is_la_inferred" => true,
},
})
end
it "has the correct inferred_check_answers_value" do
expect(question.inferred_check_answers_value).to eq([{
"condition" => {
"pcodenk" => 1,
},
"value" => "Not known",
}])
end
it "has the correct hidden_in_check_answers" do
expect(question.hidden_in_check_answers?).to eq(true)
end
end

30
spec/models/form/sales/questions/property_local_authority_spec.rb

@ -667,4 +667,34 @@ RSpec.describe Form::Sales::Questions::PropertyLocalAuthority, type: :model do
}) })
end end
end end
describe "has the correct hidden_in_check_answers" do
context "when saledate.year before 2023" do
let(:log) { build(:sales_log, saledate: Time.zone.parse("2022-07-01")) }
it "returns false" do
expect(question.hidden_in_check_answers?(log)).to eq(false)
end
end
context "when saledate.year >= 2023" do
let(:log) { build(:sales_log, saledate: Time.zone.parse("2023-07-01")) }
it "returns true" do
expect(question.hidden_in_check_answers?(log)).to eq(false)
end
end
context "when saledate.year >= 2023 and la inferred" do
let(:log) { build(:sales_log, saledate: Time.zone.parse("2023-07-01")) }
before do
allow(log).to receive(:is_la_inferred?).and_return(true)
end
it "returns true" do
expect(question.hidden_in_check_answers?(log)).to eq(true)
end
end
end
end end

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

@ -0,0 +1,49 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::TownOrCity, 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("town_or_city")
end
it "has the correct header" do
expect(question.header).to eq("Town or city")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to be_nil
end
it "has the correct type" do
expect(question.type).to eq("text")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct hint" do
expect(question.hint_text).to be_nil
end
it "has the correct inferred check answers value" do
expect(question.inferred_check_answers_value).to be_nil
end
it "has the correct check_answers_card_number" do
expect(question.check_answers_card_number).to be_nil
end
it "has the correct hidden_in_check_answers" do
expect(question.hidden_in_check_answers?).to eq(true)
end
end

90
spec/models/form/sales/questions/uprn_confirmation_spec.rb

@ -0,0 +1,90 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::UprnConfirmation, 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("uprn_confirmed")
end
it "has the correct header" do
expect(question.header).to eq("Is this the property address?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Is this the right address?")
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" do
expect(question.hint_text).to be_nil
end
it "has the correct unanswered_error_message" do
expect(question.unanswered_error_message).to eq("You must answer is this the right address?")
end
describe "notification_banner" do
context "when address is not present" do
it "returns nil" do
log = create(:sales_log)
expect(question.notification_banner(log)).to be_nil
end
end
context "when address is present" do
it "returns formatted value" do
log = create(:sales_log, address_line1: "1, Test Street", town_or_city: "Test Town", county: "Test County", postcode_full: "AA1 1AA", uprn: "1234")
expect(question.notification_banner(log)).to eq(
{
heading: "1, Test Street\nAA1 1AA\nTest Town\nTest County",
title: "UPRN: 1234",
},
)
end
end
end
describe "has the correct hidden_in_check_answers" do
context "when uprn_known != 1 && uprn_confirmed == nil" do
let(:log) { create(:sales_log, uprn_known: 0, uprn_confirmed: nil) }
it "returns true" do
expect(question.hidden_in_check_answers?(log)).to eq(true)
end
end
context "when uprn_known == 1 && uprn_confirmed == nil" do
let(:log) { create(:sales_log, uprn_known: 1, uprn_confirmed: nil) }
it "returns false" do
expect(question.hidden_in_check_answers?(log)).to eq(false)
end
end
context "when uprn_known != 1 && uprn_confirmed == 1" do
let(:log) { create(:sales_log, uprn_known: 1, uprn_confirmed: 1) }
it "returns true" do
expect(question.hidden_in_check_answers?(log)).to eq(true)
end
end
end
end

59
spec/models/form/sales/questions/uprn_known_spec.rb

@ -0,0 +1,59 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::UprnKnown, 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("uprn_known")
end
it "has the correct header" do
expect(question.header).to eq("Do you know the property UPRN?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("UPRN known?")
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({
"0" => { "value" => "No" },
"1" => { "value" => "Yes" },
})
end
it "has correct conditional for" do
expect(question.conditional_for).to be_nil
end
it "has the correct unanswered_error_message" do
expect(question.unanswered_error_message).to eq("You must answer UPRN known?")
end
it "has the correct hint" do
expect(question.hint_text).to eq(
"The Unique Property Reference Number (UPRN) is a unique number system created by Ordnance Survey and used by housing providers and sectors UK-wide. For example 10010457355.<br><br>
You can continue without the UPRN, but it means we will need you to enter the address of the property.",
)
end
it "has the correct hidden_in_check_answers" do
expect(question.hidden_in_check_answers).to be_nil
end
end

88
spec/models/form/sales/questions/uprn_spec.rb

@ -0,0 +1,88 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::Uprn, 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("uprn")
end
it "has the correct header" do
expect(question.header).to eq("What is the property's UPRN")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("UPRN")
end
it "has the correct type" do
expect(question.type).to eq("text")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct hint" do
expect(question.hint_text).to eq("The Unique Property Reference Number (UPRN) is a unique number system created by Ordnance Survey and used by housing providers and sectors UK-wide. For example 10010457355.")
end
it "has the correct unanswered_error_message" do
expect(question.unanswered_error_message).to eq("UPRN must be 12 digits or less")
end
describe "get_extra_check_answer_value" do
context "when address is not present" do
let(:log) { create(:sales_log) }
it "returns nil" do
expect(question.get_extra_check_answer_value(log)).to be_nil
end
end
context "when address is present" do
let(:log) do
create(
:sales_log,
address_line1: "1, Test Street",
town_or_city: "Test Town",
county: "Test County",
postcode_full: "AA1 1AA",
la: "E09000003",
)
end
it "returns formatted value" do
expect(question.get_extra_check_answer_value(log)).to eq(
"\n\n1, Test Street\nTest Town\nTest County\nAA1 1AA\nWestminster",
)
end
end
end
describe "has the correct hidden_in_check_answers" do
context "when uprn_known == 1" do
let(:log) { create(:sales_log, uprn_known: 1) }
it "returns false" do
expect(question.hidden_in_check_answers?(log)).to eq(false)
end
end
context "when uprn_known != 1" do
let(:log) { create(:sales_log, uprn_known: 0) }
it "returns false" do
expect(question.hidden_in_check_answers?(log)).to eq(true)
end
end
end
end

33
spec/models/form/sales/subsections/property_information_spec.rb

@ -11,8 +11,14 @@ RSpec.describe Form::Sales::Subsections::PropertyInformation, type: :model do
expect(property_information.section).to eq(section) expect(property_information.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 it "has correct pages" do
expect(property_information.pages.map(&:id)).to eq( expect(property_information.pages.compact.map(&:id)).to eq(
%w[ %w[
property_number_of_bedrooms property_number_of_bedrooms
about_price_bedrooms_value_check about_price_bedrooms_value_check
@ -26,6 +32,31 @@ RSpec.describe Form::Sales::Subsections::PropertyInformation, type: :model do
], ],
) )
end end
end
context "when 2023" do
let(:start_date) { Time.utc(2023, 2, 8) }
it "has correct pages" do
expect(property_information.pages.map(&:id)).to eq(
%w[
uprn_known
uprn
uprn_confirmation
address
property_local_authority
property_number_of_bedrooms
about_price_bedrooms_value_check
property_unit_type
monthly_charges_property_type_value_check
property_building_type
about_price_la_value_check
property_wheelchair_accessible
],
)
end
end
end
it "has the correct id" do it "has the correct id" do
expect(property_information.id).to eq("property_information") expect(property_information.id).to eq("property_information")

165
spec/models/lettings_log_spec.rb

@ -2,8 +2,8 @@ require "rails_helper"
require "shared/shared_examples_for_derived_fields" require "shared/shared_examples_for_derived_fields"
RSpec.describe LettingsLog do RSpec.describe LettingsLog do
let(:different_managing_organisation) { FactoryBot.create(:organisation) } let(:different_managing_organisation) { create(:organisation) }
let(:created_by_user) { FactoryBot.create(:user) } let(:created_by_user) { create(:user) }
let(:owning_organisation) { created_by_user.organisation } let(:owning_organisation) { created_by_user.organisation }
let(:fake_2021_2022_form) { Form.new("spec/fixtures/forms/2021_2022.json") } let(:fake_2021_2022_form) { Form.new("spec/fixtures/forms/2021_2022.json") }
@ -19,19 +19,19 @@ RSpec.describe LettingsLog do
end end
it "is a not a sales log" do it "is a not a sales log" do
lettings_log = FactoryBot.build(:lettings_log, created_by: created_by_user) lettings_log = build(:lettings_log, created_by: created_by_user)
expect(lettings_log.sales?).to be false expect(lettings_log.sales?).to be false
end 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 = build(:lettings_log, created_by: created_by_user)
expect(lettings_log).to be_lettings expect(lettings_log).to be_lettings
end end
describe "#form" do describe "#form" do
let(:lettings_log) { FactoryBot.build(:lettings_log, created_by: created_by_user) } let(:lettings_log) { build(:lettings_log, created_by: created_by_user) }
let(:lettings_log_2) { FactoryBot.build(:lettings_log, startdate: Time.zone.local(2022, 1, 1), created_by: created_by_user) } let(:lettings_log_2) { build(:lettings_log, startdate: Time.zone.local(2022, 1, 1), created_by: created_by_user) }
let(:lettings_log_year_2) { FactoryBot.build(:lettings_log, startdate: Time.zone.local(2023, 5, 1), created_by: created_by_user) } let(:lettings_log_year_2) { build(:lettings_log, startdate: Time.zone.local(2023, 5, 1), created_by: created_by_user) }
it "has returns the correct form based on the start date" do it "has returns the correct form based on the start date" do
expect(lettings_log.form_name).to be_nil expect(lettings_log.form_name).to be_nil
@ -43,7 +43,7 @@ RSpec.describe LettingsLog do
end end
context "when a date outside the collection window is passed" do context "when a date outside the collection window is passed" do
let(:lettings_log) { FactoryBot.build(:lettings_log, startdate: Time.zone.local(2015, 1, 1), created_by: created_by_user) } let(:lettings_log) { build(:lettings_log, startdate: Time.zone.local(2015, 1, 1), created_by: created_by_user) }
it "returns the first form" do it "returns the first form" do
expect(lettings_log.form).to be_a(Form) expect(lettings_log.form).to be_a(Form)
@ -70,7 +70,7 @@ RSpec.describe LettingsLog do
end end
describe "#update" do describe "#update" do
let(:lettings_log) { FactoryBot.create(:lettings_log, created_by: created_by_user) } let(:lettings_log) { create(:lettings_log, created_by: created_by_user) }
let(:validator) { lettings_log._validators[nil].first } let(:validator) { lettings_log._validators[nil].first }
after do after do
@ -168,9 +168,9 @@ RSpec.describe LettingsLog do
end end
describe "status" do describe "status" do
let!(:empty_lettings_log) { FactoryBot.create(:lettings_log) } let!(:empty_lettings_log) { create(:lettings_log) }
let!(:in_progress_lettings_log) { FactoryBot.create(:lettings_log, :in_progress) } let!(:in_progress_lettings_log) { create(:lettings_log, :in_progress) }
let!(:completed_lettings_log) { FactoryBot.create(:lettings_log, :completed) } let!(:completed_lettings_log) { create(:lettings_log, :completed) }
it "is set to not started for an empty lettings log" do it "is set to not started for an empty lettings log" do
expect(empty_lettings_log.not_started?).to be(true) expect(empty_lettings_log.not_started?).to be(true)
@ -208,7 +208,7 @@ RSpec.describe LettingsLog do
describe "weekly_net_income" do describe "weekly_net_income" do
let(:net_income) { 5000 } let(:net_income) { 5000 }
let(:lettings_log) { FactoryBot.build(:lettings_log, earnings: net_income) } let(:lettings_log) { build(:lettings_log, earnings: net_income) }
it "returns input income if frequency is already weekly" do it "returns input income if frequency is already weekly" do
lettings_log.incfreq = 1 lettings_log.incfreq = 1
@ -1860,8 +1860,8 @@ RSpec.describe LettingsLog do
end end
context "and a scheme with a single log is selected" do context "and a scheme with a single log is selected" do
let(:scheme) { FactoryBot.create(:scheme) } let(:scheme) { create(:scheme) }
let!(:location) { FactoryBot.create(:location, scheme:) } let!(:location) { create(:location, scheme:) }
before { lettings_log.update!(startdate: Time.zone.local(2022, 4, 2), scheme:) } before { lettings_log.update!(startdate: Time.zone.local(2022, 4, 2), scheme:) }
@ -1873,8 +1873,8 @@ RSpec.describe LettingsLog do
end end
context "and not renewal" do context "and not renewal" do
let(:scheme) { FactoryBot.create(:scheme) } let(:scheme) { create(:scheme) }
let(:location) { FactoryBot.create(:location, scheme:, postcode: "M11AE", type_of_unit: 1, mobility_type: "W") } let(:location) { create(:location, scheme:, postcode: "M11AE", type_of_unit: 1, mobility_type: "W") }
let(:supported_housing_lettings_log) do let(:supported_housing_lettings_log) do
described_class.create!({ described_class.create!({
@ -1916,8 +1916,8 @@ RSpec.describe LettingsLog do
end end
context "and renewal" do context "and renewal" do
let(:scheme) { FactoryBot.create(:scheme) } let(:scheme) { create(:scheme) }
let(:location) { FactoryBot.create(:location, scheme:) } let(:location) { create(:location, scheme:) }
let!(:supported_housing_lettings_log) do let!(:supported_housing_lettings_log) do
described_class.create!({ described_class.create!({
@ -2035,7 +2035,7 @@ RSpec.describe LettingsLog do
end end
describe "optional fields" do describe "optional fields" do
let(:lettings_log) { FactoryBot.create(:lettings_log) } let(:lettings_log) { create(:lettings_log) }
context "when tshortfall is marked as not known" do context "when tshortfall is marked as not known" do
it "makes tshortfall optional" do it "makes tshortfall optional" do
@ -2043,13 +2043,42 @@ RSpec.describe LettingsLog do
expect(lettings_log.optional_fields).to include("tshortfall") expect(lettings_log.optional_fields).to include("tshortfall")
end end
end end
context "when saledate is before 2023" do
let(:lettings_log) { build(:lettings_log, startdate: Time.zone.parse("2022-07-01")) }
it "returns optional fields" do
expect(lettings_log.optional_fields).to eq(%w[
first_time_property_let_as_social_housing
tenancycode
propcode
tenancylength
])
end
end
context "when saledate is after 2023" do
let(:lettings_log) { build(:lettings_log, startdate: Time.zone.parse("2023-07-01")) }
it "returns optional fields" do
expect(lettings_log.optional_fields).to eq(%w[
first_time_property_let_as_social_housing
tenancycode
propcode
tenancylength
address_line2
county
postcode_full
])
end
end
end end
describe "resetting invalidated fields" do describe "resetting invalidated fields" do
let(:scheme) { FactoryBot.create(:scheme, owning_organisation: created_by_user.organisation) } let(:scheme) { create(:scheme, owning_organisation: created_by_user.organisation) }
let(:location) { FactoryBot.create(:location, location_code: "E07000223", scheme:) } let(:location) { create(:location, location_code: "E07000223", scheme:) }
let(:lettings_log) do let(:lettings_log) do
FactoryBot.create( create(
:lettings_log, :lettings_log,
renewal: 0, renewal: 0,
rsnvac: 5, rsnvac: 5,
@ -2084,14 +2113,14 @@ RSpec.describe LettingsLog do
end end
context "when a question that has already been answered, no longer has met dependencies" do context "when a question that has already been answered, no longer has met dependencies" do
let(:lettings_log) { FactoryBot.create(:lettings_log, :in_progress, cbl: 1, preg_occ: 2, wchair: 2) } let(:lettings_log) { create(:lettings_log, :in_progress, cbl: 1, preg_occ: 2, wchair: 2) }
it "clears the answer" do it "clears the answer" do
expect { lettings_log.update!(preg_occ: nil) }.to change(lettings_log, :cbl).from(1).to(nil) expect { lettings_log.update!(preg_occ: nil) }.to change(lettings_log, :cbl).from(1).to(nil)
end end
context "when the question type does not have answer options" do context "when the question type does not have answer options" do
let(:lettings_log) { FactoryBot.create(:lettings_log, :in_progress, housingneeds_a: 1, age1: 19) } let(:lettings_log) { create(:lettings_log, :in_progress, housingneeds_a: 1, age1: 19) }
it "clears the answer" do it "clears the answer" do
expect { lettings_log.update!(housingneeds_a: 0) }.to change(lettings_log, :age1).from(19).to(nil) expect { lettings_log.update!(housingneeds_a: 0) }.to change(lettings_log, :age1).from(19).to(nil)
@ -2099,7 +2128,7 @@ RSpec.describe LettingsLog do
end end
context "when the question type has answer options" do context "when the question type has answer options" do
let(:lettings_log) { FactoryBot.create(:lettings_log, :in_progress, illness: 1, illness_type_1: 1) } let(:lettings_log) { create(:lettings_log, :in_progress, illness: 1, illness_type_1: 1) }
it "clears the answer" do it "clears the answer" do
expect { lettings_log.update!(illness: 2) }.to change(lettings_log, :illness_type_1).from(1).to(nil) expect { lettings_log.update!(illness: 2) }.to change(lettings_log, :illness_type_1).from(1).to(nil)
@ -2108,7 +2137,7 @@ RSpec.describe LettingsLog do
end end
context "with two pages having the same question key, only one's dependency is met" do context "with two pages having the same question key, only one's dependency is met" do
let(:lettings_log) { FactoryBot.create(:lettings_log, :in_progress, cbl: 0, preg_occ: 2, wchair: 2) } let(:lettings_log) { create(:lettings_log, :in_progress, cbl: 0, preg_occ: 2, wchair: 2) }
it "does not clear the value for answers that apply to both pages" do it "does not clear the value for answers that apply to both pages" do
expect(lettings_log.cbl).to eq(0) expect(lettings_log.cbl).to eq(0)
@ -2123,7 +2152,7 @@ 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, needstype: 1, renewal: 0) } let(:lettings_log) { create(:lettings_log, :in_progress, period: 2, needstype: 1, renewal: 0) }
it "does not clear the answer value" do it "does not clear the answer value" do
lettings_log.update!({ unitletas: 1 }) lettings_log.update!({ unitletas: 1 })
@ -2133,7 +2162,7 @@ RSpec.describe LettingsLog do
end end
context "when the lettings log does not have a valid form set yet" do context "when the lettings log does not have a valid form set yet" do
let(:lettings_log) { FactoryBot.create(:lettings_log) } let(:lettings_log) { create(:lettings_log) }
it "does not throw an error" do it "does not throw an error" do
expect { lettings_log.update(startdate: Time.zone.local(2015, 1, 1)) }.not_to raise_error expect { lettings_log.update(startdate: Time.zone.local(2015, 1, 1)) }.not_to raise_error
@ -2141,7 +2170,7 @@ RSpec.describe LettingsLog do
end end
context "when it changes from a renewal to not a renewal" do context "when it changes from a renewal to not a renewal" do
let(:lettings_log) { FactoryBot.create(:lettings_log) } let(:lettings_log) { create(:lettings_log) }
it "resets inferred waityear value" do it "resets inferred waityear value" do
lettings_log.update!({ renewal: 1 }) lettings_log.update!({ renewal: 1 })
@ -2173,8 +2202,8 @@ RSpec.describe LettingsLog do
end end
context "when it changes from a supported housing to not a supported housing" do context "when it changes from a supported housing to not a supported housing" do
let(:location) { FactoryBot.create(:location, mobility_type: "A", postcode: "SW1P 4DG") } let(:location) { create(:location, mobility_type: "A", postcode: "SW1P 4DG") }
let(:lettings_log) { FactoryBot.create(:lettings_log, location:) } let(:lettings_log) { create(:lettings_log, location:) }
it "resets inferred wchair value" do it "resets inferred wchair value" do
lettings_log.update!({ needstype: 2 }) lettings_log.update!({ needstype: 2 })
@ -2203,7 +2232,7 @@ RSpec.describe LettingsLog do
end end
context "when it is not a renewal" do context "when it is not a renewal" do
let(:lettings_log) { FactoryBot.create(:lettings_log) } let(:lettings_log) { create(:lettings_log) }
it "saves waityear value" do it "saves waityear value" do
lettings_log.update!({ renewal: 0, waityear: 2 }) lettings_log.update!({ renewal: 0, waityear: 2 })
@ -2215,13 +2244,13 @@ RSpec.describe LettingsLog do
end end
context "when a support user changes the owning organisation of the log" do context "when a support user changes the owning organisation of the log" do
let(:lettings_log) { FactoryBot.create(:lettings_log, created_by: created_by_user) } let(:lettings_log) { create(:lettings_log, created_by: created_by_user) }
let(:organisation_2) { FactoryBot.create(:organisation) } let(:organisation_2) { create(:organisation) }
context "when the organisation selected doesn't match the scheme set" do context "when the organisation selected doesn't match the scheme set" do
let(:scheme) { FactoryBot.create(:scheme, owning_organisation: created_by_user.organisation) } let(:scheme) { create(:scheme, owning_organisation: created_by_user.organisation) }
let(:location) { FactoryBot.create(:location, scheme:) } let(:location) { create(:location, scheme:) }
let(:lettings_log) { FactoryBot.create(:lettings_log, owning_organisation: nil, needstype: 2, scheme_id: scheme.id) } let(:lettings_log) { create(:lettings_log, owning_organisation: nil, needstype: 2, scheme_id: scheme.id) }
it "clears the scheme value" do it "clears the scheme value" do
lettings_log.update!(owning_organisation: organisation_2) lettings_log.update!(owning_organisation: organisation_2)
@ -2230,9 +2259,9 @@ RSpec.describe LettingsLog do
end end
context "when the organisation selected still matches the scheme set" do context "when the organisation selected still matches the scheme set" do
let(:scheme) { FactoryBot.create(:scheme, owning_organisation: organisation_2) } let(:scheme) { create(:scheme, owning_organisation: organisation_2) }
let(:location) { FactoryBot.create(:location, scheme:) } let(:location) { create(:location, scheme:) }
let(:lettings_log) { FactoryBot.create(:lettings_log, owning_organisation: nil, needstype: 2, scheme_id: scheme.id) } let(:lettings_log) { create(:lettings_log, owning_organisation: nil, needstype: 2, scheme_id: scheme.id) }
it "does not clear the scheme value" do it "does not clear the scheme value" do
lettings_log.update!(owning_organisation: organisation_2) lettings_log.update!(owning_organisation: organisation_2)
@ -2319,7 +2348,7 @@ RSpec.describe LettingsLog do
describe "tshortfall_unknown?" do describe "tshortfall_unknown?" do
context "when tshortfall is nil" do context "when tshortfall is nil" do
let(:lettings_log) { FactoryBot.create(:lettings_log, :in_progress, tshortfall_known: nil) } let(:lettings_log) { create(:lettings_log, :in_progress, tshortfall_known: nil) }
it "returns false" do it "returns false" do
expect(lettings_log.tshortfall_unknown?).to be false expect(lettings_log.tshortfall_unknown?).to be false
@ -2327,7 +2356,7 @@ RSpec.describe LettingsLog do
end end
context "when tshortfall is No" do context "when tshortfall is No" do
let(:lettings_log) { FactoryBot.create(:lettings_log, :in_progress, tshortfall_known: 1) } let(:lettings_log) { create(:lettings_log, :in_progress, tshortfall_known: 1) }
it "returns false" do it "returns false" do
expect(lettings_log.tshortfall_unknown?).to be true expect(lettings_log.tshortfall_unknown?).to be true
@ -2335,7 +2364,7 @@ RSpec.describe LettingsLog do
end end
context "when tshortfall is Yes" do context "when tshortfall is Yes" do
let(:lettings_log) { FactoryBot.create(:lettings_log, :in_progress, tshortfall_known: 0) } let(:lettings_log) { create(:lettings_log, :in_progress, tshortfall_known: 0) }
it "returns false" do it "returns false" do
expect(lettings_log.tshortfall_unknown?).to be false expect(lettings_log.tshortfall_unknown?).to be false
@ -2344,7 +2373,7 @@ RSpec.describe LettingsLog do
end end
describe "paper trail" do describe "paper trail" do
let(:lettings_log) { FactoryBot.create(:lettings_log, :in_progress) } let(:lettings_log) { create(:lettings_log, :in_progress) }
it "creates a record of changes to a log" do it "creates a record of changes to a log" do
expect { lettings_log.update!(age1: 64) }.to change(lettings_log.versions, :count).by(1) expect { lettings_log.update!(age1: 64) }.to change(lettings_log.versions, :count).by(1)
@ -2357,7 +2386,7 @@ RSpec.describe LettingsLog do
end end
describe "soft values for period" do describe "soft values for period" do
let(:lettings_log) { FactoryBot.create(:lettings_log) } let(:lettings_log) { create(:lettings_log) }
before do before do
LaRentRange.create!( LaRentRange.create!(
@ -2404,12 +2433,12 @@ RSpec.describe LettingsLog do
end end
describe "scopes" do describe "scopes" do
let!(:lettings_log_1) { FactoryBot.create(:lettings_log, :in_progress, startdate: Time.utc(2021, 5, 3), created_by: created_by_user) } let!(:lettings_log_1) { create(:lettings_log, :in_progress, startdate: Time.utc(2021, 5, 3), created_by: created_by_user) }
let!(:lettings_log_2) { FactoryBot.create(:lettings_log, :completed, startdate: Time.utc(2021, 5, 3), created_by: created_by_user) } let!(:lettings_log_2) { create(:lettings_log, :completed, startdate: Time.utc(2021, 5, 3), created_by: created_by_user) }
before do before do
Timecop.freeze(Time.utc(2022, 6, 3)) Timecop.freeze(Time.utc(2022, 6, 3))
FactoryBot.create(:lettings_log, startdate: Time.utc(2022, 6, 3)) create(:lettings_log, startdate: Time.utc(2022, 6, 3))
end end
after do after do
@ -2417,10 +2446,10 @@ RSpec.describe LettingsLog do
end end
context "when searching logs" do context "when searching logs" do
let!(:lettings_log_to_search) { FactoryBot.create(:lettings_log, :completed) } let!(:lettings_log_to_search) { create(:lettings_log, :completed) }
before do before do
FactoryBot.create_list(:lettings_log, 5, :completed) create_list(:lettings_log, 5, :completed)
end end
describe "#filter_by_id" do describe "#filter_by_id" do
@ -2475,7 +2504,7 @@ RSpec.describe LettingsLog do
end end
context "when lettings log is supported housing" do context "when lettings log is supported housing" do
let(:location) { FactoryBot.create(:location, postcode: "W6 0ST") } let(:location) { create(:location, postcode: "W6 0ST") }
before do before do
lettings_log_to_search.update!(needstype: 2, location:) lettings_log_to_search.update!(needstype: 2, location:)
@ -2515,7 +2544,7 @@ RSpec.describe LettingsLog do
end end
context "when lettings log is supported housing" do context "when lettings log is supported housing" do
let(:location) { FactoryBot.create(:location, postcode: "W6 0ST") } let(:location) { create(:location, postcode: "W6 0ST") }
before do before do
lettings_log_to_search.update!(needstype: 2, location:) lettings_log_to_search.update!(needstype: 2, location:)
@ -2573,15 +2602,15 @@ RSpec.describe LettingsLog do
end end
context "when filtering by organisation" do context "when filtering by organisation" do
let(:organisation_1) { FactoryBot.create(:organisation) } let(:organisation_1) { create(:organisation) }
let(:organisation_2) { FactoryBot.create(:organisation) } let(:organisation_2) { create(:organisation) }
let(:organisation_3) { FactoryBot.create(:organisation) } let(:organisation_3) { create(:organisation) }
before do before do
FactoryBot.create(:lettings_log, :in_progress, owning_organisation: organisation_1, managing_organisation: organisation_1, created_by: nil) create(:lettings_log, :in_progress, owning_organisation: organisation_1, managing_organisation: organisation_1, created_by: nil)
FactoryBot.create(:lettings_log, :completed, owning_organisation: organisation_1, managing_organisation: organisation_2, created_by: nil) create(:lettings_log, :completed, owning_organisation: organisation_1, managing_organisation: organisation_2, created_by: nil)
FactoryBot.create(:lettings_log, :completed, owning_organisation: organisation_2, managing_organisation: organisation_1, created_by: nil) create(:lettings_log, :completed, owning_organisation: organisation_2, managing_organisation: organisation_1, created_by: nil)
FactoryBot.create(:lettings_log, :completed, owning_organisation: organisation_2, managing_organisation: organisation_2, created_by: nil) create(:lettings_log, :completed, owning_organisation: organisation_2, managing_organisation: organisation_2, created_by: nil)
end end
it "filters by given organisation" do it "filters by given organisation" do
@ -2623,7 +2652,7 @@ RSpec.describe LettingsLog do
describe "#retirement_age_for_person" do describe "#retirement_age_for_person" do
context "when a person gender is Male" do context "when a person gender is Male" do
let(:lettings_log) { FactoryBot.build(:lettings_log, sex1: "M") } let(:lettings_log) { build(:lettings_log, sex1: "M") }
it "returns the expected retirement age" do it "returns the expected retirement age" do
expect(lettings_log.retirement_age_for_person_1).to eq(67) expect(lettings_log.retirement_age_for_person_1).to eq(67)
@ -2635,7 +2664,7 @@ RSpec.describe LettingsLog do
end end
context "when a person gender is Female" do context "when a person gender is Female" do
let(:lettings_log) { FactoryBot.build(:lettings_log, sex2: "F") } let(:lettings_log) { build(:lettings_log, sex2: "F") }
it "returns the expected retirement age" do it "returns the expected retirement age" do
expect(lettings_log.retirement_age_for_person_2).to eq(60) expect(lettings_log.retirement_age_for_person_2).to eq(60)
@ -2647,7 +2676,7 @@ RSpec.describe LettingsLog do
end end
context "when a person gender is Non-Binary" do context "when a person gender is Non-Binary" do
let(:lettings_log) { FactoryBot.build(:lettings_log, sex3: "X") } let(:lettings_log) { build(:lettings_log, sex3: "X") }
it "returns the expected retirement age" do it "returns the expected retirement age" do
expect(lettings_log.retirement_age_for_person_3).to eq(67) expect(lettings_log.retirement_age_for_person_3).to eq(67)
@ -2659,7 +2688,7 @@ RSpec.describe LettingsLog do
end end
context "when the person gender is not set" do context "when the person gender is not set" do
let(:lettings_log) { FactoryBot.build(:lettings_log) } let(:lettings_log) { build(:lettings_log) }
it "returns nil" do it "returns nil" do
expect(lettings_log.retirement_age_for_person_3).to be_nil expect(lettings_log.retirement_age_for_person_3).to be_nil
@ -2671,7 +2700,7 @@ RSpec.describe LettingsLog do
end end
context "when a postcode contains unicode characters" do context "when a postcode contains unicode characters" do
let(:lettings_log) { FactoryBot.build(:lettings_log, postcode_full: "SR81LS\u00A0") } let(:lettings_log) { build(:lettings_log, postcode_full: "SR81LS\u00A0") }
it "triggers a validation error" do it "triggers a validation error" do
expect { lettings_log.save! }.to raise_error(ActiveRecord::RecordInvalid, /Enter a postcode in the correct format/) expect { lettings_log.save! }.to raise_error(ActiveRecord::RecordInvalid, /Enter a postcode in the correct format/)
@ -2680,9 +2709,9 @@ RSpec.describe LettingsLog do
end end
describe "csv download" do describe "csv download" do
let(:scheme) { FactoryBot.create(:scheme) } let(:scheme) { create(:scheme) }
let(:location) { FactoryBot.create(:location, :export, scheme:, type_of_unit: 6, postcode: "SE11TE", startdate: Time.zone.local(2021, 10, 1)) } let(:location) { create(:location, :export, scheme:, type_of_unit: 6, postcode: "SE11TE", startdate: Time.zone.local(2021, 10, 1)) }
let(:user) { FactoryBot.create(:user, organisation: location.scheme.owning_organisation) } let(:user) { create(:user, organisation: location.scheme.owning_organisation) }
let(:expected_content) { csv_export_file.read } let(:expected_content) { csv_export_file.read }
after do after do

87
spec/models/sales_log_spec.rb

@ -1,6 +1,7 @@
require "rails_helper" require "rails_helper"
require "shared/shared_examples_for_derived_fields" require "shared/shared_examples_for_derived_fields"
# rubocop:disable RSpec/AnyInstance
RSpec.describe SalesLog, type: :model do RSpec.describe SalesLog, type: :model do
let(:owning_organisation) { create(:organisation) } let(:owning_organisation) { create(:organisation) }
let(:created_by_user) { create(:user) } let(:created_by_user) { create(:user) }
@ -36,7 +37,7 @@ RSpec.describe SalesLog, type: :model do
end end
describe "#update" do describe "#update" do
let(:sales_log) { FactoryBot.create(:sales_log, created_by: created_by_user) } let(:sales_log) { create(:sales_log, created_by: created_by_user) }
let(:validator) { sales_log._validators[nil].first } let(:validator) { sales_log._validators[nil].first }
after do after do
@ -49,10 +50,34 @@ RSpec.describe SalesLog, type: :model do
end end
describe "#optional_fields" do describe "#optional_fields" do
let(:sales_log) { build(:sales_log) } context "when saledate is before 2023" do
let(:sales_log) { build(:sales_log, saledate: Time.zone.parse("2022-07-01")) }
it "returns optional fields" do it "returns optional fields" do
expect(sales_log.optional_fields).to eq(%w[saledate_check purchid monthly_charges_value_check old_persons_shared_ownership_value_check]) expect(sales_log.optional_fields).to eq(%w[
saledate_check
purchid
monthly_charges_value_check
old_persons_shared_ownership_value_check
proplen
])
end
end
context "when saledate is after 2023" do
let(:sales_log) { build(:sales_log, saledate: Time.zone.parse("2023-07-01")) }
it "returns optional fields" do
expect(sales_log.optional_fields).to eq(%w[
saledate_check
purchid
monthly_charges_value_check
old_persons_shared_ownership_value_check
address_line2
county
postcode_full
])
end
end end
end end
@ -135,7 +160,7 @@ RSpec.describe SalesLog, type: :model do
end end
describe "derived variables" do describe "derived variables" do
let(:sales_log) { FactoryBot.create(:sales_log, :completed) } let(:sales_log) { create(:sales_log, :completed) }
it "correctly derives and saves exday, exmonth and exyear" do it "correctly derives and saves exday, exmonth and exyear" do
sales_log.update!(exdate: Time.gm(2022, 5, 4), saledate: Time.gm(2022, 7, 4), ownershipsch: 1, staircase: 2, resale: 2) sales_log.update!(exdate: Time.gm(2022, 5, 4), saledate: Time.gm(2022, 7, 4), ownershipsch: 1, staircase: 2, resale: 2)
@ -192,7 +217,7 @@ RSpec.describe SalesLog, type: :model do
end end
let!(:address_sales_log) do let!(:address_sales_log) do
FactoryBot.create( create(
:sales_log, :sales_log,
:completed, :completed,
owning_organisation:, owning_organisation:,
@ -336,7 +361,7 @@ RSpec.describe SalesLog, type: :model do
context "when deriving household variables" do context "when deriving household variables" do
let!(:sales_log) do let!(:sales_log) do
FactoryBot.create( create(
:sales_log, :sales_log,
:completed, :completed,
jointpur: 1, jointpur: 1,
@ -463,7 +488,7 @@ RSpec.describe SalesLog, type: :model do
end end
describe "#field_formatted_as_currency" do describe "#field_formatted_as_currency" do
let(:completed_sales_log) { FactoryBot.create(:sales_log, :completed) } let(:completed_sales_log) { create(:sales_log, :completed) }
it "returns small numbers correctly formatted as currency" do it "returns small numbers correctly formatted as currency" do
completed_sales_log.update!(savings: 4) completed_sales_log.update!(savings: 4)
@ -483,4 +508,52 @@ RSpec.describe SalesLog, type: :model do
expect(completed_sales_log.field_formatted_as_currency("savings")).to eq("£400,000,000.00") expect(completed_sales_log.field_formatted_as_currency("savings")).to eq("£400,000,000.00")
end end
end end
describe "#process_uprn_change!" do
context "when UPRN set to a value" do
let(:sales_log) { create(:sales_log, uprn: "123456789", uprn_confirmed: 1) }
it "updates sales log fields" do
sales_log.uprn = "1111111"
allow_any_instance_of(UprnClient).to receive(:call)
allow_any_instance_of(UprnClient).to receive(:result).and_return({
"UPRN" => "UPRN",
"UDPRN" => "UDPRN",
"ADDRESS" => "full address",
"SUB_BUILDING_NAME" => "0",
"BUILDING_NAME" => "building name",
"THOROUGHFARE_NAME" => "thoroughfare",
"POST_TOWN" => "posttown",
"POSTCODE" => "postcode",
})
expect { sales_log.process_uprn_change! }.to change(sales_log, :address_line1).from(nil).to("0, Building Name, Thoroughfare")
.and change(sales_log, :town_or_city).from(nil).to("Posttown")
.and change(sales_log, :postcode_full).from(nil).to("POSTCODE")
.and change(sales_log, :uprn_confirmed).from(1).to(nil)
end
end
context "when UPRN nil" do
let(:sales_log) { create(:sales_log, uprn: nil) }
it "does not update sales log" do
expect { sales_log.process_uprn_change! }.not_to change(sales_log, :attributes)
end
end
context "when service errors" do
let(:sales_log) { create(:sales_log, uprn: "123456789", uprn_confirmed: 1) }
let(:error_message) { "error" }
it "adds error to sales log" do
allow_any_instance_of(UprnClient).to receive(:call)
allow_any_instance_of(UprnClient).to receive(:error).and_return(error_message)
expect { sales_log.process_uprn_change! }.to change { sales_log.errors[:uprn] }.from([]).to([error_message])
end
end
end
end end
# rubocop:enable RSpec/AnyInstance

29
spec/models/validations/property_validations_spec.rb

@ -290,4 +290,33 @@ RSpec.describe Validations::PropertyValidations do
end end
end end
end end
describe "#validate_uprn" do
context "when within length limit but alphanumeric" do
let(:record) { build(:sales_log, uprn: "123abc") }
it "adds an error" do
property_validator.validate_uprn(record)
expect(record.errors.added?(:uprn, "UPRN must be 12 digits or less")).to be true
end
end
context "when over the length limit" do
let(:record) { build(:sales_log, uprn: "1234567890123") }
it "adds an error" do
property_validator.validate_uprn(record)
expect(record.errors.added?(:uprn, "UPRN must be 12 digits or less")).to be true
end
end
context "when within the limit and only numeric" do
let(:record) { build(:sales_log, uprn: "123456789012") }
it "does not add an error" do
property_validator.validate_uprn(record)
expect(record.errors).not_to be_present
end
end
end
end end

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

@ -7,7 +7,7 @@ RSpec.describe Validations::Sales::PropertyValidations do
describe "#validate_postcodes_match_if_discounted_ownership" do describe "#validate_postcodes_match_if_discounted_ownership" do
context "when ownership scheme is not discounted ownership" do context "when ownership scheme is not discounted ownership" do
let(:record) { FactoryBot.build(:sales_log, ownershipsch: 1) } let(:record) { build(:sales_log, ownershipsch: 1) }
it "when postcodes match no error is added" do it "when postcodes match no error is added" do
record.postcode_full = "SW1A 1AA" record.postcode_full = "SW1A 1AA"
@ -19,7 +19,7 @@ RSpec.describe Validations::Sales::PropertyValidations do
end end
context "when ownership scheme is discounted ownership" do context "when ownership scheme is discounted ownership" do
let(:record) { FactoryBot.build(:sales_log, ownershipsch: 2) } let(:record) { build(:sales_log, ownershipsch: 2) }
it "when ppostcode_full is not present no error is added" do it "when ppostcode_full is not present no error is added" do
record.postcode_full = "SW1A 1AA" record.postcode_full = "SW1A 1AA"
@ -51,7 +51,7 @@ RSpec.describe Validations::Sales::PropertyValidations do
describe "#validate_property_unit_type" do describe "#validate_property_unit_type" do
context "when number of bedrooms is 1" do context "when number of bedrooms is 1" do
let(:record) { FactoryBot.build(:sales_log, beds: 1, proptype: 2) } let(:record) { build(:sales_log, beds: 1, proptype: 2) }
it "does not add an error if it's a bedsit" do it "does not add an error if it's a bedsit" do
property_validator.validate_bedsit_number_of_beds(record) property_validator.validate_bedsit_number_of_beds(record)
@ -60,7 +60,7 @@ RSpec.describe Validations::Sales::PropertyValidations do
end end
context "when number of bedrooms is > 1" do context "when number of bedrooms is > 1" do
let(:record) { FactoryBot.build(:sales_log, beds: 2, proptype: 2) } let(:record) { build(:sales_log, beds: 2, proptype: 2) }
it "does add an error if it's a bedsit" do it "does add an error if it's a bedsit" do
property_validator.validate_bedsit_number_of_beds(record) property_validator.validate_bedsit_number_of_beds(record)
@ -76,7 +76,7 @@ RSpec.describe Validations::Sales::PropertyValidations do
end end
context "when number of bedrooms is undefined" do context "when number of bedrooms is undefined" do
let(:record) { FactoryBot.build(:sales_log, beds: nil, proptype: 2) } let(:record) { build(:sales_log, beds: nil, proptype: 2) }
it "does not add an error if it's a bedsit" do it "does not add an error if it's a bedsit" do
property_validator.validate_bedsit_number_of_beds(record) property_validator.validate_bedsit_number_of_beds(record)
@ -84,4 +84,33 @@ RSpec.describe Validations::Sales::PropertyValidations do
end end
end end
end end
describe "#validate_uprn" do
context "when within length limit but alphanumeric" do
let(:record) { build(:sales_log, uprn: "123abc") }
it "adds an error" do
property_validator.validate_uprn(record)
expect(record.errors.added?(:uprn, "UPRN must be 12 digits or less")).to be true
end
end
context "when over the length limit" do
let(:record) { build(:sales_log, uprn: "1234567890123") }
it "adds an error" do
property_validator.validate_uprn(record)
expect(record.errors.added?(:uprn, "UPRN must be 12 digits or less")).to be true
end
end
context "when within the limit and only numeric" do
let(:record) { build(:sales_log, uprn: "123456789012") }
it "does not add an error" do
property_validator.validate_uprn(record)
expect(record.errors).not_to be_present
end
end
end
end end

9
spec/services/csv/lettings_log_csv_service_spec.rb

@ -201,6 +201,13 @@ RSpec.describe Csv::LettingsLogCsvService do
vacdays vacdays
unresolved unresolved
updated_by_id updated_by_id
uprn
uprn_known
uprn_confirmed
address_line1
address_line2
town_or_city
county
unittype_sh unittype_sh
scheme_code scheme_code
scheme_service_name scheme_service_name
@ -222,7 +229,9 @@ RSpec.describe Csv::LettingsLogCsvService do
location_mobility_type location_mobility_type
location_admin_district location_admin_district
location_startdate] location_startdate]
csv = CSV.parse(described_class.new(user, export_type: "labels").to_csv) csv = CSV.parse(described_class.new(user, export_type: "labels").to_csv)
expect(csv.first).to eq(expected_csv_attributes) expect(csv.first).to eq(expected_csv_attributes)
end end
end end

68
spec/services/uprn_client_spec.rb

@ -0,0 +1,68 @@
require "rails_helper"
describe UprnClient do
let(:client) { described_class.new("123") }
let(:valid_response) do
{ results: [{ DPA: { postcode: "12345" } }] }.to_json
end
def stub_api_request(body:, status: 200)
stub_request(:get, "https://api.os.uk/search/places/v1/uprn?key=OS_DATA_KEY&uprn=123")
.to_return(status:, body:, headers: {})
end
describe "call" do
context "when json parse error" do
before do
stub_api_request(body: "{", status: 200)
client.call
end
it "returns error" do
expect(client.error).to eq("UPRN is not recognised. Check the number, or enter the address")
end
end
context "when http error" do
before do
stub_api_request(body: valid_response, status: 500)
client.call
end
it "returns error" do
expect(client.error).to eq("UPRN is not recognised. Check the number, or enter the address")
end
end
context "when results empty" do
before do
stub_api_request(body: {}.to_json)
client.call
end
it "returns error" do
expect(client.error).to eq("UPRN is not recognised. Check the number, or enter the address")
end
end
context "with results" do
before do
stub_api_request(body: valid_response)
client.call
end
it "returns result" do
expect(client.result).to eq({ "postcode" => "12345" })
end
it "returns no error" do
expect(client.error).to be_nil
end
end
end
end

66
spec/services/uprn_data_presenter_spec.rb

@ -0,0 +1,66 @@
require "rails_helper"
describe UprnDataPresenter do
let(:data) do
JSON.parse(
'{
"UPRN": "UPRN",
"UDPRN": "UDPRN",
"ADDRESS": "full address",
"SUB_BUILDING_NAME": "0",
"BUILDING_NAME": "building name",
"THOROUGHFARE_NAME": "thoroughfare",
"POST_TOWN": "posttown",
"POSTCODE": "postcode",
"STATUS": "APPROVED",
"DOUBLE_DEPENDENT_LOCALITY": "double dependent locality",
"DEPENDENT_LOCALITY": "dependent locality",
"CLASSIFICATION_CODE": "classification code",
"LOCAL_CUSTODIAN_CODE_DESCRIPTION": "LONDON BOROUGH OF HARINGEY",
"BLPU_STATE_CODE": "2",
"BLPU_STATE_CODE_DESCRIPTION": "In use",
"LAST_UPDATE_DATE": "31/07/2020",
"ENTRY_DATE": "30/01/2015",
"BLPU_STATE_DATE": "30/01/2015",
"LANGUAGE": "EN",
"MATCH_DESCRIPTION": "EXACT"
}',
)
end
let(:presenter) { described_class.new(data) }
describe "#postcode" do
it "returns postcode" do
expect(presenter.postcode).to eq("postcode")
end
end
describe "#address_line1" do
it "returns address_line1" do
expect(presenter.address_line1).to eq("0, Building Name, Thoroughfare")
end
end
describe "#address_line2" do
it "returns address_line2" do
expect(presenter.address_line2).to eq("Double Dependent Locality, Dependent Locality")
end
end
describe "#town_or_city" do
it "returns town_or_city" do
expect(presenter.town_or_city).to eq("Posttown")
end
end
context "when address_line2 fields are missing" do
let(:data) { {} }
describe "#address_line2" do
it "returns nil" do
expect(presenter.address_line2).to be_nil
end
end
end
end
Loading…
Cancel
Save