Browse Source

Merge branch 'main' into BuArbitraryYear

pull/2839/head
Rachael Booth 6 months ago committed by GitHub
parent
commit
89ee03bce1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      Dockerfile
  2. 12
      Gemfile.lock
  3. 36
      app/controllers/form_controller.rb
  4. 9
      app/helpers/tag_helper.rb
  5. 13
      app/helpers/tasklist_helper.rb
  6. 9
      app/models/derived_variables/lettings_log_variables.rb
  7. 12
      app/models/derived_variables/sales_log_variables.rb
  8. 2
      app/models/form/sales/pages/about_staircase.rb
  9. 2
      app/models/form/sales/pages/equity.rb
  10. 17
      app/models/form/sales/pages/monthly_rent_staircasing.rb
  11. 17
      app/models/form/sales/pages/monthly_rent_staircasing_owned.rb
  12. 15
      app/models/form/sales/pages/staircase_first_time.rb
  13. 16
      app/models/form/sales/pages/staircase_initial_date.rb
  14. 18
      app/models/form/sales/pages/staircase_previous.rb
  15. 17
      app/models/form/sales/pages/staircase_sale.rb
  16. 2
      app/models/form/sales/pages/value_shared_ownership.rb
  17. 1
      app/models/form/sales/questions/equity.rb
  18. 15
      app/models/form/sales/questions/monthly_rent_after_staircasing.rb
  19. 15
      app/models/form/sales/questions/monthly_rent_before_staircasing.rb
  20. 2
      app/models/form/sales/questions/mortgageused.rb
  21. 15
      app/models/form/sales/questions/staircase_count.rb
  22. 16
      app/models/form/sales/questions/staircase_first_time.rb
  23. 11
      app/models/form/sales/questions/staircase_initial_date.rb
  24. 11
      app/models/form/sales/questions/staircase_last_date.rb
  25. 2
      app/models/form/sales/questions/staircase_sale.rb
  26. 1
      app/models/form/sales/questions/value.rb
  27. 16
      app/models/form/sales/sections/sale_information.rb
  28. 5
      app/models/form/sales/subsections/shared_ownership_initial_purchase.rb
  29. 4
      app/models/form/sales/subsections/shared_ownership_scheme.rb
  30. 35
      app/models/form/sales/subsections/shared_ownership_staircasing_transaction.rb
  31. 26
      app/models/location.rb
  32. 4
      app/models/log.rb
  33. 4
      app/models/sales_log.rb
  34. 16
      app/models/scheme.rb
  35. 17
      app/models/validations/sales/sale_information_validations.rb
  36. 4
      app/views/devise/sessions/new.html.erb
  37. 2
      app/views/devise/shared/_links.html.erb
  38. 5
      app/views/form/_checkbox_question.html.erb
  39. 8
      app/views/form/_radio_question.html.erb
  40. 28
      app/views/logs/_tasklist.html.erb
  41. 4
      config/locales/en.yml
  42. 92
      config/locales/forms/2025/sales/sale_information.en.yml
  43. 6
      config/locales/validations/sales/sale_information.en.yml
  44. 11
      db/migrate/20241114173226_add_fields_to_sales_log.rb
  45. 5
      db/schema.rb
  46. 62
      lib/tasks/count_duplicates.rake
  47. 5
      spec/factories/scheme.rb
  48. 348
      spec/features/lettings_log_spec.rb
  49. 348
      spec/features/sales_log_spec.rb
  50. 4
      spec/features/user_spec.rb
  51. 4
      spec/helpers/tag_helper_spec.rb
  52. 110
      spec/lib/tasks/count_duplicates_spec.rb
  53. 4
      spec/models/form/sales/pages/about_staircase_spec.rb
  54. 4
      spec/models/form/sales/pages/equity_spec.rb
  55. 31
      spec/models/form/sales/pages/monthly_rent_staircasing_owned_spec.rb
  56. 31
      spec/models/form/sales/pages/monthly_rent_staircasing_spec.rb
  57. 31
      spec/models/form/sales/pages/staircase_first_time_spec.rb
  58. 31
      spec/models/form/sales/pages/staircase_initial_date_spec.rb
  59. 31
      spec/models/form/sales/pages/staircase_previous_spec.rb
  60. 38
      spec/models/form/sales/pages/staircase_sale_spec.rb
  61. 2
      spec/models/form/sales/pages/value_shared_ownership_spec.rb
  62. 2
      spec/models/form/sales/questions/equity_spec.rb
  63. 37
      spec/models/form/sales/questions/monthly_rent_after_staircasing_spec.rb
  64. 37
      spec/models/form/sales/questions/monthly_rent_before_staircasing_spec.rb
  65. 98
      spec/models/form/sales/questions/mortgageused_spec.rb
  66. 33
      spec/models/form/sales/questions/staircase_count_spec.rb
  67. 40
      spec/models/form/sales/questions/staircase_first_time_spec.rb
  68. 33
      spec/models/form/sales/questions/staircase_initial_date_spec.rb
  69. 33
      spec/models/form/sales/questions/staircase_last_date_spec.rb
  70. 4
      spec/models/form/sales/questions/staircase_sale_spec.rb
  71. 6
      spec/models/form/sales/questions/value_spec.rb
  72. 11
      spec/models/form/sales/sections/sale_information_spec.rb
  73. 2
      spec/models/form/sales/subsections/shared_ownership_initial_purchase_spec.rb
  74. 36
      spec/models/location_spec.rb
  75. 40
      spec/models/validations/sales/sale_information_validations_spec.rb
  76. 2
      spec/models/validations/setup_validations_spec.rb
  77. 11
      spec/requests/form_controller_spec.rb

2
Dockerfile

@ -10,7 +10,7 @@ RUN apk add --update --no-cache tzdata && \
# build-base: compilation tools for bundle
# yarn: node package manager
# postgresql-dev: postgres driver and libraries
RUN apk add --no-cache build-base=0.5-r3 busybox=1.36.1-r7 nodejs-current=20.8.1-r0 yarn=1.22.19-r0 postgresql13-dev=13.17-r0 git=2.40.3-r0 bash=5.2.15-r5
RUN apk add --no-cache build-base=0.5-r3 busybox=1.36.1-r7 nodejs-current=20.8.1-r0 yarn=1.22.19-r0 postgresql13-dev=13.18-r0 git=2.40.3-r0 bash=5.2.15-r5
# Bundler version should be the same version as what the Gemfile.lock was bundled with
RUN gem install bundler:2.3.14 --no-document

12
Gemfile.lock

@ -246,7 +246,7 @@ GEM
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
loofah (2.22.0)
loofah (2.23.1)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.8.1)
@ -274,11 +274,11 @@ GEM
net-smtp (0.5.0)
net-protocol
nio4r (2.7.3)
nokogiri (1.16.7-arm64-darwin)
nokogiri (1.16.8-arm64-darwin)
racc (~> 1.4)
nokogiri (1.16.7-x86_64-darwin)
nokogiri (1.16.8-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.16.7-x86_64-linux)
nokogiri (1.16.8-x86_64-linux)
racc (~> 1.4)
notifications-ruby-client (6.0.0)
jwt (>= 1.5, < 3)
@ -345,9 +345,9 @@ GEM
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.0)
rails-html-sanitizer (1.6.1)
loofah (~> 2.21)
nokogiri (~> 1.14)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
rails_admin (3.1.3)
activemodel-serializers-xml (>= 1.0)
kaminari (>= 0.14, < 2.0)

36
app/controllers/form_controller.rb

@ -5,6 +5,7 @@ class FormController < ApplicationController
before_action :find_resource, only: %i[review]
before_action :find_resource_by_named_id, except: %i[review]
before_action :check_collection_period, only: %i[submit_form show_page]
before_action :set_cache_headers, only: [:show_page]
def submit_form
if @log
@ -37,8 +38,9 @@ class FormController < ApplicationController
error_attributes = @log.errors.map(&:attribute)
Rails.logger.info "User triggered validation(s) on: #{error_attributes.join(', ')}"
@subsection = form.subsection_for_page(@page)
restore_error_field_values(@page&.questions)
render "form/page"
flash[:errors] = @log.errors
flash[:log_data] = responses_for_page
redirect_to send("#{@log.class.name.underscore}_#{@page.id}_path", @log, { referrer: request.params["referrer"], original_page_id: request.params["original_page_id"], related_question_ids: request.params["related_question_ids"] })
end
else
render_not_found
@ -84,6 +86,10 @@ class FormController < ApplicationController
@questions = request.params["related_question_ids"].map { |id| @log.form.get_question(id, @log) }
render "form/check_errors"
else
if flash[:errors].present?
restore_previous_errors(flash[:errors])
restore_error_field_values(flash[:log_data])
end
render "form/page"
end
else
@ -96,13 +102,21 @@ class FormController < ApplicationController
private
def restore_error_field_values(questions)
return unless questions
def restore_error_field_values(previous_responses)
return unless previous_responses
questions.each do |question|
if question&.type == "date" && @log.attributes.key?(question.id)
@log[question.id] = @log.send("#{question.id}_was")
end
previous_responses_to_reset = previous_responses.reject do |key, _|
@log.form.get_question(key, @log)&.type == "date"
end
@log.assign_attributes(previous_responses_to_reset)
end
def restore_previous_errors(previous_errors)
return unless previous_errors
previous_errors.each do |attribute, message|
@log.errors.add attribute, message.first
end
end
@ -431,4 +445,10 @@ private
def updated_answer_from_check_errors_page?
params["check_errors"]
end
def set_cache_headers
response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0"
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "Mon, 01 Jan 1990 00:00:00 GMT"
end
end

9
app/helpers/tag_helper.rb

@ -30,8 +30,7 @@ module TagHelper
}.freeze
COLOUR = {
not_started: "grey",
cannot_start_yet: "grey",
not_started: "light-blue",
in_progress: "blue",
completed: "green",
active: "green",
@ -58,6 +57,8 @@ module TagHelper
}.freeze
def status_tag(status, classes = [])
return nil if COLOUR[status.to_sym].nil?
govuk_tag(
classes:,
colour: COLOUR[status.to_sym],
@ -65,6 +66,10 @@ module TagHelper
)
end
def status_text(status)
TEXT[status.to_sym]
end
def status_tag_from_resource(resource, classes = [])
status = resource.status
status = :active if resource.deactivates_in_a_long_time?

13
app/helpers/tasklist_helper.rb

@ -41,12 +41,19 @@ module TasklistHelper
def subsection_link(subsection, log, current_user)
if subsection.status(log) != :cannot_start_yet
next_page_path = next_page_or_check_answers(subsection, log, current_user).to_s
govuk_link_to(subsection.label, next_page_path.dasherize, aria: { describedby: subsection.id.dasherize })
govuk_link_to(subsection.label, next_page_path.dasherize, class: "govuk-task-list__link", aria: { describedby: subsection.id.dasherize })
else
subsection.label
end
end
def subsection_href(subsection, log, current_user)
if subsection.status(log) != :cannot_start_yet
next_page_path = next_page_or_check_answers(subsection, log, current_user).to_s
next_page_path.dasherize
end
end
def review_log_text(log)
if log.collection_period_open?
path = log.sales? ? review_sales_log_path(id: log, sales_log: true) : review_lettings_log_path(log)
@ -59,6 +66,10 @@ module TasklistHelper
end
end
def tasklist_link_class(status)
status == :cannot_start_yet ? "" : "govuk-task-list__item--with-link"
end
private
def breadcrumb_organisation(log)

9
app/models/derived_variables/lettings_log_variables.rb

@ -84,6 +84,15 @@ module DerivedVariables::LettingsLogVariables
if uprn_known&.zero?
self.uprn = nil
if uprn_known_was == 1
self.address_line1 = nil
self.address_line2 = nil
self.town_or_city = nil
self.county = nil
self.postcode_known = nil
self.postcode_full = nil
self.la = nil
end
end
if uprn_known == 1 && uprn_confirmed&.zero?

12
app/models/derived_variables/sales_log_variables.rb

@ -53,6 +53,15 @@ module DerivedVariables::SalesLogVariables
if uprn_known&.zero?
self.uprn = nil
if uprn_known_was == 1
self.address_line1 = nil
self.address_line2 = nil
self.town_or_city = nil
self.county = nil
self.pcodenk = nil
self.postcode_full = nil
self.la = nil
end
end
if uprn_known == 1 && uprn_confirmed&.zero?
@ -80,6 +89,9 @@ module DerivedVariables::SalesLogVariables
self.is_la_inferred = false
end
self.numstair = is_firststair? ? 1 : nil if numstair == 1 && firststair_changed?
self.mrent = 0 if stairowned_100?
set_encoded_derived_values!(DEPENDENCIES)
end

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

@ -18,7 +18,7 @@ class Form::Sales::Pages::AboutStaircase < ::Form::Page
end
def staircase_sale_question
if form.start_date.year >= 2023
if [2023, 2024].include?(form.start_date.year)
Form::Sales::Questions::StaircaseSale.new(nil, nil, self)
end
end

2
app/models/form/sales/pages/equity.rb

@ -1,7 +1,7 @@
class Form::Sales::Pages::Equity < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "equity"
@copy_key = "sales.sale_information.equity"
end
def questions

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

@ -0,0 +1,17 @@
class Form::Sales::Pages::MonthlyRentStaircasing < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "monthly_rent_staircasing"
@copy_key = "sales.sale_information.mrent_staircasing"
@depends_on = [{
"stairowned_100?" => false,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::MonthlyRentBeforeStaircasing.new(nil, nil, self),
Form::Sales::Questions::MonthlyRentAfterStaircasing.new(nil, nil, self),
]
end
end

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

@ -0,0 +1,17 @@
class Form::Sales::Pages::MonthlyRentStaircasingOwned < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "monthly_rent_staircasing_owned"
@copy_key = "sales.sale_information.mrent_staircasing"
@header = ""
@depends_on = [{
"stairowned_100?" => true,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::MonthlyRentBeforeStaircasing.new(nil, nil, self),
]
end
end

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

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

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

@ -0,0 +1,16 @@
class Form::Sales::Pages::StaircaseInitialDate < ::Form::Page
def initialize(id, hsh, subsection)
super(id, hsh, subsection)
@id = "staircase_initial_date"
@header = ""
@depends_on = [{
"is_firststair?" => true,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::StaircaseInitialDate.new(nil, nil, self),
]
end
end

18
app/models/form/sales/pages/staircase_previous.rb

@ -0,0 +1,18 @@
class Form::Sales::Pages::StaircasePrevious < ::Form::Page
def initialize(id, hsh, subsection)
super(id, hsh, subsection)
@id = "staircase_previous"
@copy_key = "sales.sale_information.stairprevious"
@depends_on = [{
"is_firststair?" => false,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::StaircaseCount.new(nil, nil, self),
Form::Sales::Questions::StaircaseLastDate.new(nil, nil, self),
Form::Sales::Questions::StaircaseInitialDate.new(nil, nil, self),
]
end
end

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

@ -0,0 +1,17 @@
class Form::Sales::Pages::StaircaseSale < ::Form::Page
def initialize(id, hsh, subsection)
super(id, hsh, subsection)
@id = "staircase_sale"
@copy_key = form.start_year_2025_or_later? ? "sales.sale_information.staircasesale" : "sales.sale_information.about_staircasing.staircasesale"
@depends_on = [{
"staircase" => 1,
"stairowned" => 100,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::StaircaseSale.new(nil, nil, self),
]
end
end

2
app/models/form/sales/pages/value_shared_ownership.rb

@ -1,7 +1,7 @@
class Form::Sales::Pages::ValueSharedOwnership < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "value_shared_ownership"
@copy_key = "sales.sale_information.value"
end
def questions

1
app/models/form/sales/questions/equity.rb

@ -2,6 +2,7 @@ class Form::Sales::Questions::Equity < ::Form::Question
def initialize(id, hsh, page)
super
@id = "equity"
@copy_key = "sales.sale_information.equity.#{page.id}"
@type = "numeric"
@min = 0
@max = 100

15
app/models/form/sales/questions/monthly_rent_after_staircasing.rb

@ -0,0 +1,15 @@
class Form::Sales::Questions::MonthlyRentAfterStaircasing < ::Form::Question
def initialize(id, hsh, page)
super
@id = "mrent"
@copy_key = "sales.sale_information.mrent_staircasing.poststaircasing"
@type = "numeric"
@min = 0
@step = 0.01
@width = 5
@prefix = "£"
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end
QUESTION_NUMBER_FROM_YEAR = { 2025 => 99 }.freeze
end

15
app/models/form/sales/questions/monthly_rent_before_staircasing.rb

@ -0,0 +1,15 @@
class Form::Sales::Questions::MonthlyRentBeforeStaircasing < ::Form::Question
def initialize(id, hsh, page)
super
@id = "mrentprestaircasing"
@copy_key = "sales.sale_information.mrent_staircasing.prestaircasing"
@type = "numeric"
@min = 0
@step = 0.01
@width = 5
@prefix = "£"
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end
QUESTION_NUMBER_FROM_YEAR = { 2025 => 98 }.freeze
end

2
app/models/form/sales/questions/mortgageused.rb

@ -12,7 +12,7 @@ class Form::Sales::Questions::Mortgageused < ::Form::Question
def displayed_answer_options(log, _user = nil)
if log.outright_sale? && log.saledate && !form.start_year_2024_or_later?
answer_options_without_dont_know
elsif log.stairowned == 100 || log.outright_sale?
elsif log.stairowned_100? || log.outright_sale? || (log.is_staircase? && form.start_year_2025_or_later?)
ANSWER_OPTIONS
else
answer_options_without_dont_know

15
app/models/form/sales/questions/staircase_count.rb

@ -0,0 +1,15 @@
class Form::Sales::Questions::StaircaseCount < ::Form::Question
def initialize(id, hsh, page)
super
@id = "numstair"
@copy_key = "sales.sale_information.stairprevious.numstair"
@type = "numeric"
@width = 2
@min = 2
@max = 10
@step = 1
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end
QUESTION_NUMBER_FROM_YEAR = { 2025 => 82 }.freeze
end

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

@ -0,0 +1,16 @@
class Form::Sales::Questions::StaircaseFirstTime < ::Form::Question
def initialize(id, hsh, page)
super
@id = "firststair"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
}.freeze
QUESTION_NUMBER_FROM_YEAR = { 2025 => 81 }.freeze
end

11
app/models/form/sales/questions/staircase_initial_date.rb

@ -0,0 +1,11 @@
class Form::Sales::Questions::StaircaseInitialDate < ::Form::Question
def initialize(id, hsh, page)
super
@id = "initialpurchase"
@copy_key = "sales.sale_information.stairprevious.initialpurchase"
@type = "date"
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end
QUESTION_NUMBER_FROM_YEAR = { 2025 => 83 }.freeze
end

11
app/models/form/sales/questions/staircase_last_date.rb

@ -0,0 +1,11 @@
class Form::Sales::Questions::StaircaseLastDate < ::Form::Question
def initialize(id, hsh, page)
super
@id = "lasttransaction"
@copy_key = "sales.sale_information.stairprevious.lasttransaction"
@type = "date"
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]
end
QUESTION_NUMBER_FROM_YEAR = { 2025 => 83 }.freeze
end

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

@ -2,7 +2,7 @@ class Form::Sales::Questions::StaircaseSale < ::Form::Question
def initialize(id, hsh, page)
super
@id = "staircasesale"
@copy_key = "sales.sale_information.about_staircasing.staircasesale"
@copy_key = form.start_year_2025_or_later? ? "sales.sale_information.staircasesale" : "sales.sale_information.about_staircasing.staircasesale"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max]

1
app/models/form/sales/questions/value.rb

@ -2,6 +2,7 @@ class Form::Sales::Questions::Value < ::Form::Question
def initialize(id, hsh, page)
super
@id = "value"
@copy_key = "sales.sale_information.value.#{page.id}"
@type = "numeric"
@min = 0
@step = 1

16
app/models/form/sales/sections/sale_information.rb

@ -4,18 +4,20 @@ class Form::Sales::Sections::SaleInformation < ::Form::Section
@id = "sale_information"
@label = "Sale information"
@description = ""
@subsections = [
shared_ownership_scheme_subsection,
Form::Sales::Subsections::DiscountedOwnershipScheme.new(nil, nil, self),
Form::Sales::Subsections::OutrightSale.new(nil, nil, self),
] || []
@subsections = []
@subsections.concat(shared_ownership_scheme_subsection)
@subsections << Form::Sales::Subsections::DiscountedOwnershipScheme.new(nil, nil, self)
@subsections << Form::Sales::Subsections::OutrightSale.new(nil, nil, self)
end
def shared_ownership_scheme_subsection
if form.start_year_2025_or_later?
Form::Sales::Subsections::SharedOwnershipInitialPurchase.new(nil, nil, self)
[
Form::Sales::Subsections::SharedOwnershipInitialPurchase.new(nil, nil, self),
Form::Sales::Subsections::SharedOwnershipStaircasingTransaction.new(nil, nil, self),
]
else
Form::Sales::Subsections::SharedOwnershipScheme.new(nil, nil, self)
[Form::Sales::Subsections::SharedOwnershipScheme.new(nil, nil, self)]
end
end
end

5
app/models/form/sales/subsections/shared_ownership_initial_purchase.rb

@ -4,6 +4,7 @@ class Form::Sales::Subsections::SharedOwnershipInitialPurchase < ::Form::Subsect
@id = "shared_ownership_initial_purchase"
@label = "Shared ownership - initial purchase"
@depends_on = [{ "ownershipsch" => 1, "setup_completed?" => true, "staircase" => 2 }]
@copy_key = "sale_information"
end
def pages
@ -18,9 +19,9 @@ class Form::Sales::Subsections::SharedOwnershipInitialPurchase < ::Form::Subsect
Form::Sales::Pages::PreviousBedrooms.new(nil, nil, self),
Form::Sales::Pages::PreviousPropertyType.new(nil, nil, self),
Form::Sales::Pages::PreviousTenure.new(nil, nil, self),
Form::Sales::Pages::ValueSharedOwnership.new(nil, nil, self),
Form::Sales::Pages::ValueSharedOwnership.new("value_shared_ownership", nil, self),
Form::Sales::Pages::AboutPriceValueCheck.new("about_price_shared_ownership_value_check", nil, self),
Form::Sales::Pages::Equity.new(nil, nil, self),
Form::Sales::Pages::Equity.new("initial_equity", nil, self),
Form::Sales::Pages::SharedOwnershipDepositValueCheck.new("shared_ownership_equity_value_check", nil, self),
Form::Sales::Pages::Mortgageused.new("mortgage_used_shared_ownership", nil, self, ownershipsch: 1),
Form::Sales::Pages::MortgageValueCheck.new("mortgage_used_mortgage_value_check", nil, self),

4
app/models/form/sales/subsections/shared_ownership_scheme.rb

@ -27,9 +27,9 @@ class Form::Sales::Subsections::SharedOwnershipScheme < ::Form::Subsection
Form::Sales::Pages::PreviousBedrooms.new(nil, nil, self),
Form::Sales::Pages::PreviousPropertyType.new(nil, nil, self),
Form::Sales::Pages::PreviousTenure.new(nil, nil, self),
Form::Sales::Pages::ValueSharedOwnership.new(nil, nil, self),
Form::Sales::Pages::ValueSharedOwnership.new("value_shared_ownership", nil, self),
Form::Sales::Pages::AboutPriceValueCheck.new("about_price_shared_ownership_value_check", nil, self),
Form::Sales::Pages::Equity.new(nil, nil, self),
Form::Sales::Pages::Equity.new("equity", nil, self),
Form::Sales::Pages::SharedOwnershipDepositValueCheck.new("shared_ownership_equity_value_check", nil, self),
Form::Sales::Pages::Mortgageused.new("mortgage_used_shared_ownership", nil, self, ownershipsch: 1),
Form::Sales::Pages::MortgageValueCheck.new("mortgage_used_mortgage_value_check", nil, self),

35
app/models/form/sales/subsections/shared_ownership_staircasing_transaction.rb

@ -0,0 +1,35 @@
class Form::Sales::Subsections::SharedOwnershipStaircasingTransaction < ::Form::Subsection
def initialize(id, hsh, section)
super
@id = "shared_ownership_staircasing_transaction"
@label = "Shared ownership - staircasing transaction"
@depends_on = [{ "ownershipsch" => 1, "setup_completed?" => true, "staircase" => 1 }]
@copy_key = "sale_information"
end
def pages
@pages ||= [
Form::Sales::Pages::AboutStaircase.new("about_staircasing_joint_purchase", nil, self, joint_purchase: true),
Form::Sales::Pages::AboutStaircase.new("about_staircasing_not_joint_purchase", nil, self, joint_purchase: false),
Form::Sales::Pages::StaircaseSale.new(nil, nil, self),
Form::Sales::Pages::StaircaseBoughtValueCheck.new(nil, nil, self),
Form::Sales::Pages::StaircaseOwnedValueCheck.new("staircase_owned_value_check_joint_purchase", nil, self, joint_purchase: true),
Form::Sales::Pages::StaircaseOwnedValueCheck.new("staircase_owned_value_check_not_joint_purchase", nil, self, joint_purchase: false),
Form::Sales::Pages::StaircaseFirstTime.new(nil, nil, self),
Form::Sales::Pages::StaircasePrevious.new(nil, nil, self),
Form::Sales::Pages::StaircaseInitialDate.new(nil, nil, self),
Form::Sales::Pages::ValueSharedOwnership.new("value_shared_ownership_staircase", nil, self),
Form::Sales::Pages::AboutPriceValueCheck.new("about_price_shared_ownership_value_check", nil, self),
Form::Sales::Pages::Equity.new("staircase_equity", nil, self),
Form::Sales::Pages::SharedOwnershipDepositValueCheck.new("shared_ownership_equity_value_check", nil, self),
Form::Sales::Pages::Mortgageused.new("staircase_mortgage_used_shared_ownership", nil, self, ownershipsch: 1),
Form::Sales::Pages::MonthlyRentStaircasingOwned.new(nil, nil, self),
Form::Sales::Pages::MonthlyRentStaircasing.new(nil, nil, self),
Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_shared_ownership_value_check", nil, self),
].compact
end
def displayed_in_tasklist?(log)
log.staircase == 1 && (log.ownershipsch.nil? || log.ownershipsch == 1)
end
end

26
app/models/location.rb

@ -2,7 +2,8 @@ class Location < ApplicationRecord
validates :postcode, on: :postcode, presence: { message: I18n.t("validations.location.postcode_blank") }
validate :validate_postcode, on: :postcode, if: proc { |model| model.postcode.presence }
validates :location_admin_district, on: :location_admin_district, presence: { message: I18n.t("validations.location_admin_district") }
validates :units, on: :units, presence: { message: I18n.t("validations.location.units") }
validates :units, on: :units, presence: { message: I18n.t("validations.location.units.must_be_number") }
validates :units, on: :units, numericality: { greater_than_or_equal_to: 1, message: I18n.t("validations.location.units.must_be_one_or_more") }
validates :type_of_unit, on: :type_of_unit, presence: { message: I18n.t("validations.location.type_of_unit") }
validates :mobility_type, on: :mobility_type, presence: { message: I18n.t("validations.location.mobility_standards") }
validates :startdate, on: :startdate, presence: { message: I18n.t("validations.location.startdate_invalid") }
@ -144,6 +145,29 @@ class Location < ApplicationRecord
scope.pluck("ARRAY_AGG(id)")
}
scope :duplicate_active_sets, lambda {
scope = active
.group(*DUPLICATE_LOCATION_ATTRIBUTES)
.where.not(scheme_id: nil)
.where.not(postcode: nil)
.where.not(mobility_type: nil)
.having(
"COUNT(*) > 1",
)
scope.pluck("ARRAY_AGG(id)")
}
scope :duplicate_active_sets_within_given_schemes, lambda {
scope = active
.group(*DUPLICATE_LOCATION_ATTRIBUTES - %w[scheme_id])
.where.not(postcode: nil)
.where.not(mobility_type: nil)
.having(
"COUNT(*) > 1",
)
scope.pluck("ARRAY_AGG(id)")
}
DUPLICATE_LOCATION_ATTRIBUTES = %w[scheme_id postcode mobility_type].freeze
LOCAL_AUTHORITIES = LocalAuthority.all.map { |la| [la.name, la.code] }.to_h

4
app/models/log.rb

@ -126,9 +126,11 @@ class Log < ApplicationRecord
end
def address_options
return @address_options if @address_options
return @address_options if @address_options && @last_searched_address_string == address_string
if [address_line1_input, postcode_full_input].all?(&:present?)
@last_searched_address_string = address_string
service = AddressClient.new(address_string)
service.call
if service.result.blank? || service.error.present?

4
app/models/sales_log.rb

@ -557,4 +557,8 @@ class SalesLog < Log
def is_resale?
resale == 1
end
def is_firststair?
firststair == 1
end
end

16
app/models/scheme.rb

@ -119,6 +119,22 @@ class Scheme < ApplicationRecord
scope.pluck("ARRAY_AGG(id)")
}
scope :duplicate_active_sets, lambda {
scope = active
.group(*DUPLICATE_SCHEME_ATTRIBUTES)
.where.not(scheme_type: nil)
.where.not(registered_under_care_act: nil)
.where.not(primary_client_group: nil)
.where.not(has_other_client_group: nil)
.where.not(secondary_client_group: nil).or(where(has_other_client_group: 0))
.where.not(support_type: nil)
.where.not(intended_stay: nil)
.having(
"COUNT(*) > 1",
)
scope.pluck("ARRAY_AGG(id)")
}
validate :validate_confirmed
validate :validate_owning_organisation

17
app/models/validations/sales/sale_information_validations.rb

@ -35,6 +35,14 @@ module Validations::Sales::SaleInformationValidations
end
end
def validate_staircasing_initial_purchase_date(record)
return unless record.initialpurchase
if record.initialpurchase < Time.zone.local(1980, 1, 1)
record.errors.add :initialpurchase, I18n.t("validations.sales.sale_information.initialpurchase.must_be_after_1980")
end
end
def validate_previous_property_unit_type(record)
return unless record.fromprop && record.frombeds
@ -351,6 +359,15 @@ module Validations::Sales::SaleInformationValidations
end
end
def validate_number_of_staircase_transactions(record)
return unless record.numstair
if record.firststair == 2 && record.numstair < 2
record.errors.add :numstair, I18n.t("validations.sales.sale_information.numstair.must_be_greater_than_one")
record.errors.add :firststair, I18n.t("validations.sales.sale_information.firststair.cannot_be_no")
end
end
def over_tolerance?(expected, actual, tolerance, strict: false)
if strict
(expected - actual).abs > tolerance

4
app/views/devise/sessions/new.html.erb

@ -12,6 +12,8 @@
<%= content_for(:title) %>
</h1>
<%= render "devise/shared/links" %>
<%= f.govuk_email_field :email,
label: { text: "Email address" },
autocomplete: "email",
@ -25,5 +27,3 @@
</div>
</div>
<% end %>
<%= render "devise/shared/links" %>

2
app/views/devise/shared/_links.html.erb

@ -7,7 +7,7 @@
<% end %>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<p class="govuk-body">You can <%= govuk_link_to "reset your password", new_password_path(resource_name) %> if you’ve forgotten it.</p>
<p class="govuk-body"><%= govuk_link_to "Forgot password", new_password_path(resource_name) %></p>
<% end %>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>

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

@ -6,16 +6,17 @@
hint: { text: question.hint_text&.html_safe } do %>
<% after_divider = false %>
<% question.displayed_answer_options(@log).map do |key, option| %>
<% question.displayed_answer_options(@log).each_with_index do |(key, option), index| %>
<% if key.starts_with?("divider") %>
<% after_divider = true %>
<%= f.govuk_check_box_divider %>
<% else %>
<%= f.govuk_check_box question.id, key,
<%= f.govuk_check_box question.id.to_sym, key,
label: { text: option["value"] },
hint: { text: option["hint"] },
checked: @log[key] == 1,
exclusive: after_divider,
link_errors: index.zero? ? true : nil,
**stimulus_html_attributes(question) %>
<% end %>
<% end %>

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

@ -18,22 +18,24 @@
legend: legend(question, page_header, conditional),
hint: { text: question.hint_text&.html_safe } do %>
<% question.displayed_answer_options(@log, current_user).map do |key, options| %>
<% question.displayed_answer_options(@log, current_user).each_with_index do |(key, options), index| %>
<% if key.starts_with?("divider") %>
<%= f.govuk_radio_divider %>
<% else %>
<% conditional_question = find_conditional_question(@page, question, key) %>
<% if conditional_question.nil? %>
<%= f.govuk_radio_button question.id,
<%= f.govuk_radio_button question.id.to_sym,
key,
label: { text: options["value"] },
hint: { text: options["hint"] },
link_errors: index.zero? ? true : nil,
**stimulus_html_attributes(question) %>
<% else %>
<%= f.govuk_radio_button question.id,
<%= f.govuk_radio_button question.id.to_sym,
key,
label: { text: options["value"] },
hint: { text: options["hint"] },
link_errors: index.zero? ? true : nil,
**stimulus_html_attributes(question) do %>
<%= render partial: "#{conditional_question.type}_question", locals: {
question: conditional_question,

28
app/views/logs/_tasklist.html.erb

@ -8,19 +8,21 @@
<% if section.description %>
<p class="govuk-body"><%= section.description.html_safe %></p>
<% end %>
<ul class="app-task-list__items">
<% section.subsections.each do |subsection| %>
<% if subsection.displayed_in_tasklist?(@log) && (subsection.applicable_questions(@log).count > 0 || !subsection.enabled?(@log)) %>
<% subsection_status = subsection.status(@log) %>
<li class="app-task-list__item">
<span class="app-task-list__task-name" id="<%= subsection.id.dasherize %>">
<%= subsection_link(subsection, @log, current_user) %>
</span>
<%= status_tag(subsection_status, "app-task-list__tag") %>
</li>
<% end %>
<% end %>
</ul>
<%= govuk_task_list(id_prefix: "logs", classes: "app-task-list__items") do |task_list|
section.subsections.each do |subsection|
next unless subsection.displayed_in_tasklist?(@log) && (subsection.applicable_questions(@log).count.positive? || !subsection.enabled?(@log))
subsection_status = subsection.status(@log)
task_list.with_item(classes: "#{tasklist_link_class(subsection_status)} app-task-list__item") do |item|
item.with_title(text: subsection.label, href: subsection_href(subsection, @log, current_user), classes: "app-task-list__name-and-hint--my-modifier")
if status_tag(subsection_status, "app-task-list__tag").present?
item.with_status(text: status_tag(subsection_status), classes: "app-task-list__tag")
else
item.with_status(text: status_text(subsection_status), classes: "app-task-list__tag")
end
end
end
end %>
</li>
<% end %>
</ol>

4
config/locales/en.yml

@ -361,7 +361,9 @@ en:
location:
postcode_blank: "Enter a postcode."
units: "The units at this location must be a number."
units:
must_be_number: "The units at this location must be a number."
must_be_one_or_more: "Number of units must be at least 1."
type_of_unit: "Select the most common type of unit at this location."
mobility_standards: "Select the mobility standard for the majority of the units at this location."
startdate_invalid: "Enter a valid day, month and year when the first property became available at this location."

92
config/locales/forms/2025/sales/sale_information.en.yml

@ -26,24 +26,47 @@ en:
question_text: "Did the buyer live in the property before purchasing it?"
about_staircasing:
page_header: "About the staircasing transaction"
stairbought:
check_answer_label: "Percentage bought in this staircasing transaction"
page_header: "About the staircasing transaction"
stairbought:
check_answer_label: "Percentage bought in this staircasing transaction"
hint_text: ""
question_text: "What percentage of the property has been bought in this staircasing transaction?"
stairowned:
joint_purchase:
check_answer_label: "Percentage the buyers now own in total"
hint_text: ""
question_text: "What percentage of the property has been bought in this staircasing transaction?"
stairowned:
joint_purchase:
check_answer_label: "Percentage the buyers now own in total"
hint_text: ""
question_text: "What percentage of the property do the buyers now own in total?"
not_joint_purchase:
check_answer_label: "Percentage the buyer now owns in total"
hint_text: ""
question_text: "What percentage of the property does the buyer now own in total?"
staircasesale:
check_answer_label: "Part of a back-to-back staircasing transaction"
question_text: "What percentage of the property do the buyers now own in total?"
not_joint_purchase:
check_answer_label: "Percentage the buyer now owns in total"
hint_text: ""
question_text: "Is this transaction part of a back-to-back staircasing transaction to facilitate sale of the home on the open market?"
question_text: "What percentage of the property does the buyer now own in total?"
staircasesale:
page_header: ""
check_answer_label: "Part of a back-to-back staircasing transaction?"
hint_text: ""
question_text: "Is this transaction part of a back-to-back staircasing transaction to facilitate sale of the home on the open market?"
firststair:
page_header: ""
check_answer_label: "First time staircasing?"
hint_text: ""
question_text: "Is this the first time the shared owner has engaged in staircasing in the home?"
stairprevious:
page_header: "About previous staircasing transactions"
numstair:
check_answer_label: "Number of staircasing transactions"
hint_text: ""
question_text: "Including this time, how many times has the shared owner engaged in staircasing in the home?"
initialpurchase:
check_answer_label: "Initial staircasing transaction"
hint_text: ""
question_text: "What was the date of the initial purchase of a share in the property?"
lasttransaction:
check_answer_label: "Last staircasing transaction"
hint_text: ""
question_text: "What was the date of the last staircasing transaction?"
resale:
page_header: ""
@ -101,19 +124,29 @@ en:
value:
page_header: "About the price of the property"
check_answer_label: "Full purchase price"
hint_text: "Enter the full purchase price of the property before any discounts are applied. For shared ownership, enter the full purchase price paid for 100% equity (this is equal to the value of the share owned by the PRP plus the value bought by the purchaser)"
question_text: "What was the full purchase price?"
value_shared_ownership:
check_answer_label: "Full purchase price"
hint_text: "Enter the full purchase price of the property before any discounts are applied. For shared ownership, enter the full purchase price paid for 100% equity (this is equal to the value of the share owned by the PRP plus the value bought by the purchaser)."
question_text: "What was the full purchase price?"
value_shared_ownership_staircase:
check_answer_label: "Full purchase price"
hint_text: "Enter the full purchase price paid for the equity bought in this staircasing transaction (this is equal to the value of the share bought by the purchaser)."
question_text: "What was the full purchase price for this staircasing transaction?"
equity:
page_header: "About the price of the property"
check_answer_label: "Initial percentage equity share"
hint_text: "Enter the amount of initial equity share held by the purchaser (for example, 25% or 50%)"
question_text: "What was the initial percentage share purchased?"
initial_equity:
check_answer_label: "Initial percentage equity share"
hint_text: "Enter the amount of initial equity share held by the purchaser (for example, 25% or 50%)"
question_text: "What was the initial percentage share purchased?"
staircase_equity:
check_answer_label: "Initial percentage equity share"
hint_text: "Enter the amount of initial equity share held by the purchaser (for example, 25% or 50%)"
question_text: "What was the percentage shared purchased in the initial transaction?"
mortgageused:
page_header: "Mortgage Amount"
check_answer_label: "Mortgage used"
check_answer_label: "Mortgage used?"
hint_text: ""
question_text: "Was a mortgage used for the purchase of this property?"
@ -165,6 +198,17 @@ en:
hint_text: "Amount paid before any charges"
question_text: "What is the basic monthly rent?"
mrent_staircasing:
page_header: "Monthly rent"
prestaircasing:
check_answer_label: "Monthly rent prior to staircasing"
hint_text: "Amount paid before any charges"
question_text: "What was the basic monthly rent prior to staircasing?"
poststaircasing:
check_answer_label: "Monthly rent after staircasing"
hint_text: "Amount paid before any charges"
question_text: "What is the basic monthly rent after staircasing?"
leaseholdcharges:
page_header: ""
has_mscharge:
@ -199,7 +243,7 @@ en:
check_answer_label: "Amount of any loan, grant or subsidy"
hint_text: "For all schemes except Right to Buy (RTB), Preserved Right to Buy (PRTB), Voluntary Right to Buy (VRTB) and Rent to Buy"
question_text: "What was the amount of any loan, grant, discount or subsidy given?"
management_fee:
page_header: ""
has_management_fee:

6
config/locales/validations/sales/sale_information.en.yml

@ -19,6 +19,8 @@ en:
exdate:
must_be_before_saledate: "Contract exchange date must be before sale completion date."
must_be_less_than_1_year_from_saledate: "Contract exchange date must be less than 1 year before sale completion date."
initialpurchase:
must_be_after_1980: "The initial purchase date must be after January 1, 1980."
fromprop:
previous_property_type_bedsit: "A bedsit cannot have more than 1 bedroom."
frombeds:
@ -125,3 +127,7 @@ en:
postcode_full:
value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £136,400 for properties in London."
value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £102,400 for properties outside of London."
numstair:
must_be_greater_than_one: "The number of staircasing transactions must be greater than 1 when this is not the first staircasing transaction."
firststair:
cannot_be_no: "The answer to 'Is this the first staircasing transaction?' cannot be 'no' if the number of staircasing transactions is 1."

11
db/migrate/20241114173226_add_fields_to_sales_log.rb

@ -0,0 +1,11 @@
class AddFieldsToSalesLog < ActiveRecord::Migration[7.0]
def change
change_table :sales_logs, bulk: true do |t|
t.column :firststair, :integer
t.column :numstair, :integer
t.column :mrentprestaircasing, :decimal, precision: 10, scale: 2
t.column :lasttransaction, :datetime
t.column :initialpurchase, :datetime
end
end
end

5
db/schema.rb

@ -761,6 +761,11 @@ ActiveRecord::Schema[7.0].define(version: 2024_11_22_154743) do
t.bigint "created_by_id"
t.integer "has_management_fee"
t.decimal "management_fee", precision: 10, scale: 2
t.integer "firststair"
t.integer "numstair"
t.decimal "mrentprestaircasing", precision: 10, scale: 2
t.datetime "lasttransaction"
t.datetime "initialpurchase"
t.index ["assigned_to_id"], name: "index_sales_logs_on_assigned_to_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"

62
lib/tasks/count_duplicates.rake

@ -60,4 +60,66 @@ namespace :count_duplicates do
url = storage_service.get_presigned_url(filename, 72.hours.to_i)
Rails.logger.info("Download URL: #{url}")
end
desc "Count the number of duplicate active schemes per organisation"
task active_scheme_duplicates_per_org: :environment do
duplicates_csv = CSV.generate(headers: true) do |csv|
csv << ["Organisation id", "Number of duplicate sets", "Total duplicate schemes"]
Organisation.visible.each do |organisation|
if organisation.owned_schemes.duplicate_active_sets.count.positive?
csv << [organisation.id, organisation.owned_schemes.duplicate_active_sets.count, organisation.owned_schemes.duplicate_active_sets.sum(&:size)]
end
end
end
filename = "active-scheme-duplicates-#{Time.zone.now}.csv"
storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"])
storage_service.write_file(filename, "#{duplicates_csv}")
url = storage_service.get_presigned_url(filename, 72.hours.to_i)
Rails.logger.info("Download URL: #{url}")
end
desc "Count the number of duplicate active locations per organisation"
task active_location_duplicates_per_org: :environment do
duplicates_csv = CSV.generate(headers: true) do |csv|
csv << ["Organisation id", "Duplicate sets within individual schemes", "Duplicate locations within individual schemes", "All duplicate sets", "All duplicates"]
Organisation.visible.each do |organisation|
duplicate_sets_within_individual_schemes = []
organisation.owned_schemes.each do |scheme|
duplicate_sets_within_individual_schemes += scheme.locations.duplicate_active_sets
end
duplicate_locations_within_individual_schemes = duplicate_sets_within_individual_schemes.flatten
duplicate_sets_within_duplicate_schemes = []
if organisation.owned_schemes.duplicate_active_sets.count.positive?
organisation.owned_schemes.duplicate_active_sets.each do |duplicate_set|
duplicate_sets_within_duplicate_schemes += Location.where(scheme_id: duplicate_set).duplicate_active_sets_within_given_schemes
end
duplicate_locations_within_duplicate_schemes_ids = duplicate_sets_within_duplicate_schemes.flatten
duplicate_sets_within_individual_schemes_without_intersecting_sets = duplicate_sets_within_individual_schemes.reject { |set| set.any? { |id| duplicate_sets_within_duplicate_schemes.any? { |duplicate_set| duplicate_set.include?(id) } } }
all_duplicate_sets_count = (duplicate_sets_within_individual_schemes_without_intersecting_sets + duplicate_sets_within_duplicate_schemes).count
all_duplicate_locations_count = (duplicate_locations_within_duplicate_schemes_ids + duplicate_locations_within_individual_schemes).uniq.count
else
all_duplicate_sets_count = duplicate_sets_within_individual_schemes.count
all_duplicate_locations_count = duplicate_locations_within_individual_schemes.count
end
if all_duplicate_locations_count.positive?
csv << [organisation.id, duplicate_sets_within_individual_schemes.count, duplicate_locations_within_individual_schemes.count, all_duplicate_sets_count, all_duplicate_locations_count]
end
end
end
filename = "active-location-duplicates-#{Time.zone.now}.csv"
storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"])
storage_service.write_file(filename, "#{duplicates_csv}")
url = storage_service.get_presigned_url(filename, 72.hours.to_i)
Rails.logger.info("Download URL: #{url}")
end
end

5
spec/factories/scheme.rb

@ -44,5 +44,10 @@ FactoryBot.define do
trait :created_now do
created_at { Time.zone.now }
end
trait :with_location do
after(:create) do |scheme|
create(:location, scheme:)
end
end
end
end

348
spec/features/lettings_log_spec.rb

@ -729,5 +729,353 @@ RSpec.describe "Lettings Log Features" do
expect(duplicate_log.duplicate_set_id).to be_nil
end
end
context "when filling out address fields" do
let(:lettings_log) { create(:lettings_log, :setup_completed, assigned_to: user) }
before do
body = {
results: [
{
DPA: {
"POSTCODE": "AA1 1AA",
"POST_TOWN": "Bristol",
"ORGANISATION_NAME": "Some place",
},
},
],
}.to_json
WebMock.stub_request(:get, "https://api.os.uk/search/places/v1/uprn?dataset=DPA,LPI&key=OS_DATA_KEY&uprn=111")
.to_return(status: 200, body:, headers: {})
body = { results: [{ DPA: { UPRN: "111" } }] }.to_json
WebMock.stub_request(:get, "https://api.os.uk/search/places/v1/find?query=Address+line+1%2C+AA1+1AA&key=OS_DATA_KEY&maxresults=10&minmatch=0.4")
.to_return(status: 200, body:, headers: {})
WebMock.stub_request(:get, "https://api.postcodes.io/postcodes/AA11AA")
.to_return(status: 200, body: "{\"status\":200,\"result\":{\"postcode\":\"AA1 1AA\",\"admin_district\":\"Westminster\",\"codes\":{\"admin_district\":\"E09000033\"}}}", headers: {})
WebMock.stub_request(:get, "https://api.postcodes.io/postcodes/AA12AA")
.to_return(status: 200, body: "{\"status\":200,\"result\":{\"postcode\":\"AA1 2AA\",\"admin_district\":\"Wigan\",\"codes\":{\"admin_district\":\"E08000010\"}}}", headers: {})
body = { results: [] }.to_json
WebMock.stub_request(:get, "https://api.os.uk/search/places/v1/find?query=Address+line+1%2C+AA1+1AB&key=OS_DATA_KEY&maxresults=10&minmatch=0.4")
.to_return(status: 200, body:, headers: {})
visit("/lettings-logs/#{lettings_log.id}/uprn")
end
context "and uprn is known and answered" do
before do
choose "Yes"
fill_in("lettings_log[uprn]", with: "111")
click_button("Save and continue")
end
context "and uprn is confirmed" do
it "sets correct address fields" do
lettings_log.reload
expect(lettings_log.uprn_known).to eq(1) # yes
expect(lettings_log.uprn).to eq("111")
expect(lettings_log.uprn_confirmed).to eq(nil)
expect(lettings_log.uprn_selection).to eq(nil)
expect(lettings_log.postcode_known).to eq(1)
expect(lettings_log.postcode_full).to eq("AA1 1AA")
expect(lettings_log.address_line1).to eq("Some Place")
expect(lettings_log.address_line2).to eq(nil)
expect(lettings_log.town_or_city).to eq("Bristol")
expect(lettings_log.address_line1_input).to eq(nil)
expect(lettings_log.postcode_full_input).to eq(nil)
expect(lettings_log.address_search_value_check).to eq(nil)
expect(lettings_log.la).to eq("E09000033")
choose "Yes"
click_button("Save and continue")
lettings_log.reload
expect(lettings_log.uprn_known).to eq(1) # yes
expect(lettings_log.uprn).to eq("111")
expect(lettings_log.uprn_confirmed).to eq(1) # yes
expect(lettings_log.uprn_selection).to eq(nil)
expect(lettings_log.postcode_known).to eq(1)
expect(lettings_log.postcode_full).to eq("AA1 1AA")
expect(lettings_log.address_line1).to eq("Some Place")
expect(lettings_log.address_line2).to eq(nil)
expect(lettings_log.town_or_city).to eq("Bristol")
expect(lettings_log.address_line1_input).to eq(nil)
expect(lettings_log.postcode_full_input).to eq(nil)
expect(lettings_log.address_search_value_check).to eq(nil)
expect(lettings_log.la).to eq("E09000033")
end
context "and changes to uprn not known" do
it "sets correct address fields" do
visit("/lettings-logs/#{lettings_log.id}/uprn")
choose "No"
click_button("Save and continue")
lettings_log.reload
expect(lettings_log.uprn_known).to eq(0) # no
expect(lettings_log.uprn).to eq(nil)
expect(lettings_log.uprn_confirmed).to eq(nil)
expect(lettings_log.uprn_selection).to eq(nil)
expect(lettings_log.postcode_known).to eq(nil)
expect(lettings_log.postcode_full).to eq(nil)
expect(lettings_log.address_line1).to eq(nil)
expect(lettings_log.address_line2).to eq(nil)
expect(lettings_log.town_or_city).to eq(nil)
expect(lettings_log.address_line1_input).to eq(nil)
expect(lettings_log.postcode_full_input).to eq(nil)
expect(lettings_log.address_search_value_check).to eq(nil)
expect(lettings_log.la).to eq(nil)
end
end
end
context "and uprn is not confirmed" do
before do
choose "No, I want to search for the address instead"
click_button("Save and continue")
end
it "sets correct address fields" do
lettings_log.reload
expect(lettings_log.uprn_known).to eq(0) # no
expect(lettings_log.uprn).to eq(nil)
expect(lettings_log.uprn_confirmed).to eq(nil)
expect(lettings_log.uprn_selection).to eq(nil)
expect(lettings_log.postcode_known).to eq(nil)
expect(lettings_log.postcode_full).to eq(nil)
expect(lettings_log.address_line1).to eq(nil)
expect(lettings_log.address_line2).to eq(nil)
expect(lettings_log.town_or_city).to eq(nil)
expect(lettings_log.address_line1_input).to eq(nil)
expect(lettings_log.postcode_full_input).to eq(nil)
expect(lettings_log.address_search_value_check).to eq(nil)
expect(lettings_log.la).to eq(nil)
end
end
end
context "and uprn is not known" do
before do
choose "No"
click_button("Save and continue")
end
it "sets correct address fields" do
lettings_log.reload
expect(lettings_log.uprn_known).to eq(0) # no
expect(lettings_log.uprn).to eq(nil)
expect(lettings_log.uprn_confirmed).to eq(nil)
expect(lettings_log.uprn_selection).to eq(nil)
expect(lettings_log.postcode_known).to eq(nil)
expect(lettings_log.postcode_full).to eq(nil)
expect(lettings_log.address_line1).to eq(nil)
expect(lettings_log.address_line2).to eq(nil)
expect(lettings_log.town_or_city).to eq(nil)
expect(lettings_log.address_line1_input).to eq(nil)
expect(lettings_log.postcode_full_input).to eq(nil)
expect(lettings_log.address_search_value_check).to eq(nil)
expect(lettings_log.la).to eq(nil)
end
context "and the address is not found" do
it "sets correct address fields" do
fill_in("lettings_log[address_line1_input]", with: "Address line 1")
fill_in("lettings_log[postcode_full_input]", with: "AA1 1AB")
click_button("Search")
lettings_log.reload
expect(lettings_log.uprn_known).to eq(0) # no
expect(lettings_log.uprn).to eq(nil)
expect(lettings_log.uprn_confirmed).to eq(nil)
expect(lettings_log.uprn_selection).to eq(nil)
expect(lettings_log.postcode_known).to eq(nil)
expect(lettings_log.postcode_full).to eq(nil)
expect(lettings_log.address_line1).to eq(nil)
expect(lettings_log.address_line2).to eq(nil)
expect(lettings_log.town_or_city).to eq(nil)
expect(lettings_log.address_line1_input).to eq("Address line 1")
expect(lettings_log.postcode_full_input).to eq("AA1 1AB")
expect(lettings_log.address_search_value_check).to eq(nil)
expect(lettings_log.la).to eq(nil)
click_button("Confirm and continue")
lettings_log.reload
expect(lettings_log.uprn_known).to eq(0) # no
expect(lettings_log.uprn).to eq(nil)
expect(lettings_log.uprn_confirmed).to eq(nil)
expect(lettings_log.uprn_selection).to eq(nil)
expect(lettings_log.postcode_known).to eq(nil)
expect(lettings_log.postcode_full).to eq(nil)
expect(lettings_log.address_line1).to eq(nil)
expect(lettings_log.address_line2).to eq(nil)
expect(lettings_log.town_or_city).to eq(nil)
expect(lettings_log.address_line1_input).to eq("Address line 1")
expect(lettings_log.postcode_full_input).to eq("AA1 1AB")
expect(lettings_log.address_search_value_check).to eq(0)
expect(lettings_log.la).to eq(nil)
end
end
context "and address is found, re-searched and not found" do
before do
fill_in("lettings_log[address_line1_input]", with: "Address line 1")
fill_in("lettings_log[postcode_full_input]", with: "AA1 1AA")
click_button("Search")
visit("/lettings-logs/#{lettings_log.id}/address-matcher")
fill_in("lettings_log[address_line1_input]", with: "Address line 1")
fill_in("lettings_log[postcode_full_input]", with: "AA1 1AB")
click_button("Search")
end
it "routes to the correct page" do
expect(page).to have_current_path("/lettings-logs/#{lettings_log.id}/no-address-found")
end
end
context "and the user selects 'address_not_listed'" do
before do
fill_in("lettings_log[address_line1_input]", with: "Address line 1")
fill_in("lettings_log[postcode_full_input]", with: "AA1 1AA")
click_button("Search")
choose "The address is not listed, I want to enter the address manually"
click_button("Save and continue")
end
it "sets correct address fields" do
lettings_log.reload
expect(lettings_log.uprn_known).to eq(0) # no
expect(lettings_log.uprn).to eq(nil)
expect(lettings_log.uprn_confirmed).to eq(nil)
expect(lettings_log.uprn_selection).to eq("uprn_not_listed")
expect(lettings_log.postcode_known).to eq(1)
expect(lettings_log.postcode_full).to eq("AA1 1AA")
expect(lettings_log.address_line1).to eq("Address line 1")
expect(lettings_log.address_line2).to eq(nil)
expect(lettings_log.town_or_city).to eq(nil)
expect(lettings_log.address_line1_input).to eq("Address line 1")
expect(lettings_log.postcode_full_input).to eq("AA1 1AA")
expect(lettings_log.address_search_value_check).to eq(nil)
expect(lettings_log.la).to eq("E09000033")
end
context "and the user enters a new address manually" do
context "without changing a valid postcode" do
before do
fill_in("lettings_log[town_or_city]", with: "Town")
click_button("Save and continue")
end
it "sets correct address fields" do
lettings_log.reload
expect(lettings_log.uprn_known).to eq(0) # no
expect(lettings_log.uprn).to eq(nil)
expect(lettings_log.uprn_confirmed).to eq(nil)
expect(lettings_log.uprn_selection).to eq("uprn_not_listed")
expect(lettings_log.postcode_known).to eq(1)
expect(lettings_log.postcode_full).to eq("AA1 1AA")
expect(lettings_log.address_line1).to eq("Address line 1")
expect(lettings_log.address_line2).to eq("")
expect(lettings_log.town_or_city).to eq("Town")
expect(lettings_log.address_line1_input).to eq("Address line 1")
expect(lettings_log.postcode_full_input).to eq("AA1 1AA")
expect(lettings_log.address_search_value_check).to eq(nil)
expect(lettings_log.la).to eq("E09000033")
end
end
context "with changing the postcode" do
before do
fill_in("lettings_log[town_or_city]", with: "Town")
fill_in("lettings_log[postcode_full]", with: "AA12AA")
click_button("Save and continue")
end
it "sets correct address fields" do
lettings_log.reload
expect(lettings_log.uprn_known).to eq(0) # no
expect(lettings_log.uprn).to eq(nil)
expect(lettings_log.uprn_confirmed).to eq(nil)
expect(lettings_log.uprn_selection).to eq("uprn_not_listed")
expect(lettings_log.postcode_known).to eq(1)
expect(lettings_log.postcode_full).to eq("AA1 2AA")
expect(lettings_log.address_line1).to eq("Address line 1")
expect(lettings_log.address_line2).to eq("")
expect(lettings_log.town_or_city).to eq("Town")
expect(lettings_log.address_line1_input).to eq("Address line 1")
expect(lettings_log.postcode_full_input).to eq("AA1 1AA")
expect(lettings_log.address_search_value_check).to eq(nil)
expect(lettings_log.la).to eq("E08000010")
end
end
end
end
context "and the user selects 'address_not_listed' and then changes their mind and selects an address" do
before do
fill_in("lettings_log[address_line1_input]", with: "Address line 1")
fill_in("lettings_log[postcode_full_input]", with: "AA1 1AA")
click_button("Search")
choose "The address is not listed, I want to enter the address manually"
click_button("Save and continue")
visit("/lettings-logs/#{lettings_log.id}/uprn-selection")
choose("lettings-log-uprn-selection-111-field", allow_label_click: true)
click_button("Save and continue")
end
it "sets correct address fields" do
lettings_log.reload
expect(lettings_log.uprn_known).to eq(1)
expect(lettings_log.uprn).to eq("111")
expect(lettings_log.uprn_confirmed).to eq(1)
expect(lettings_log.uprn_selection).to eq(nil)
expect(lettings_log.postcode_known).to eq(1)
expect(lettings_log.postcode_full).to eq("AA1 1AA")
expect(lettings_log.address_line1).to eq("Some Place")
expect(lettings_log.address_line2).to eq(nil)
expect(lettings_log.town_or_city).to eq("Bristol")
expect(lettings_log.address_line1_input).to eq("Address line 1")
expect(lettings_log.postcode_full_input).to eq("AA1 1AA")
expect(lettings_log.address_search_value_check).to eq(nil)
expect(lettings_log.la).to eq("E09000033")
end
end
context "and possible addresses found and selected" do
before do
fill_in("lettings_log[address_line1_input]", with: "Address line 1")
fill_in("lettings_log[postcode_full_input]", with: "AA1 1AA")
click_button("Search")
choose("lettings-log-uprn-selection-111-field", allow_label_click: true)
click_button("Save and continue")
end
it "sets correct address fields" do
lettings_log.reload
expect(lettings_log.uprn_known).to eq(1)
expect(lettings_log.uprn).to eq("111")
expect(lettings_log.uprn_confirmed).to eq(1)
expect(lettings_log.uprn_selection).to eq(nil)
expect(lettings_log.postcode_known).to eq(1)
expect(lettings_log.postcode_full).to eq("AA1 1AA")
expect(lettings_log.address_line1).to eq("Some Place")
expect(lettings_log.address_line2).to eq(nil)
expect(lettings_log.town_or_city).to eq("Bristol")
expect(lettings_log.address_line1_input).to eq("Address line 1")
expect(lettings_log.postcode_full_input).to eq("AA1 1AA")
expect(lettings_log.address_search_value_check).to eq(nil)
expect(lettings_log.la).to eq("E09000033")
end
end
end
end
end
end

348
spec/features/sales_log_spec.rb

@ -310,6 +310,354 @@ RSpec.describe "Sales Log Features" do
expect(page).to have_current_path("/sales-logs/bulk-uploads")
end
end
context "when filling out address fields" do
let(:sales_log) { create(:sales_log, :shared_ownership_setup_complete, assigned_to: user) }
before do
body = {
results: [
{
DPA: {
"POSTCODE": "AA1 1AA",
"POST_TOWN": "Bristol",
"ORGANISATION_NAME": "Some place",
},
},
],
}.to_json
WebMock.stub_request(:get, "https://api.os.uk/search/places/v1/uprn?dataset=DPA,LPI&key=OS_DATA_KEY&uprn=111")
.to_return(status: 200, body:, headers: {})
body = { results: [{ DPA: { UPRN: "111" } }] }.to_json
WebMock.stub_request(:get, "https://api.os.uk/search/places/v1/find?query=Address+line+1%2C+AA1+1AA&key=OS_DATA_KEY&maxresults=10&minmatch=0.4")
.to_return(status: 200, body:, headers: {})
WebMock.stub_request(:get, "https://api.postcodes.io/postcodes/AA11AA")
.to_return(status: 200, body: "{\"status\":200,\"result\":{\"postcode\":\"AA1 1AA\",\"admin_district\":\"Westminster\",\"codes\":{\"admin_district\":\"E09000033\"}}}", headers: {})
WebMock.stub_request(:get, "https://api.postcodes.io/postcodes/AA12AA")
.to_return(status: 200, body: "{\"status\":200,\"result\":{\"postcode\":\"AA1 2AA\",\"admin_district\":\"Wigan\",\"codes\":{\"admin_district\":\"E08000010\"}}}", headers: {})
body = { results: [] }.to_json
WebMock.stub_request(:get, "https://api.os.uk/search/places/v1/find?query=Address+line+1%2C+AA1+1AB&key=OS_DATA_KEY&maxresults=10&minmatch=0.4")
.to_return(status: 200, body:, headers: {})
visit("/sales-logs/#{sales_log.id}/uprn")
end
context "and uprn is known and answered" do
before do
choose "Yes"
fill_in("sales_log[uprn]", with: "111")
click_button("Save and continue")
end
context "and uprn is confirmed" do
it "sets correct address fields" do
sales_log.reload
expect(sales_log.uprn_known).to eq(1) # yes
expect(sales_log.uprn).to eq("111")
expect(sales_log.uprn_confirmed).to eq(nil)
expect(sales_log.uprn_selection).to eq(nil)
expect(sales_log.pcodenk).to eq(0)
expect(sales_log.postcode_full).to eq("AA1 1AA")
expect(sales_log.address_line1).to eq("Some Place")
expect(sales_log.address_line2).to eq(nil)
expect(sales_log.town_or_city).to eq("Bristol")
expect(sales_log.address_line1_input).to eq(nil)
expect(sales_log.postcode_full_input).to eq(nil)
expect(sales_log.address_search_value_check).to eq(nil)
expect(sales_log.la).to eq("E09000033")
choose "Yes"
click_button("Save and continue")
sales_log.reload
expect(sales_log.uprn_known).to eq(1) # yes
expect(sales_log.uprn).to eq("111")
expect(sales_log.uprn_confirmed).to eq(1) # yes
expect(sales_log.uprn_selection).to eq(nil)
expect(sales_log.pcodenk).to eq(0)
expect(sales_log.postcode_full).to eq("AA1 1AA")
expect(sales_log.address_line1).to eq("Some Place")
expect(sales_log.address_line2).to eq(nil)
expect(sales_log.town_or_city).to eq("Bristol")
expect(sales_log.address_line1_input).to eq(nil)
expect(sales_log.postcode_full_input).to eq(nil)
expect(sales_log.address_search_value_check).to eq(nil)
expect(sales_log.la).to eq("E09000033")
end
context "and changes to uprn not known" do
it "sets correct address fields" do
visit("/sales-logs/#{sales_log.id}/uprn")
choose "No"
click_button("Save and continue")
sales_log.reload
expect(sales_log.uprn_known).to eq(0) # no
expect(sales_log.uprn).to eq(nil)
expect(sales_log.uprn_confirmed).to eq(nil)
expect(sales_log.uprn_selection).to eq(nil)
expect(sales_log.pcodenk).to eq(nil)
expect(sales_log.postcode_full).to eq(nil)
expect(sales_log.address_line1).to eq(nil)
expect(sales_log.address_line2).to eq(nil)
expect(sales_log.town_or_city).to eq(nil)
expect(sales_log.address_line1_input).to eq(nil)
expect(sales_log.postcode_full_input).to eq(nil)
expect(sales_log.address_search_value_check).to eq(nil)
expect(sales_log.la).to eq(nil)
end
end
end
context "and uprn is not confirmed" do
before do
choose "No, I want to search for the address instead"
click_button("Save and continue")
end
it "sets correct address fields" do
sales_log.reload
expect(sales_log.uprn_known).to eq(0) # no
expect(sales_log.uprn).to eq(nil)
expect(sales_log.uprn_confirmed).to eq(nil)
expect(sales_log.uprn_selection).to eq(nil)
expect(sales_log.pcodenk).to eq(nil)
expect(sales_log.postcode_full).to eq(nil)
expect(sales_log.address_line1).to eq(nil)
expect(sales_log.address_line2).to eq(nil)
expect(sales_log.town_or_city).to eq(nil)
expect(sales_log.address_line1_input).to eq(nil)
expect(sales_log.postcode_full_input).to eq(nil)
expect(sales_log.address_search_value_check).to eq(nil)
expect(sales_log.la).to eq(nil)
end
end
end
context "and uprn is not known" do
before do
choose "No"
click_button("Save and continue")
end
it "sets correct address fields" do
sales_log.reload
expect(sales_log.uprn_known).to eq(0) # no
expect(sales_log.uprn).to eq(nil)
expect(sales_log.uprn_confirmed).to eq(nil)
expect(sales_log.uprn_selection).to eq(nil)
expect(sales_log.pcodenk).to eq(nil)
expect(sales_log.postcode_full).to eq(nil)
expect(sales_log.address_line1).to eq(nil)
expect(sales_log.address_line2).to eq(nil)
expect(sales_log.town_or_city).to eq(nil)
expect(sales_log.address_line1_input).to eq(nil)
expect(sales_log.postcode_full_input).to eq(nil)
expect(sales_log.address_search_value_check).to eq(nil)
expect(sales_log.la).to eq(nil)
end
context "and the address is not found" do
it "sets correct address fields" do
fill_in("sales_log[address_line1_input]", with: "Address line 1")
fill_in("sales_log[postcode_full_input]", with: "AA1 1AB")
click_button("Search")
sales_log.reload
expect(sales_log.uprn_known).to eq(0) # no
expect(sales_log.uprn).to eq(nil)
expect(sales_log.uprn_confirmed).to eq(nil)
expect(sales_log.uprn_selection).to eq(nil)
expect(sales_log.pcodenk).to eq(nil)
expect(sales_log.postcode_full).to eq(nil)
expect(sales_log.address_line1).to eq(nil)
expect(sales_log.address_line2).to eq(nil)
expect(sales_log.town_or_city).to eq(nil)
expect(sales_log.address_line1_input).to eq("Address line 1")
expect(sales_log.postcode_full_input).to eq("AA1 1AB")
expect(sales_log.address_search_value_check).to eq(nil)
expect(sales_log.la).to eq(nil)
click_button("Confirm and continue")
sales_log.reload
expect(sales_log.uprn_known).to eq(0) # no
expect(sales_log.uprn).to eq(nil)
expect(sales_log.uprn_confirmed).to eq(nil)
expect(sales_log.uprn_selection).to eq(nil)
expect(sales_log.pcodenk).to eq(nil)
expect(sales_log.postcode_full).to eq(nil)
expect(sales_log.address_line1).to eq(nil)
expect(sales_log.address_line2).to eq(nil)
expect(sales_log.town_or_city).to eq(nil)
expect(sales_log.address_line1_input).to eq("Address line 1")
expect(sales_log.postcode_full_input).to eq("AA1 1AB")
expect(sales_log.address_search_value_check).to eq(0)
expect(sales_log.la).to eq(nil)
end
end
context "and address is found, re-searched and not found" do
before do
fill_in("sales_log[address_line1_input]", with: "Address line 1")
fill_in("sales_log[postcode_full_input]", with: "AA1 1AA")
click_button("Search")
visit("/sales-logs/#{sales_log.id}/address-matcher")
fill_in("sales_log[address_line1_input]", with: "Address line 1")
fill_in("sales_log[postcode_full_input]", with: "AA1 1AB")
click_button("Search")
end
it "routes to the correct page" do
expect(page).to have_current_path("/sales-logs/#{sales_log.id}/no-address-found")
end
end
context "and the user selects 'address_not_listed'" do
before do
fill_in("sales_log[address_line1_input]", with: "Address line 1")
fill_in("sales_log[postcode_full_input]", with: "AA1 1AA")
click_button("Search")
choose "The address is not listed, I want to enter the address manually"
click_button("Save and continue")
end
it "sets correct address fields" do
sales_log.reload
expect(sales_log.uprn_known).to eq(0) # no
expect(sales_log.uprn).to eq(nil)
expect(sales_log.uprn_confirmed).to eq(nil)
expect(sales_log.uprn_selection).to eq("uprn_not_listed")
expect(sales_log.pcodenk).to eq(0)
expect(sales_log.postcode_full).to eq("AA1 1AA")
expect(sales_log.address_line1).to eq("Address line 1")
expect(sales_log.address_line2).to eq(nil)
expect(sales_log.town_or_city).to eq(nil)
expect(sales_log.address_line1_input).to eq("Address line 1")
expect(sales_log.postcode_full_input).to eq("AA1 1AA")
expect(sales_log.address_search_value_check).to eq(nil)
expect(sales_log.la).to eq("E09000033")
end
context "and the user enters a new address manually" do
context "without changing a valid postcode" do
before do
fill_in("sales_log[town_or_city]", with: "Town")
click_button("Save and continue")
end
it "sets correct address fields" do
sales_log.reload
expect(sales_log.uprn_known).to eq(0) # no
expect(sales_log.uprn).to eq(nil)
expect(sales_log.uprn_confirmed).to eq(nil)
expect(sales_log.uprn_selection).to eq("uprn_not_listed")
expect(sales_log.pcodenk).to eq(0)
expect(sales_log.postcode_full).to eq("AA1 1AA")
expect(sales_log.address_line1).to eq("Address line 1")
expect(sales_log.address_line2).to eq("")
expect(sales_log.town_or_city).to eq("Town")
expect(sales_log.address_line1_input).to eq("Address line 1")
expect(sales_log.postcode_full_input).to eq("AA1 1AA")
expect(sales_log.address_search_value_check).to eq(nil)
expect(sales_log.la).to eq("E09000033")
end
end
context "with changing the postcode" do
before do
fill_in("sales_log[town_or_city]", with: "Town")
fill_in("sales_log[postcode_full]", with: "AA12AA")
click_button("Save and continue")
end
it "sets correct address fields" do
sales_log.reload
expect(sales_log.uprn_known).to eq(0) # no
expect(sales_log.uprn).to eq(nil)
expect(sales_log.uprn_confirmed).to eq(nil)
expect(sales_log.uprn_selection).to eq("uprn_not_listed")
expect(sales_log.pcodenk).to eq(0)
expect(sales_log.postcode_full).to eq("AA1 2AA")
expect(sales_log.address_line1).to eq("Address line 1")
expect(sales_log.address_line2).to eq("")
expect(sales_log.town_or_city).to eq("Town")
expect(sales_log.address_line1_input).to eq("Address line 1")
expect(sales_log.postcode_full_input).to eq("AA1 1AA")
expect(sales_log.address_search_value_check).to eq(nil)
expect(sales_log.la).to eq("E08000010")
end
end
end
end
context "and the user selects 'address_not_listed' and then changes their mind and selects an address" do
before do
fill_in("sales_log[address_line1_input]", with: "Address line 1")
fill_in("sales_log[postcode_full_input]", with: "AA1 1AA")
click_button("Search")
choose "The address is not listed, I want to enter the address manually"
click_button("Save and continue")
visit("/sales-logs/#{sales_log.id}/uprn-selection")
choose("sales-log-uprn-selection-111-field", allow_label_click: true)
click_button("Save and continue")
end
it "sets correct address fields" do
sales_log.reload
expect(sales_log.uprn_known).to eq(1)
expect(sales_log.uprn).to eq("111")
expect(sales_log.uprn_confirmed).to eq(1)
expect(sales_log.uprn_selection).to eq(nil)
expect(sales_log.pcodenk).to eq(0)
expect(sales_log.postcode_full).to eq("AA1 1AA")
expect(sales_log.address_line1).to eq("Some Place")
expect(sales_log.address_line2).to eq(nil)
expect(sales_log.town_or_city).to eq("Bristol")
expect(sales_log.address_line1_input).to eq("Address line 1")
expect(sales_log.postcode_full_input).to eq("AA1 1AA")
expect(sales_log.address_search_value_check).to eq(nil)
expect(sales_log.la).to eq("E09000033")
end
end
context "and possible addresses found and selected" do
before do
fill_in("sales_log[address_line1_input]", with: "Address line 1")
fill_in("sales_log[postcode_full_input]", with: "AA1 1AA")
click_button("Search")
choose("sales-log-uprn-selection-111-field", allow_label_click: true)
click_button("Save and continue")
end
it "sets correct address fields" do
sales_log.reload
expect(sales_log.uprn_known).to eq(1)
expect(sales_log.uprn).to eq("111")
expect(sales_log.uprn_confirmed).to eq(1)
expect(sales_log.uprn_selection).to eq(nil)
expect(sales_log.pcodenk).to eq(0)
expect(sales_log.postcode_full).to eq("AA1 1AA")
expect(sales_log.address_line1).to eq("Some Place")
expect(sales_log.address_line2).to eq(nil)
expect(sales_log.town_or_city).to eq("Bristol")
expect(sales_log.address_line1_input).to eq("Address line 1")
expect(sales_log.postcode_full_input).to eq("AA1 1AA")
expect(sales_log.address_search_value_check).to eq(nil)
expect(sales_log.la).to eq("E09000033")
end
end
end
end
end
context "when a log becomes a duplicate" do

4
spec/features/user_spec.rb

@ -61,7 +61,7 @@ RSpec.describe "User Features" do
context "when the user has forgotten their password" do
it "is redirected to the reset password page when they click the reset password link" do
visit("/lettings-logs")
click_link("reset your password")
click_link("Forgot password")
expect(page).to have_current_path("/account/password/new")
end
@ -744,7 +744,7 @@ RSpec.describe "User Features" do
it "is redirected to the reset password page when they click the reset password link" do
visit("/account/sign-in")
click_link("reset your password")
click_link("Forgot password")
expect(page).to have_current_path("/account/password/new")
end

4
spec/helpers/tag_helper_spec.rb

@ -10,8 +10,8 @@ RSpec.describe TagHelper do
end
it "returns tag with correct status text and colour and custom class" do
expect(status_tag("not_started", "app-tag--small")).to eq("<strong class=\"govuk-tag govuk-tag--grey app-tag--small\">Not started</strong>")
expect(status_tag("cannot_start_yet", "app-tag--small")).to eq("<strong class=\"govuk-tag govuk-tag--grey app-tag--small\">Cannot start yet</strong>")
expect(status_tag("not_started", "app-tag--small")).to eq("<strong class=\"govuk-tag govuk-tag--light-blue app-tag--small\">Not started</strong>")
expect(status_tag("cannot_start_yet", "app-tag--small")).to eq(nil)
expect(status_tag("in_progress", "app-tag--small")).to eq("<strong class=\"govuk-tag govuk-tag--blue app-tag--small\">In progress</strong>")
expect(status_tag("completed", "app-tag--small")).to eq("<strong class=\"govuk-tag govuk-tag--green app-tag--small\">Completed</strong>")
expect(status_tag("active", "app-tag--small")).to eq("<strong class=\"govuk-tag govuk-tag--green app-tag--small\">Active</strong>")

110
spec/lib/tasks/count_duplicates_spec.rb

@ -108,4 +108,114 @@ RSpec.describe "count_duplicates" do
end
end
end
describe "count_duplicates:active_scheme_duplicates_per_org", type: :task do
subject(:task) { Rake::Task["count_duplicates:active_scheme_duplicates_per_org"] }
let(:storage_service) { instance_double(Storage::S3Service) }
let(:test_url) { "test_url" }
before do
Rake.application.rake_require("tasks/count_duplicates")
Rake::Task.define_task(:environment)
task.reenable
end
context "when the rake task is run" do
context "and there are no duplicate schemes" do
before do
create(:organisation)
end
it "creates a csv with headers only" do
expect(storage_service).to receive(:write_file).with(/scheme-duplicates-.*\.csv/, "\uFEFFOrganisation id,Number of duplicate sets,Total duplicate schemes\n")
expect(Rails.logger).to receive(:info).with("Download URL: #{test_url}")
task.invoke
end
end
context "and there are duplicate schemes" do
let(:organisation) { create(:organisation) }
let(:organisation2) { create(:organisation) }
before do
create_list(:scheme, 2, :duplicate, :with_location, owning_organisation: organisation)
create_list(:scheme, 3, :duplicate, :with_location, primary_client_group: "I", owning_organisation: organisation)
create_list(:scheme, 5, :duplicate, :with_location, owning_organisation: organisation2)
deactivated_schemes = create_list(:scheme, 2, :duplicate, owning_organisation: organisation)
deactivated_schemes.each do |scheme|
create(:scheme_deactivation_period, deactivation_date: Time.zone.yesterday, reactivation_date: nil, scheme:)
end
end
it "creates a csv with correct duplicate numbers" do
expect(storage_service).to receive(:write_file).with(/scheme-duplicates-.*\.csv/, "\uFEFFOrganisation id,Number of duplicate sets,Total duplicate schemes\n#{organisation.id},2,5\n#{organisation2.id},1,5\n")
expect(Rails.logger).to receive(:info).with("Download URL: #{test_url}")
task.invoke
end
end
end
end
describe "count_duplicates:active_location_duplicates_per_org", type: :task do
subject(:task) { Rake::Task["count_duplicates:active_location_duplicates_per_org"] }
let(:storage_service) { instance_double(Storage::S3Service) }
let(:test_url) { "test_url" }
before do
Rake.application.rake_require("tasks/count_duplicates")
Rake::Task.define_task(:environment)
task.reenable
end
context "when the rake task is run" do
context "and there are no duplicate locations" do
before do
create(:organisation)
end
it "creates a csv with headers only" do
expect(storage_service).to receive(:write_file).with(/location-duplicates-.*\.csv/, "\uFEFFOrganisation id,Duplicate sets within individual schemes,Duplicate locations within individual schemes,All duplicate sets,All duplicates\n")
expect(Rails.logger).to receive(:info).with("Download URL: #{test_url}")
task.invoke
end
end
context "and there are duplicate locations" do
let(:organisation) { create(:organisation) }
let(:scheme_a) { create(:scheme, :duplicate, owning_organisation: organisation) }
let(:scheme_b) { create(:scheme, :duplicate, owning_organisation: organisation) }
let(:scheme_c) { create(:scheme, owning_organisation: organisation) }
let(:organisation2) { create(:organisation) }
let(:scheme2) { create(:scheme, owning_organisation: organisation2) }
let(:scheme3) { create(:scheme, owning_organisation: organisation2) }
before do
create_list(:location, 2, postcode: "A1 1AB", mobility_type: "M", scheme: scheme_a) # Location A
create_list(:location, 1, postcode: "A1 1AB", mobility_type: "A", scheme: scheme_a) # Location B
create_list(:location, 1, postcode: "A1 1AB", mobility_type: "M", scheme: scheme_b) # Location A
create_list(:location, 1, postcode: "A1 1AB", mobility_type: "A", scheme: scheme_b) # Location B
create_list(:location, 2, postcode: "A1 1AB", mobility_type: "N", scheme: scheme_b) # Location C
create_list(:location, 2, postcode: "A1 1AB", mobility_type: "A", scheme: scheme_c) # Location B
create_list(:location, 5, postcode: "A1 1AB", mobility_type: "M", scheme: scheme2)
create_list(:location, 2, postcode: "A1 1AB", mobility_type: "M", scheme: scheme3)
deactivated_locations = create_list(:location, 1, postcode: "A1 1AB", mobility_type: "M", scheme: scheme_b)
deactivated_locations.each do |location|
create(:location_deactivation_period, deactivation_date: Time.zone.yesterday, reactivation_date: nil, location:)
end
end
it "creates a csv with correct duplicate numbers" do
expect(storage_service).to receive(:write_file).with(/location-duplicates-.*\.csv/, "\uFEFFOrganisation id,Duplicate sets within individual schemes,Duplicate locations within individual schemes,All duplicate sets,All duplicates\n#{organisation.id},3,6,4,9\n#{organisation2.id},2,7,2,7\n")
expect(Rails.logger).to receive(:info).with("Download URL: #{test_url}")
task.invoke
end
end
end
end
end

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

@ -15,6 +15,10 @@ RSpec.describe Form::Sales::Pages::AboutStaircase, type: :model do
describe "questions" do
let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date:)) }
before do
allow(subsection.form).to receive(:start_year_2025_or_later?).and_return(false)
end
context "when 2022" do
let(:start_date) { Time.utc(2022, 2, 8) }

4
spec/models/form/sales/pages/equity_spec.rb

@ -15,10 +15,6 @@ RSpec.describe Form::Sales::Pages::Equity, type: :model do
expect(page.questions.map(&:id)).to eq(%w[equity])
end
it "has the correct id" do
expect(page.id).to eq("equity")
end
it "has the correct description" do
expect(page.description).to be_nil
end

31
spec/models/form/sales/pages/monthly_rent_staircasing_owned_spec.rb

@ -0,0 +1,31 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::MonthlyRentStaircasingOwned, 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, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) }
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[mrentprestaircasing])
end
it "has the correct id" do
expect(page.id).to eq("monthly_rent_staircasing_owned")
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([
{ "stairowned_100?" => true },
])
end
end

31
spec/models/form/sales/pages/monthly_rent_staircasing_spec.rb

@ -0,0 +1,31 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::MonthlyRentStaircasing, 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, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) }
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[mrentprestaircasing mrent])
end
it "has the correct id" do
expect(page.id).to eq("monthly_rent_staircasing")
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([
{ "stairowned_100?" => false },
])
end
end

31
spec/models/form/sales/pages/staircase_first_time_spec.rb

@ -0,0 +1,31 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::StaircaseFirstTime, 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, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) }
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[firststair])
end
it "has the correct id" do
expect(page.id).to eq("staircase_first_time")
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([
{ "staircase" => 1 },
])
end
end

31
spec/models/form/sales/pages/staircase_initial_date_spec.rb

@ -0,0 +1,31 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::StaircaseInitialDate, 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, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) }
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[initialpurchase])
end
it "has the correct id" do
expect(page.id).to eq("staircase_initial_date")
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([
{ "is_firststair?" => true },
])
end
end

31
spec/models/form/sales/pages/staircase_previous_spec.rb

@ -0,0 +1,31 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::StaircasePrevious, 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, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) }
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[numstair lasttransaction initialpurchase])
end
it "has the correct id" do
expect(page.id).to eq("staircase_previous")
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([
{ "is_firststair?" => false },
])
end
end

38
spec/models/form/sales/pages/staircase_sale_spec.rb

@ -0,0 +1,38 @@
require "rails_helper"
RSpec.describe Form::Sales::Pages::StaircaseSale, 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, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) }
before do
allow(subsection.form).to receive(:start_year_2025_or_later?).and_return(true)
end
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[staircasesale])
end
it "has the correct id" do
expect(page.id).to eq("staircase_sale")
end
it "has the correct description" do
expect(page.description).to be_nil
end
it "has correct depends_on" do
expect(page.depends_on).to eq([
{
"staircase" => 1,
"stairowned" => 100,
},
])
end
end

2
spec/models/form/sales/pages/value_shared_ownership_spec.rb

@ -3,7 +3,7 @@ require "rails_helper"
RSpec.describe Form::Sales::Pages::ValueSharedOwnership, type: :model do
subject(:page) { described_class.new(page_id, page_definition, subsection) }
let(:page_id) { nil }
let(:page_id) { "value_shared_ownership" }
let(:page_definition) { nil }
let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1))) }

2
spec/models/form/sales/questions/equity_spec.rb

@ -5,7 +5,7 @@ RSpec.describe Form::Sales::Questions::Equity, type: :model do
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) }
let(:page) { instance_double(Form::Page, id: "initial_equity", subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) }
it "has correct page" do
expect(question.page).to eq(page)

37
spec/models/form/sales/questions/monthly_rent_after_staircasing_spec.rb

@ -0,0 +1,37 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::MonthlyRentAfterStaircasing, 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, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) }
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("mrent")
end
it "has the correct type" do
expect(question.type).to eq("numeric")
end
it "is not marked as derived" do
expect(question.derived?(nil)).to be false
end
it "has correct width" do
expect(question.width).to eq(5)
end
it "has correct prefix" do
expect(question.prefix).to eq("£")
end
it "has correct min" do
expect(question.min).to eq(0)
end
end

37
spec/models/form/sales/questions/monthly_rent_before_staircasing_spec.rb

@ -0,0 +1,37 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::MonthlyRentBeforeStaircasing, 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, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) }
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("mrentprestaircasing")
end
it "has the correct type" do
expect(question.type).to eq("numeric")
end
it "is not marked as derived" do
expect(question.derived?(nil)).to be false
end
it "has correct width" do
expect(question.width).to eq(5)
end
it "has correct prefix" do
expect(question.prefix).to eq("£")
end
it "has correct min" do
expect(question.min).to eq(0)
end
end

98
spec/models/form/sales/questions/mortgageused_spec.rb

@ -3,28 +3,39 @@ require "rails_helper"
RSpec.describe Form::Sales::Questions::Mortgageused, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page, ownershipsch:) }
let(:ownershipsch) { 1 }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form:)) }
let(:stairowned) { nil }
let(:staircase) { nil }
let(:saledate) { Time.zone.today }
let(:log) { build(:sales_log, :in_progress, ownershipsch:, stairowned:, staircase:) }
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don’t know" },
})
end
context "when the form start year is 2024" do
let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form:)) }
let(:saledate) { Time.zone.local(2024, 5, 1) }
let(:ownershipsch) { 1 }
before do
allow(form).to receive(:start_year_2024_or_later?).and_return true
allow(form).to receive(:start_year_2025_or_later?).and_return false
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don’t know" },
})
end
describe "the displayed answer options" do
context "when it is a discounted ownership sale" do
let(:ownershipsch) { 2 }
it "shows the correct question number" do
expect(question.question_number).to eq 104
end
it "does not show the don't know option" do
expect_the_question_not_to_show_dont_know
end
@ -34,20 +45,14 @@ RSpec.describe Form::Sales::Questions::Mortgageused, type: :model do
let(:ownershipsch) { 3 }
context "and the saledate is before 24/25" do
before do
allow(form).to receive(:start_year_2024_or_later?).and_return false
end
let(:saledate) { Time.zone.local(2023, 5, 1) }\
it "does not show the don't know option" do
expect_the_question_not_to_show_dont_know
it "does show the don't know option" do
expect_the_question_to_show_dont_know
end
end
context "and the saledate is 24/25 or after" do
before do
allow(form).to receive(:start_year_2024_or_later?).and_return true
end
context "and the saledate is 24/25" do
it "shows the don't know option" do
expect_the_question_to_show_dont_know
end
@ -87,6 +92,57 @@ RSpec.describe Form::Sales::Questions::Mortgageused, type: :model do
end
end
context "when the form start year is 2025" do
let(:form) { instance_double(Form, start_date: Time.zone.local(2025, 4, 1)) }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form:)) }
let(:saledate) { Time.zone.local(2025, 5, 1) }
before do
allow(form).to receive(:start_year_2024_or_later?).and_return true
allow(form).to receive(:start_year_2025_or_later?).and_return true
end
context "when it is a discounted ownership sale" do
let(:ownershipsch) { 2 }
it "shows the correct question number" do
expect(question.question_number).to eq 104
end
it "does not show the don't know option" do
expect_the_question_not_to_show_dont_know
end
end
context "when it is a shared ownership scheme" do
let(:ownershipsch) { 1 }
context "and it is a staircasing transaction" do
let(:staircase) { 1 }
it "does show the don't know option" do
expect_the_question_to_show_dont_know
end
context "and stairowned is 100" do
let(:stairowned) { 100 }
it "shows the don't know option" do
expect_the_question_to_show_dont_know
end
end
end
context "and it is not a staircasing transaction" do
let(:staircase) { 2 }
it "does not show the don't know option" do
expect_the_question_not_to_show_dont_know
end
end
end
end
private
def expect_the_question_not_to_show_dont_know

33
spec/models/form/sales/questions/staircase_count_spec.rb

@ -0,0 +1,33 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::StaircaseCount, 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, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1)))) }
before do
allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(true)
end
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("numstair")
end
it "has the correct type" do
expect(question.type).to eq("numeric")
end
it "is not marked as derived" do
expect(question.derived?(nil)).to be false
end
it "has correct conditional for" do
expect(question.conditional_for).to eq(nil)
end
end

40
spec/models/form/sales/questions/staircase_first_time_spec.rb

@ -0,0 +1,40 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::StaircaseFirstTime, 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, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1)))) }
before do
allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(true)
end
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("firststair")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?(nil)).to be false
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
})
end
it "has correct conditional for" do
expect(question.conditional_for).to eq(nil)
end
end

33
spec/models/form/sales/questions/staircase_initial_date_spec.rb

@ -0,0 +1,33 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::StaircaseInitialDate, 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, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1)))) }
before do
allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(true)
end
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("initialpurchase")
end
it "has the correct type" do
expect(question.type).to eq("date")
end
it "is not marked as derived" do
expect(question.derived?(nil)).to be false
end
it "has correct conditional for" do
expect(question.conditional_for).to eq(nil)
end
end

33
spec/models/form/sales/questions/staircase_last_date_spec.rb

@ -0,0 +1,33 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::StaircaseLastDate, 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, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1)))) }
before do
allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(true)
end
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("lasttransaction")
end
it "has the correct type" do
expect(question.type).to eq("date")
end
it "is not marked as derived" do
expect(question.derived?(nil)).to be false
end
it "has correct conditional for" do
expect(question.conditional_for).to eq(nil)
end
end

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

@ -7,6 +7,10 @@ RSpec.describe Form::Sales::Questions::StaircaseSale, type: :model do
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) }
before do
allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(false)
end
it "has correct page" do
expect(question.page).to eq(page)
end

6
spec/models/form/sales/questions/value_spec.rb

@ -5,7 +5,11 @@ RSpec.describe Form::Sales::Questions::Value, type: :model do
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) }
let(:page) { instance_double(Form::Page, id: "value_shared_ownership", subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) }
before do
allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(false)
end
it "has correct page" do
expect(question.page).to eq(page)

11
spec/models/form/sales/sections/sale_information_spec.rb

@ -7,6 +7,10 @@ RSpec.describe Form::Sales::Sections::SaleInformation, type: :model do
let(:section_definition) { nil }
let(:form) { instance_double(Form, start_year_2025_or_later?: false) }
before do
allow(form).to receive(:start_year_2025_or_later?).and_return(false)
end
it "has correct form" do
expect(sale_information.form).to eq(form)
end
@ -22,11 +26,16 @@ RSpec.describe Form::Sales::Sections::SaleInformation, type: :model do
end
context "when form is 2025 or later" do
let(:form) { instance_double(Form, start_year_2025_or_later?: true) }
let(:form) { instance_double(Form) }
before do
allow(form).to receive(:start_year_2025_or_later?).and_return(true)
end
it "has correct subsections" do
expect(sale_information.subsections.map(&:id)).to eq(%w[
shared_ownership_initial_purchase
shared_ownership_staircasing_transaction
discounted_ownership_scheme
outright_sale
])

2
spec/models/form/sales/subsections/shared_ownership_initial_purchase_spec.rb

@ -30,7 +30,7 @@ RSpec.describe Form::Sales::Subsections::SharedOwnershipInitialPurchase, type: :
shared_ownership_previous_tenure
value_shared_ownership
about_price_shared_ownership_value_check
equity
initial_equity
shared_ownership_equity_value_check
mortgage_used_shared_ownership
mortgage_used_mortgage_value_check

36
spec/models/location_spec.rb

@ -740,10 +740,38 @@ RSpec.describe Location, type: :model do
describe "#units" do
let(:location) { FactoryBot.build(:location) }
it "does add an error when the number of units is invalid" do
location.units = nil
location.valid?(:units)
expect(location.errors.count).to eq(1)
context "when the number of units is invalid" do
it "adds an error when units is nil" do
location.units = nil
location.valid?(:units)
expect(location.errors.count).to eq(2)
end
it "adds an error when units is 0" do
location.units = 0
location.valid?(:units)
expect(location.errors.count).to eq(1)
end
end
context "when the number of units is valid" do
it "does not add an error when units is 1" do
location.units = 1
location.valid?(:units)
expect(location.errors.count).to eq(0)
end
it "does not add an error when units is 10" do
location.units = 10
location.valid?(:units)
expect(location.errors.count).to eq(0)
end
it "does not add an error when units is 200" do
location.units = 200
location.valid?(:units)
expect(location.errors.count).to eq(0)
end
end
end

40
spec/models/validations/sales/sale_information_validations_spec.rb

@ -1407,4 +1407,44 @@ RSpec.describe Validations::Sales::SaleInformationValidations do
end
end
end
describe "#validate_number_of_staircase_transactions" do
let(:record) { build(:sales_log, numstair:, firststair:) }
before do
sale_information_validator.validate_number_of_staircase_transactions(record)
end
context "when it is not the first staircasing transaction" do
context "and the number of staircasing transactions is between 2 and 10" do
let(:numstair) { 6 }
let(:firststair) { 2 }
it "does not add an error" do
expect(record.errors).to be_empty
end
end
context "and the number of staircasing transactions is less than 2" do
let(:numstair) { 1 }
let(:firststair) { 2 }
it "adds an error" do
expect(record.errors[:numstair]).to include(I18n.t("validations.sales.sale_information.numstair.must_be_greater_than_one"))
expect(record.errors[:firststair]).to include(I18n.t("validations.sales.sale_information.firststair.cannot_be_no"))
end
end
end
context "when it is the first staircasing transaction" do
context "and numstair is also 1" do
let(:numstair) { 1 }
let(:firststair) { 1 }
it "does not add an error" do
expect(record.errors).to be_empty
end
end
end
end
end

2
spec/models/validations/setup_validations_spec.rb

@ -703,7 +703,7 @@ RSpec.describe Validations::SetupValidations do
.to include("This location is incomplete. Select another location or update this one.")
end
it "produces no error when location is completes" do
it "produces no error when location is complete" do
location.update!(units: 1)
location.reload
record.location = location

11
spec/requests/form_controller_spec.rb

@ -582,8 +582,9 @@ RSpec.describe FormController, type: :request do
allow(Rails.logger).to receive(:info)
end
it "re-renders the same page with errors if validation fails" do
it "redirects to the same page with errors if validation fails" do
post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params
follow_redirect!
expect(page).to have_content("There is a problem")
expect(page).to have_content("Error: What is the tenant’s age?")
end
@ -591,6 +592,8 @@ RSpec.describe FormController, type: :request do
it "resets errors when fixed" do
post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params
post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: valid_params
follow_redirect!
expect(page).not_to have_content("There is a problem")
get "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}"
expect(page).not_to have_content("There is a problem")
end
@ -616,6 +619,7 @@ RSpec.describe FormController, type: :request do
it "validates the date correctly" do
post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params
follow_redirect!
expect(page).to have_content("There is a problem")
end
end
@ -693,6 +697,7 @@ RSpec.describe FormController, type: :request do
it "validates the date correctly" do
post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params
follow_redirect!
expect(page).to have_content("There is a problem")
end
end
@ -713,6 +718,7 @@ RSpec.describe FormController, type: :request do
it "validates the date correctly" do
post "/sales-logs/#{sales_log.id}/#{page_id.dasherize}", params: params
follow_redirect!
expect(page).to have_content("There is a problem")
end
end
@ -748,8 +754,9 @@ RSpec.describe FormController, type: :request do
Timecop.unfreeze
end
it "re-renders the same page with errors if validation fails" do
it "redirects the same page with errors if validation fails" do
post "/lettings-logs/#{lettings_log.id}/managing-organisation", params: params
follow_redirect!
expect(page).to have_content("There is a problem")
expect(page).to have_content("Error: Which organisation manages this letting?")
end

Loading…
Cancel
Save