Browse Source

Merge branch 'main' into CLDC-20-more-ac-fixes2

CLDC-20-more-ac-fixes2
James Rose 2 years ago committed by GitHub
parent
commit
fa9804aa4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      .github/workflows/review_pipeline.yml
  2. 50
      app/controllers/bulk_upload_lettings_logs_controller.rb
  3. 50
      app/controllers/bulk_upload_sales_logs_controller.rb
  4. 6
      app/controllers/cookies_controller.rb
  5. 13
      app/controllers/locations_controller.rb
  6. 16
      app/controllers/organisation_relationships_controller.rb
  7. 58
      app/controllers/schemes_controller.rb
  8. 1
      app/controllers/users_controller.rb
  9. 7
      app/frontend/application.js
  10. 57
      app/frontend/cookie-banner.js
  11. 32
      app/helpers/locations_helper.rb
  12. 21
      app/helpers/logs_helper.rb
  13. 4
      app/helpers/navigation_items_helper.rb
  14. 52
      app/helpers/schemes_helper.rb
  15. 2
      app/helpers/tag_helper.rb
  16. 17
      app/helpers/toggle_active_scheme_helper.rb
  17. 8
      app/models/form.rb
  18. 15
      app/models/form/sales/pages/buyer1_mortgage.rb
  19. 19
      app/models/form/sales/pages/buyer2_income.rb
  20. 16
      app/models/form/sales/questions/buyer1_mortgage.rb
  21. 14
      app/models/form/sales/questions/buyer2_income.rb
  22. 21
      app/models/form/sales/questions/buyer2_income_known.rb
  23. 2
      app/models/form/sales/sections/finances.rb
  24. 15
      app/models/form/sales/subsections/income_benefits_and_outgoings.rb
  25. 17
      app/models/form/sales/subsections/income_benefits_and_savings.rb
  26. 5
      app/models/form_handler.rb
  27. 41
      app/models/forms/bulk_upload_lettings/prepare_your_file.rb
  28. 19
      app/models/forms/bulk_upload_lettings/upload_your_file.rb
  29. 37
      app/models/forms/bulk_upload_lettings/year.rb
  30. 41
      app/models/forms/bulk_upload_sales/prepare_your_file.rb
  31. 19
      app/models/forms/bulk_upload_sales/upload_your_file.rb
  32. 37
      app/models/forms/bulk_upload_sales/year.rb
  33. 24
      app/models/location.rb
  34. 5
      app/models/organisation.rb
  35. 12
      app/models/organisation_relationship.rb
  36. 26
      app/models/scheme.rb
  37. 35
      app/models/scheme_deactivation_period.rb
  38. 5
      app/models/validations/date_validations.rb
  39. 11
      app/models/validations/setup_validations.rb
  40. 35
      app/models/validations/shared_validations.rb
  41. 33
      app/views/bulk_upload_lettings_logs/forms/prepare_your_file.html.erb
  42. 17
      app/views/bulk_upload_lettings_logs/forms/upload_your_file.html.erb
  43. 16
      app/views/bulk_upload_lettings_logs/forms/year.html.erb
  44. 33
      app/views/bulk_upload_sales_logs/forms/prepare_your_file.html.erb
  45. 17
      app/views/bulk_upload_sales_logs/forms/upload_your_file.html.erb
  46. 16
      app/views/bulk_upload_sales_logs/forms/year.html.erb
  47. 48
      app/views/cookies/_banner.html.erb
  48. 24
      app/views/layouts/application.html.erb
  49. 2
      app/views/locations/deactivate_confirm.html.erb
  50. 135
      app/views/locations/index.html.erb
  51. 12
      app/views/logs/index.html.erb
  52. 22
      app/views/schemes/_scheme_list.html.erb
  53. 6
      app/views/schemes/deactivate_confirm.html.erb
  54. 34
      app/views/schemes/show.html.erb
  55. 18
      app/views/schemes/toggle_active.html.erb
  56. 0
      config/environments/review.rb
  57. 6
      config/forms/2022_2023.json
  58. 4
      config/initializers/feature_toggle.rb
  59. 42
      config/locales/en.yml
  60. 16
      config/routes.rb
  61. 5
      db/migrate/20221122122311_delete_relationship_type.rb
  62. 10
      db/migrate/20221122130928_add_org_relation_indexes.rb
  63. 7
      db/migrate/20221124102329_add_mortgage1_to_sales.rb
  64. 6
      db/migrate/20221125142847_add_buyer2_to_sales.rb
  65. 17
      db/schema.rb
  66. 12
      db/seeds.rb
  67. 51
      docs/infrastructure.md
  68. BIN
      public/files/bulk-upload-lettings-template-v1.xlsx
  69. BIN
      public/files/bulk-upload-sales-template-v1.xlsx
  70. 2
      spec/components/check_answers_summary_list_card_component_spec.rb
  71. 4
      spec/factories/lettings_log.rb
  72. 2
      spec/factories/location.rb
  73. 8
      spec/factories/organisation_relationship.rb
  74. 3
      spec/factories/sales_log.rb
  75. 2
      spec/factories/scheme.rb
  76. 1
      spec/factories/scheme_deactivation_period.rb
  77. 51
      spec/features/bulk_upload_lettings_logs_spec.rb
  78. 51
      spec/features/bulk_upload_sales_logs_spec.rb
  79. 3
      spec/features/form/check_answers_page_spec.rb
  80. 1
      spec/features/form/validations_spec.rb
  81. 18
      spec/features/schemes_spec.rb
  82. 2
      spec/fixtures/files/lettings_logs_download.csv
  83. 2
      spec/fixtures/files/lettings_logs_download_non_support.csv
  84. 56
      spec/helpers/locations_helper_spec.rb
  85. 273
      spec/helpers/schemes_helper_spec.rb
  86. 9
      spec/helpers/tag_helper_spec.rb
  87. 3
      spec/jobs/email_csv_job_spec.rb
  88. 7
      spec/models/form/lettings/pages/housing_provider_spec.rb
  89. 10
      spec/models/form/lettings/pages/managing_organisation_spec.rb
  90. 6
      spec/models/form/lettings/questions/housing_provider_spec.rb
  91. 33
      spec/models/form/lettings/questions/managing_organisation_spec.rb
  92. 33
      spec/models/form/sales/pages/buyer1_mortgage_spec.rb
  93. 33
      spec/models/form/sales/pages/buyer2_income_spec.rb
  94. 40
      spec/models/form/sales/questions/buyer1_mortgage_spec.rb
  95. 55
      spec/models/form/sales/questions/buyer2_income_known_spec.rb
  96. 53
      spec/models/form/sales/questions/buyer2_income_spec.rb
  97. 2
      spec/models/form/sales/sections/finances_spec.rb
  98. 8
      spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb
  99. 4
      spec/models/form_handler_spec.rb
  100. 34
      spec/models/form_spec.rb
  101. Some files were not shown because too many files have changed in this diff Show More

11
.github/workflows/review_pipeline.yml

@ -1,5 +1,7 @@
name: Review app pipeline
concurrency: ${{ github.workflow }}-${{ github.event.pull_request.number }}
on:
pull_request:
types:
@ -120,6 +122,7 @@ jobs:
cf set-env $APP_NAME S3_CONFIG $S3_CONFIG
cf set-env $APP_NAME CSV_DOWNLOAD_PAAS_INSTANCE $CSV_DOWNLOAD_PAAS_INSTANCE
cf set-env $APP_NAME SENTRY_DSN $SENTRY_DSN
cf set-env $APP_NAME APP_HOST "https://dluhc-core-review-${{ github.event.pull_request.number }}.london.cloudapps.digital"
- name: Bind postgres service
env:
@ -142,6 +145,14 @@ jobs:
run: |
cf bind-service $APP_NAME $SERVICE_NAME --wait
- name: Bind S3 buckets services
env:
APP_NAME: dluhc-core-review-${{ github.event.pull_request.number }}
run: |
cf bind-service $APP_NAME dluhc-core-review-csv-bucket --wait
cf bind-service $APP_NAME dluhc-core-review-export-bucket --wait
cf bind-service $APP_NAME dluhc-core-review-import-bucket --wait
- name: Start review app
env:
APP_NAME: dluhc-core-review-${{ github.event.pull_request.number }}

50
app/controllers/bulk_upload_lettings_logs_controller.rb

@ -0,0 +1,50 @@
class BulkUploadLettingsLogsController < ApplicationController
before_action :authenticate_user!
def start
if in_crossover_period?
redirect_to bulk_upload_lettings_log_path(id: "year")
else
redirect_to bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year: current_year })
end
end
def show
render form.view_path
end
def update
if form.valid?
redirect_to form.next_path
else
render form.view_path
end
end
private
def current_year
FormHandler.instance.forms["current_lettings"].start_date.year
end
def in_crossover_period?
FormHandler.instance.forms.values.any?(&:in_crossover_period?)
end
def form
@form ||= case params[:id]
when "year"
Forms::BulkUploadLettings::Year.new(form_params)
when "prepare-your-file"
Forms::BulkUploadLettings::PrepareYourFile.new(form_params)
when "upload-your-file"
Forms::BulkUploadLettings::UploadYourFile.new(form_params)
else
raise "Page not found for path #{params[:id]}"
end
end
def form_params
params.fetch(:form, {}).permit(:year)
end
end

50
app/controllers/bulk_upload_sales_logs_controller.rb

@ -0,0 +1,50 @@
class BulkUploadSalesLogsController < ApplicationController
before_action :authenticate_user!
def start
if in_crossover_period?
redirect_to bulk_upload_sales_log_path(id: "year")
else
redirect_to bulk_upload_sales_log_path(id: "prepare-your-file", form: { year: current_year })
end
end
def show
render form.view_path
end
def update
if form.valid?
redirect_to form.next_path
else
render form.view_path
end
end
private
def current_year
FormHandler.instance.forms["current_sales"].start_date.year
end
def in_crossover_period?
FormHandler.instance.forms.values.any?(&:in_crossover_period?)
end
def form
@form ||= case params[:id]
when "year"
Forms::BulkUploadSales::Year.new(form_params)
when "prepare-your-file"
Forms::BulkUploadSales::PrepareYourFile.new(form_params)
when "upload-your-file"
Forms::BulkUploadSales::UploadYourFile.new(form_params)
else
raise "Page not found for path #{params[:id]}"
end
end
def form_params
params.fetch(:form, {}).permit(:year)
end
end

6
app/controllers/cookies_controller.rb

@ -18,6 +18,12 @@ class CookiesController < ApplicationController
redirect_to cookies_path
end
format.json do
render json: {
status: "ok",
message: %(You’ve #{analytics_consent == 'on' ? 'accepted' : 'rejected'} analytics cookies.),
}
end
end
end

13
app/controllers/locations_controller.rb

@ -38,8 +38,13 @@ class LocationsController < ApplicationController
end
def deactivate_confirm
@deactivation_date = params[:deactivation_date]
@deactivation_date_type = params[:deactivation_date_type]
@affected_logs = @location.lettings_logs.filter_by_before_startdate(params[:deactivation_date])
if @affected_logs.count.zero?
deactivate
else
@deactivation_date = params[:deactivation_date]
@deactivation_date_type = params[:deactivation_date_type]
end
end
def deactivate
@ -50,12 +55,12 @@ class LocationsController < ApplicationController
end
def new_reactivation
@location_deactivation_period = LocationDeactivationPeriod.deactivations_without_reactivation.first
@location_deactivation_period = @location.location_deactivation_periods.deactivations_without_reactivation.first
render "toggle_active", locals: { action: "reactivate" }
end
def reactivate
@location_deactivation_period = LocationDeactivationPeriod.deactivations_without_reactivation.first
@location_deactivation_period = @location.location_deactivation_periods.deactivations_without_reactivation.first
@location_deactivation_period.reactivation_date = toggle_date("reactivation_date")
@location_deactivation_period.reactivation_date_type = params[:location_deactivation_period][:reactivation_date_type]

16
app/controllers/organisation_relationships_controller.rb

@ -39,7 +39,6 @@ class OrganisationRelationshipsController < ApplicationController
def create_housing_provider
child_organisation = @organisation
relationship_type = OrganisationRelationship::OWNING
if params[:organisation][:related_organisation_id].empty?
@organisation.errors.add :related_organisation_id, "You must choose a housing provider"
@organisations = Organisation.where.not(id: child_organisation.id).pluck(:id, :name)
@ -47,21 +46,20 @@ class OrganisationRelationshipsController < ApplicationController
return
else
parent_organisation = related_organisation
if OrganisationRelationship.exists?(child_organisation:, parent_organisation:, relationship_type:)
if OrganisationRelationship.exists?(child_organisation:, parent_organisation:)
@organisation.errors.add :related_organisation_id, "You have already added this housing provider"
@organisations = Organisation.where.not(id: child_organisation.id).pluck(:id, :name)
render "organisation_relationships/add_housing_provider"
return
end
end
create!(child_organisation:, parent_organisation:, relationship_type:)
create!(child_organisation:, parent_organisation:)
flash[:notice] = "#{related_organisation.name} is now one of #{current_user.data_coordinator? ? 'your' : "this organisation's"} housing providers"
redirect_to housing_providers_organisation_path
end
def create_managing_agent
parent_organisation = @organisation
relationship_type = OrganisationRelationship::MANAGING
if params[:organisation][:related_organisation_id].empty?
@organisation.errors.add :related_organisation_id, "You must choose a managing agent"
@organisations = Organisation.where.not(id: parent_organisation.id).pluck(:id, :name)
@ -69,14 +67,14 @@ class OrganisationRelationshipsController < ApplicationController
return
else
child_organisation = related_organisation
if OrganisationRelationship.exists?(child_organisation:, parent_organisation:, relationship_type:)
if OrganisationRelationship.exists?(child_organisation:, parent_organisation:)
@organisation.errors.add :related_organisation_id, "You have already added this managing agent"
@organisations = Organisation.where.not(id: parent_organisation.id).pluck(:id, :name)
render "organisation_relationships/add_managing_agent"
return
end
end
create!(child_organisation:, parent_organisation:, relationship_type:)
create!(child_organisation:, parent_organisation:)
flash[:notice] = "#{related_organisation.name} is now one of #{current_user.data_coordinator? ? 'your' : "this organisation's"} managing agents"
redirect_to managing_agents_organisation_path
end
@ -89,7 +87,6 @@ class OrganisationRelationshipsController < ApplicationController
relationship = OrganisationRelationship.find_by!(
child_organisation: @organisation,
parent_organisation: target_organisation,
relationship_type: OrganisationRelationship::OWNING,
)
relationship.destroy!
flash[:notice] = "#{target_organisation.name} is no longer one of #{current_user.data_coordinator? ? 'your' : "this organisation's"} housing providers"
@ -104,7 +101,6 @@ class OrganisationRelationshipsController < ApplicationController
relationship = OrganisationRelationship.find_by!(
parent_organisation: @organisation,
child_organisation: target_organisation,
relationship_type: OrganisationRelationship::MANAGING,
)
relationship.destroy!
flash[:notice] = "#{target_organisation.name} is no longer one of #{current_user.data_coordinator? ? 'your' : "this organisation's"} managing agents"
@ -113,8 +109,8 @@ class OrganisationRelationshipsController < ApplicationController
private
def create!(child_organisation:, parent_organisation:, relationship_type:)
@resource = OrganisationRelationship.new(child_organisation:, parent_organisation:, relationship_type:)
def create!(child_organisation:, parent_organisation:)
@resource = OrganisationRelationship.new(child_organisation:, parent_organisation:)
@resource.save!
end

58
app/controllers/schemes_controller.rb

@ -9,7 +9,7 @@ class SchemesController < ApplicationController
def index
redirect_to schemes_organisation_path(current_user.organisation) unless current_user.support?
all_schemes = Scheme.all.order("service_name ASC")
all_schemes = Scheme.order(confirmed: :asc, service_name: :asc)
@pagy, @schemes = pagy(filtered_collection(all_schemes, search_term))
@searched = search_term.presence
@ -27,10 +27,10 @@ class SchemesController < ApplicationController
if params[:scheme_deactivation_period].blank?
render "toggle_active", locals: { action: "deactivate" }
else
@scheme_deactivation_period.deactivation_date = deactivation_date
@scheme_deactivation_period.deactivation_date = toggle_date("deactivation_date")
@scheme_deactivation_period.deactivation_date_type = params[:scheme_deactivation_period][:deactivation_date_type]
@scheme_deactivation_period.scheme = @scheme
if @scheme_deactivation_period.validate
if @scheme_deactivation_period.valid?
redirect_to scheme_deactivate_confirm_path(@scheme, deactivation_date: @scheme_deactivation_period.deactivation_date, deactivation_date_type: @scheme_deactivation_period.deactivation_date_type)
else
render "toggle_active", locals: { action: "deactivate" }, status: :unprocessable_entity
@ -39,8 +39,13 @@ class SchemesController < ApplicationController
end
def deactivate_confirm
@deactivation_date = params[:deactivation_date]
@deactivation_date_type = params[:deactivation_date_type]
@affected_logs = @scheme.lettings_logs.filter_by_before_startdate(params[:deactivation_date])
if @affected_logs.count.zero?
deactivate
else
@deactivation_date = params[:deactivation_date]
@deactivation_date_type = params[:deactivation_date_type]
end
end
def deactivate
@ -50,10 +55,25 @@ class SchemesController < ApplicationController
redirect_to scheme_details_path(@scheme)
end
def reactivate
def new_reactivation
@scheme_deactivation_period = @scheme.scheme_deactivation_periods.deactivations_without_reactivation.first
render "toggle_active", locals: { action: "reactivate" }
end
def reactivate
@scheme_deactivation_period = @scheme.scheme_deactivation_periods.deactivations_without_reactivation.first
@scheme_deactivation_period.reactivation_date = toggle_date("reactivation_date")
@scheme_deactivation_period.reactivation_date_type = params[:scheme_deactivation_period][:reactivation_date_type]
if @scheme_deactivation_period.update(reactivation_date: toggle_date("reactivation_date"))
flash[:notice] = reactivate_success_notice
redirect_to scheme_details_path(@scheme)
else
render "toggle_active", locals: { action: "reactivate" }, status: :unprocessable_entity
end
end
def new
@scheme = Scheme.new
end
@ -239,8 +259,7 @@ private
:support_type,
:arrangement_type,
:intended_stay,
:confirmed,
:deactivation_date)
:confirmed)
if arrangement_type_changed_to_different_org?(required_params)
required_params[:managing_organisation_id] = nil
@ -303,18 +322,27 @@ private
end
end
def deactivation_date
def reactivate_success_notice
case @scheme.status
when :active
"#{@scheme.service_name} has been reactivated"
when :reactivating_soon
"#{@scheme.service_name} will reactivate on #{toggle_date('reactivation_date').to_time.to_formatted_s(:govuk_date)}"
end
end
def toggle_date(key)
if params[:scheme_deactivation_period].blank?
return
elsif params[:scheme_deactivation_period][:deactivation_date_type] == "default"
elsif params[:scheme_deactivation_period]["#{key}_type".to_sym] == "default"
return FormHandler.instance.current_collection_start_date
elsif params[:scheme_deactivation_period][:deactivation_date].present?
return params[:scheme_deactivation_period][:deactivation_date]
elsif params[:scheme_deactivation_period][key.to_sym].present?
return params[:scheme_deactivation_period][key.to_sym]
end
day = params[:scheme_deactivation_period]["deactivation_date(3i)"]
month = params[:scheme_deactivation_period]["deactivation_date(2i)"]
year = params[:scheme_deactivation_period]["deactivation_date(1i)"]
day = params[:scheme_deactivation_period]["#{key}(3i)"]
month = params[:scheme_deactivation_period]["#{key}(2i)"]
year = params[:scheme_deactivation_period]["#{key}(1i)"]
return nil if [day, month, year].any?(&:blank?)
Time.zone.local(year.to_i, month.to_i, day.to_i) if Date.valid_date?(year.to_i, month.to_i, day.to_i)

1
app/controllers/users_controller.rb

@ -3,6 +3,7 @@ class UsersController < ApplicationController
include Devise::Controllers::SignInOut
include Helpers::Email
include Modules::SearchFilter
before_action :authenticate_user!
before_action :find_resource, except: %i[new create]
before_action :authenticate_scope!, except: %i[new]

7
app/frontend/application.js

@ -4,18 +4,19 @@
// files to reference that code so it'll be compiled.
// Polyfills for IE
import '@stimulus/polyfills'
import '@webcomponents/webcomponentsjs'
import 'core-js/stable'
import 'regenerator-runtime/runtime'
import '@stimulus/polyfills'
import 'custom-event-polyfill'
import 'intersection-observer'
import 'regenerator-runtime/runtime'
//
import { initAll as GOVUKFrontend } from 'govuk-frontend'
import { initAll as GOVUKPrototypeComponents } from 'govuk-prototype-components'
import './styles/application.scss'
import './controllers'
import './cookie-banner'
import './styles/application.scss'
require.context('govuk-frontend/govuk/assets')

57
app/frontend/cookie-banner.js

@ -0,0 +1,57 @@
const cookieBannerEl = document.querySelector('.js-cookie-banner')
if (cookieBannerEl) {
const cookieFormEl = document.querySelector('.js-cookie-form')
cookieFormEl.addEventListener('click', (e) => {
if (e.target.tagName !== 'BUTTON') {
return
}
const body = new window.FormData(cookieFormEl)
body.append('cookies_form[accept_analytics_cookies]', e.target.value)
fetch(cookieFormEl.action, {
method: 'PUT',
headers: {
Accept: 'application/json'
},
body
})
.then((res) => {
if (res.status >= 200 && res.status < 300) {
return res
}
throw new Error(res)
})
.then((res) => res.json())
.then(({ message }) => {
const messageEl = cookieBannerEl.querySelector('.js-cookie-message')
messageEl.textContent = message
cookieBannerEl
.querySelector('.js-cookie-banner__form')
.setAttribute('hidden', '')
cookieBannerEl
.querySelector('.js-cookie-banner__success')
.removeAttribute('hidden')
})
const gaSrc = window.analyticsScript
if (e.target.value === 'on' && gaSrc) {
const scriptEl = document.createElement('script')
scriptEl.src = gaSrc
document.body.appendChild(scriptEl)
}
e.preventDefault()
})
const hideBannerEl = document.querySelector('.js-hide-cookie-banner')
hideBannerEl.addEventListener('click', (e) => {
e.preventDefault()
cookieBannerEl.setAttribute('hidden', '')
})
}

32
app/helpers/locations_helper.rb

@ -26,12 +26,12 @@ module LocationsHelper
def display_location_attributes(location)
base_attributes = [
{ name: "Postcode", value: location.postcode },
{ name: "Local authority", value: location.location_admin_district },
{ name: "Location name", value: location.name, edit: true },
{ name: "Local authority", value: location.location_admin_district },
{ name: "Total number of units at this location", value: location.units },
{ name: "Common type of unit", value: location.type_of_unit },
{ name: "Mobility type", value: location.mobility_type },
{ name: "Code", value: location.location_code },
{ name: "Location code", value: location.location_code },
{ name: "Availability", value: location_availability(location) },
]
@ -42,22 +42,9 @@ module LocationsHelper
base_attributes
end
ActivePeriod = Struct.new(:from, :to)
def active_periods(location)
periods = [ActivePeriod.new(location.available_from, nil)]
sorted_deactivation_periods = remove_nested_periods(location.location_deactivation_periods.sort_by(&:deactivation_date))
sorted_deactivation_periods.each do |deactivation|
periods.last.to = deactivation.deactivation_date
periods << ActivePeriod.new(deactivation.reactivation_date, nil)
end
remove_overlapping_and_empty_periods(periods)
end
def location_availability(location)
availability = ""
active_periods(location).each do |period|
location_active_periods(location).each do |period|
if period.from.present?
availability << "\nActive from #{period.from.to_formatted_s(:govuk_date)}"
availability << " to #{(period.to - 1.day).to_formatted_s(:govuk_date)}\nDeactivated on #{period.to.to_formatted_s(:govuk_date)}" if period.to.present?
@ -68,6 +55,19 @@ module LocationsHelper
private
ActivePeriod = Struct.new(:from, :to)
def location_active_periods(location)
periods = [ActivePeriod.new(location.available_from, nil)]
sorted_deactivation_periods = remove_nested_periods(location.location_deactivation_periods.sort_by(&:deactivation_date))
sorted_deactivation_periods.each do |deactivation|
periods.last.to = deactivation.deactivation_date
periods << ActivePeriod.new(deactivation.reactivation_date, nil)
end
remove_overlapping_and_empty_periods(periods)
end
def remove_overlapping_and_empty_periods(periods)
periods.select { |period| period.from.present? && (period.to.nil? || period.from < period.to) }
end

21
app/helpers/logs_helper.rb

@ -0,0 +1,21 @@
module LogsHelper
def log_type_for_controller(controller)
case controller.class.to_s
when "LettingsLogsController"
"lettings"
when "SalesLogsController"
"sales"
else
raise "Log type not found for #{controller.class}"
end
end
def bulk_upload_path_for_controller(controller, id:)
case log_type_for_controller(controller)
when "lettings"
bulk_upload_lettings_log_path(id:)
when "sales"
bulk_upload_sales_log_path(id:)
end
end
end

4
app/helpers/navigation_items_helper.rb

@ -65,11 +65,11 @@ module NavigationItemsHelper
private
def lettings_logs_current?(path)
path == "/lettings-logs"
path.starts_with?("/lettings-logs")
end
def sales_logs_current?(path)
path == "/sales-logs"
path.starts_with?("/sales-logs")
end
def users_current?(path)

52
app/helpers/schemes_helper.rb

@ -1,5 +1,5 @@
module SchemesHelper
def display_scheme_attributes(scheme)
def display_scheme_attributes(scheme, user)
base_attributes = [
{ name: "Scheme code", value: scheme.id_to_display },
{ name: "Name", value: scheme.service_name, edit: true },
@ -18,21 +18,57 @@ module SchemesHelper
]
if FeatureToggle.scheme_toggle_enabled?
base_attributes.append({ name: "Status", value: scheme.status })
base_attributes.append({ name: "Status", value: status_tag(scheme.status) })
end
if user.data_coordinator?
base_attributes.delete_if { |item| item[:name] == "Housing stock owned by" }
end
if scheme.arrangement_type_same?
base_attributes.delete({ name: "Organisation providing support", value: scheme.managing_organisation&.name })
base_attributes.delete_if { |item| item[:name] == "Organisation providing support" }
end
base_attributes
end
def scheme_availability(scheme)
availability = "Active from #{scheme.available_from.to_formatted_s(:govuk_date)}"
scheme.scheme_deactivation_periods.each do |deactivation|
availability << " to #{(deactivation.deactivation_date - 1.day).to_formatted_s(:govuk_date)}\nDeactivated on #{deactivation.deactivation_date.to_formatted_s(:govuk_date)}"
availability << "\nActive from #{deactivation.reactivation_date.to_formatted_s(:govuk_date)}" if deactivation.reactivation_date.present?
availability = ""
scheme_active_periods(scheme).each do |period|
if period.from.present?
availability << "\nActive from #{period.from.to_formatted_s(:govuk_date)}"
availability << " to #{(period.to - 1.day).to_formatted_s(:govuk_date)}\nDeactivated on #{period.to.to_formatted_s(:govuk_date)}" if period.to.present?
end
end
availability.strip
end
private
ActivePeriod = Struct.new(:from, :to)
def scheme_active_periods(scheme)
periods = [ActivePeriod.new(scheme.available_from, nil)]
sorted_deactivation_periods = remove_nested_periods(scheme.scheme_deactivation_periods.sort_by(&:deactivation_date))
sorted_deactivation_periods.each do |deactivation|
periods.last.to = deactivation.deactivation_date
periods << ActivePeriod.new(deactivation.reactivation_date, nil)
end
availability
remove_overlapping_and_empty_periods(periods)
end
def remove_overlapping_and_empty_periods(periods)
periods.select { |period| period.from.present? && (period.to.nil? || period.from < period.to) }
end
def remove_nested_periods(periods)
periods.select { |inner_period| periods.none? { |outer_period| is_nested?(inner_period, outer_period) } }
end
def is_nested?(inner, outer)
return false if inner == outer
return false if [inner.deactivation_date, inner.reactivation_date, outer.deactivation_date, outer.reactivation_date].any?(&:blank?)
[inner.deactivation_date, inner.reactivation_date].all? { |date| date.between?(outer.deactivation_date, outer.reactivation_date) }
end
end

2
app/helpers/tag_helper.rb

@ -9,6 +9,7 @@ module TagHelper
active: "Active",
incomplete: "Incomplete",
deactivating_soon: "Deactivating soon",
activating_soon: "Activating soon",
reactivating_soon: "Reactivating soon",
deactivated: "Deactivated",
}.freeze
@ -21,6 +22,7 @@ module TagHelper
active: "green",
incomplete: "red",
deactivating_soon: "yellow",
activating_soon: "blue",
reactivating_soon: "blue",
deactivated: "grey",
}.freeze

17
app/helpers/toggle_active_scheme_helper.rb

@ -0,0 +1,17 @@
module ToggleActiveSchemeHelper
def toggle_scheme_form_path(action, scheme)
if action == "deactivate"
scheme_new_deactivation_path(scheme)
else
scheme_reactivate_path(scheme)
end
end
def date_type_question(action)
action == "deactivate" ? :deactivation_date_type : :reactivation_date_type
end
def date_question(action)
action == "deactivate" ? :deactivation_date : :reactivation_date
end
end

8
app/models/form.rb

@ -223,4 +223,12 @@ class Form
end
end
end
def in_crossover_period?(now: Time.zone.now)
((end_date - 3.months) < now) && (now < end_date)
end
def inspect
"#<#{self.class} @type=#{type} @name=#{name}>"
end
end

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

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

19
app/models/form/sales/pages/buyer2_income.rb

@ -0,0 +1,19 @@
class Form::Sales::Pages::Buyer2Income < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "buyer_2_income"
@header = ""
@description = ""
@subsection = subsection
@depends_on = [{
"jointpur" => 1,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::Buyer2IncomeKnown.new(nil, nil, self),
Form::Sales::Questions::Buyer2Income.new(nil, nil, self),
]
end
end

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

@ -0,0 +1,16 @@
class Form::Sales::Questions::Buyer1Mortgage < ::Form::Question
def initialize(id, hsh, page)
super
@id = "inc1mort"
@check_answer_label = "Buyer 1's income used for mortgage application"
@header = "Was buyer 1's income used for a mortgage application"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@page = page
end
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
}.freeze
end

14
app/models/form/sales/questions/buyer2_income.rb

@ -0,0 +1,14 @@
class Form::Sales::Questions::Buyer2Income < ::Form::Question
def initialize(id, hsh, page)
super
@id = "income2"
@check_answer_label = "Buyer 2’s gross annual income"
@header = "Buyer 2’s gross annual income"
@type = "numeric"
@page = page
@min = 0
@step = 1
@width = 5
@prefix = "£"
end
end

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

@ -0,0 +1,21 @@
class Form::Sales::Questions::Buyer2IncomeKnown < ::Form::Question
def initialize(id, hsh, page)
super
@id = "income2nk"
@check_answer_label = "Buyer 2’s gross annual income"
@header = "Do you know buyer 2’s annual income?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@page = page
@guidance_position = GuidancePosition::BOTTOM
@guidance_partial = "what_counts_as_income_sales"
@conditional_for = {
"income2" => [0],
}
end
ANSWER_OPTIONS = {
"0" => { "value" => "Yes" },
"1" => { "value" => "No" },
}.freeze
end

2
app/models/form/sales/sections/finances.rb

@ -6,7 +6,7 @@ class Form::Sales::Sections::Finances < ::Form::Section
@description = ""
@form = form
@subsections = [
Form::Sales::Subsections::IncomeBenefitsAndOutgoings.new(nil, nil, self),
Form::Sales::Subsections::IncomeBenefitsAndSavings.new(nil, nil, self),
]
end
end

15
app/models/form/sales/subsections/income_benefits_and_outgoings.rb

@ -1,15 +0,0 @@
class Form::Sales::Subsections::IncomeBenefitsAndOutgoings < ::Form::Subsection
def initialize(id, hsh, section)
super
@id = "income_benefits_and_outgoings"
@label = "Income, benefits and outgoings"
@section = section
@depends_on = [{ "setup_completed?" => true }]
end
def pages
@pages ||= [
Form::Sales::Pages::Buyer1Income.new(nil, nil, self),
]
end
end

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

@ -0,0 +1,17 @@
class Form::Sales::Subsections::IncomeBenefitsAndSavings < ::Form::Subsection
def initialize(id, hsh, section)
super
@id = "income_benefits_and_savings"
@label = "Income, benefits and savings"
@section = section
@depends_on = [{ "setup_completed?" => true }]
end
def pages
@pages ||= [
Form::Sales::Pages::Buyer1Income.new(nil, nil, self),
Form::Sales::Pages::Buyer1Mortgage.new(nil, nil, self),
Form::Sales::Pages::Buyer2Income.new(nil, nil, self),
]
end
end

5
app/models/form_handler.rb

@ -49,6 +49,11 @@ class FormHandler
today < window_end_date ? today.year - 1 : today.year
end
def collection_start_date(date)
window_end_date = Time.zone.local(date.year, 4, 1)
date < window_end_date ? Time.zone.local(date.year - 1, 4, 1) : Time.zone.local(date.year, 4, 1)
end
def current_collection_start_date
Time.zone.local(current_collection_start_year, 4, 1)
end

41
app/models/forms/bulk_upload_lettings/prepare_your_file.rb

@ -0,0 +1,41 @@
module Forms
module BulkUploadLettings
class PrepareYourFile
include ActiveModel::Model
include ActiveModel::Attributes
include Rails.application.routes.url_helpers
attribute :year, :integer
def view_path
"bulk_upload_lettings_logs/forms/prepare_your_file"
end
def back_path
if in_crossover_period?
Rails.application.routes.url_helpers.bulk_upload_lettings_log_path(id: "year", form: { year: })
else
Rails.application.routes.url_helpers.lettings_logs_path
end
end
def next_path
bulk_upload_lettings_log_path(id: "upload-your-file", form: { year: })
end
def template_path
"/files/bulk-upload-lettings-template-v1.xlsx"
end
def year_combo
"#{year}/#{year + 1 - 2000}"
end
private
def in_crossover_period?
FormHandler.instance.forms.values.any?(&:in_crossover_period?)
end
end
end
end

19
app/models/forms/bulk_upload_lettings/upload_your_file.rb

@ -0,0 +1,19 @@
module Forms
module BulkUploadLettings
class UploadYourFile
include ActiveModel::Model
include ActiveModel::Attributes
include Rails.application.routes.url_helpers
attribute :year, :integer
def view_path
"bulk_upload_lettings_logs/forms/upload_your_file"
end
def back_path
bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year: })
end
end
end
end

37
app/models/forms/bulk_upload_lettings/year.rb

@ -0,0 +1,37 @@
module Forms
module BulkUploadLettings
class Year
include ActiveModel::Model
include ActiveModel::Attributes
include Rails.application.routes.url_helpers
attribute :year, :integer
validates :year, presence: true
def view_path
"bulk_upload_lettings_logs/forms/year"
end
def options
possible_years.map do |year|
OpenStruct.new(id: year, name: "#{year}/#{year + 1}")
end
end
def back_path
lettings_logs_path
end
def next_path
bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year: })
end
private
def possible_years
FormHandler.instance.lettings_forms.values.map { |form| form.start_date.year }.sort.reverse
end
end
end
end

41
app/models/forms/bulk_upload_sales/prepare_your_file.rb

@ -0,0 +1,41 @@
module Forms
module BulkUploadSales
class PrepareYourFile
include ActiveModel::Model
include ActiveModel::Attributes
include Rails.application.routes.url_helpers
attribute :year, :integer
def view_path
"bulk_upload_sales_logs/forms/prepare_your_file"
end
def back_path
if in_crossover_period?
Rails.application.routes.url_helpers.bulk_upload_sales_log_path(id: "year", form: { year: })
else
Rails.application.routes.url_helpers.sales_logs_path
end
end
def next_path
bulk_upload_sales_log_path(id: "upload-your-file", form: { year: })
end
def template_path
"/files/bulk-upload-sales-template-v1.xlsx"
end
def year_combo
"#{year}/#{year + 1 - 2000}"
end
private
def in_crossover_period?
FormHandler.instance.forms.values.any?(&:in_crossover_period?)
end
end
end
end

19
app/models/forms/bulk_upload_sales/upload_your_file.rb

@ -0,0 +1,19 @@
module Forms
module BulkUploadSales
class UploadYourFile
include ActiveModel::Model
include ActiveModel::Attributes
include Rails.application.routes.url_helpers
attribute :year, :integer
def view_path
"bulk_upload_sales_logs/forms/upload_your_file"
end
def back_path
bulk_upload_sales_log_path(id: "prepare-your-file", form: { year: })
end
end
end
end

37
app/models/forms/bulk_upload_sales/year.rb

@ -0,0 +1,37 @@
module Forms
module BulkUploadSales
class Year
include ActiveModel::Model
include ActiveModel::Attributes
include Rails.application.routes.url_helpers
attribute :year, :integer
validates :year, presence: true
def view_path
"bulk_upload_sales_logs/forms/year"
end
def options
possible_years.map do |year|
OpenStruct.new(id: year, name: "#{year}/#{year + 1}")
end
end
def back_path
sales_logs_path
end
def next_path
bulk_upload_sales_log_path(id: "prepare-your-file", form: { year: })
end
private
def possible_years
FormHandler.instance.sales_forms.values.map { |form| form.start_date.year }.sort.reverse
end
end
end
end

24
app/models/location.rb

@ -370,19 +370,29 @@ class Location < ApplicationRecord
end
def available_from
startdate || [created_at, FormHandler.instance.current_collection_start_date].min
return startdate if startdate.present?
FormHandler.instance.collection_start_date(created_at)
end
def open_deactivation
location_deactivation_periods.deactivations_without_reactivation.first
end
def status
open_deactivation = location_deactivation_periods.deactivations_without_reactivation.first
recent_deactivation = location_deactivation_periods.order("created_at").last
def recent_deactivation
location_deactivation_periods.order("created_at").last
end
return :deactivated if open_deactivation&.deactivation_date.present? && Time.zone.now >= open_deactivation.deactivation_date
return :deactivating_soon if open_deactivation&.deactivation_date.present? && Time.zone.now < open_deactivation.deactivation_date
return :reactivating_soon if recent_deactivation&.reactivation_date.present? && Time.zone.now < recent_deactivation.reactivation_date
def status(date = Time.zone.now)
return :incomplete unless confirmed
return :deactivated if open_deactivation&.deactivation_date.present? && date >= open_deactivation.deactivation_date
return :deactivating_soon if open_deactivation&.deactivation_date.present? && date < open_deactivation.deactivation_date
return :reactivating_soon if recent_deactivation&.reactivation_date.present? && date < recent_deactivation.reactivation_date
return :activating_soon if startdate.present? && date < startdate
:active
end
alias_method :status_at, :status
def active?
status == :active

5
app/models/organisation.rb

@ -13,9 +13,10 @@ class Organisation < ApplicationRecord
has_many :child_organisation_relationships, foreign_key: :parent_organisation_id, class_name: "OrganisationRelationship"
has_many :child_organisations, through: :child_organisation_relationships
has_many :housing_provider_relationships, -> { where(relationship_type: OrganisationRelationship::OWNING) }, foreign_key: :child_organisation_id, class_name: "OrganisationRelationship"
has_many :housing_provider_relationships, foreign_key: :child_organisation_id, class_name: "OrganisationRelationship"
has_many :housing_providers, through: :housing_provider_relationships, source: :parent_organisation
has_many :managing_agent_relationships, -> { where(relationship_type: OrganisationRelationship::MANAGING) }, foreign_key: :parent_organisation_id, class_name: "OrganisationRelationship"
has_many :managing_agent_relationships, foreign_key: :parent_organisation_id, class_name: "OrganisationRelationship"
has_many :managing_agents, through: :managing_agent_relationships, source: :child_organisation
scope :search_by_name, ->(name) { where("name ILIKE ?", "%#{name}%") }

12
app/models/organisation_relationship.rb

@ -1,16 +1,4 @@
class OrganisationRelationship < ApplicationRecord
belongs_to :child_organisation, class_name: "Organisation"
belongs_to :parent_organisation, class_name: "Organisation"
scope :owning, -> { where(relationship_type: OWNING) }
scope :managing, -> { where(relationship_type: MANAGING) }
OWNING = "owning".freeze
MANAGING = "managing".freeze
RELATIONSHIP_TYPE = {
OWNING => 0,
MANAGING => 1,
}.freeze
enum relationship_type: RELATIONSHIP_TYPE
end

26
app/models/scheme.rb

@ -210,18 +210,32 @@ class Scheme < ApplicationRecord
end
def available_from
created_at
FormHandler.instance.collection_start_date(created_at)
end
def status
recent_deactivation = scheme_deactivation_periods.deactivations_without_reactivation.first
return :active if recent_deactivation.blank?
return :deactivating_soon if Time.zone.now < recent_deactivation.deactivation_date
def open_deactivation
scheme_deactivation_periods.deactivations_without_reactivation.first
end
def recent_deactivation
scheme_deactivation_periods.order("created_at").last
end
:deactivated
def status(date = Time.zone.now)
return :incomplete unless confirmed
return :deactivated if open_deactivation&.deactivation_date.present? && date >= open_deactivation.deactivation_date
return :deactivating_soon if open_deactivation&.deactivation_date.present? && date < open_deactivation.deactivation_date
return :reactivating_soon if recent_deactivation&.reactivation_date.present? && date < recent_deactivation.reactivation_date
:active
end
alias_method :status_at, :status
def active?
status == :active
end
def reactivating_soon?
status == :reactivating_soon
end
end

35
app/models/scheme_deactivation_period.rb

@ -1,15 +1,40 @@
class SchemeDeactivationPeriodValidator < ActiveModel::Validator
def validate(record)
scheme = record.scheme
recent_deactivation = scheme.scheme_deactivation_periods.deactivations_without_reactivation.first
if recent_deactivation.present?
validate_reactivation(record, recent_deactivation, scheme)
else
validate_deactivation(record, scheme)
end
end
def validate_reactivation(record, recent_deactivation, scheme)
if record.reactivation_date.blank?
if record.reactivation_date_type.blank?
record.errors.add(:reactivation_date_type, message: I18n.t("validations.scheme.toggle_date.not_selected"))
elsif record.reactivation_date_type == "other"
record.errors.add(:reactivation_date, message: I18n.t("validations.scheme.toggle_date.invalid"))
end
elsif !record.reactivation_date.between?(scheme.available_from, Time.zone.local(2200, 1, 1))
record.errors.add(:reactivation_date, message: I18n.t("validations.scheme.toggle_date.out_of_range", date: scheme.available_from.to_formatted_s(:govuk_date)))
elsif record.reactivation_date < recent_deactivation.deactivation_date
record.errors.add(:reactivation_date, message: I18n.t("validations.scheme.reactivation.before_deactivation", date: recent_deactivation.deactivation_date.to_formatted_s(:govuk_date)))
end
end
def validate_deactivation(record, scheme)
if record.deactivation_date.blank?
if record.deactivation_date_type.blank?
record.errors.add(:deactivation_date_type, message: I18n.t("validations.scheme.deactivation_date.not_selected"))
record.errors.add(:deactivation_date_type, message: I18n.t("validations.scheme.toggle_date.not_selected"))
elsif record.deactivation_date_type == "other"
record.errors.add(:deactivation_date, message: I18n.t("validations.scheme.deactivation_date.invalid"))
record.errors.add(:deactivation_date, message: I18n.t("validations.scheme.toggle_date.invalid"))
end
elsif scheme.scheme_deactivation_periods.any? { |period| period.reactivation_date.present? && record.deactivation_date.between?(period.deactivation_date, period.reactivation_date - 1.day) }
record.errors.add(:deactivation_date, message: I18n.t("validations.scheme.deactivation.during_deactivated_period"))
else
collection_start_date = FormHandler.instance.current_collection_start_date
unless record.deactivation_date.between?(collection_start_date, Time.zone.local(2200, 1, 1))
record.errors.add(:deactivation_date, message: I18n.t("validations.scheme.deactivation_date.out_of_range", date: collection_start_date.to_formatted_s(:govuk_date)))
unless record.deactivation_date.between?(scheme.available_from, Time.zone.local(2200, 1, 1))
record.errors.add(:deactivation_date, message: I18n.t("validations.scheme.toggle_date.out_of_range", date: scheme.available_from.to_formatted_s(:govuk_date)))
end
end
end

5
app/models/validations/date_validations.rb

@ -1,4 +1,6 @@
module Validations::DateValidations
include Validations::SharedValidations
def validate_property_major_repairs(record)
date_valid?("mrcdate", record)
if record["startdate"].present? && record["mrcdate"].present? && record["startdate"] < record["mrcdate"]
@ -59,6 +61,9 @@ module Validations::DateValidations
if record["mrcdate"].present? && record.startdate < record["mrcdate"]
record.errors.add :startdate, I18n.t("validations.setup.startdate.after_major_repair_date")
end
location_during_startdate_validation(record, :startdate)
scheme_during_startdate_validation(record, :startdate)
end
private

11
app/models/validations/setup_validations.rb

@ -1,10 +1,21 @@
module Validations::SetupValidations
include Validations::SharedValidations
def validate_irproduct_other(record)
if intermediate_product_rent_type?(record) && record.irproduct_other.blank?
record.errors.add :irproduct_other, I18n.t("validations.setup.intermediate_rent_product_name.blank")
end
end
def validate_location(record)
location_during_startdate_validation(record, :location_id)
end
def validate_scheme(record)
location_during_startdate_validation(record, :scheme_id)
scheme_during_startdate_validation(record, :scheme_id)
end
private
def intermediate_product_rent_type?(record)

35
app/models/validations/shared_validations.rb

@ -33,4 +33,39 @@ module Validations::SharedValidations
end
end
end
def location_during_startdate_validation(record, field)
location_inactive_status = inactive_status(record.startdate, record.location)
if location_inactive_status.present?
date, scope, deactivation_date = location_inactive_status.values_at(:date, :scope, :deactivation_date)
record.errors.add field, I18n.t("validations.setup.startdate.location.#{scope}", postcode: record.location.postcode, date:, deactivation_date:)
end
end
def scheme_during_startdate_validation(record, field)
scheme_inactive_status = inactive_status(record.startdate, record.scheme)
if scheme_inactive_status.present?
date, scope, deactivation_date = scheme_inactive_status.values_at(:date, :scope, :deactivation_date)
record.errors.add field, I18n.t("validations.setup.startdate.scheme.#{scope}", name: record.scheme.service_name, date:, deactivation_date:)
end
end
def inactive_status(date, resource)
return if date.blank? || resource.blank?
status = resource.status_at(date)
return unless %i[reactivating_soon activating_soon deactivated].include?(status)
closest_reactivation = resource.recent_deactivation
open_deactivation = resource.open_deactivation
date = case status
when :reactivating_soon then closest_reactivation.reactivation_date
when :activating_soon then resource&.available_from
when :deactivated then open_deactivation.deactivation_date
end
{ scope: status, date: date&.to_formatted_s(:govuk_date), deactivation_date: closest_reactivation&.deactivation_date&.to_formatted_s(:govuk_date) }
end
end

33
app/views/bulk_upload_lettings_logs/forms/prepare_your_file.html.erb

@ -0,0 +1,33 @@
<% content_for :before_content do %>
<%= govuk_back_link href: @form.back_path %>
<% end %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<%= form_with model: @form, scope: :form, url: bulk_upload_lettings_log_path(id: "prepare-your-file"), method: :patch do |f| %>
<%= f.hidden_field :year %>
<span class="govuk-caption-l">Upload lettings logs in bulk (<%= @form.year_combo %>)</span>
<h1 class="govuk-heading-l">Prepare your file</h1>
<h2 class="govuk-heading-m">Create your file</h2>
<ul class="govuk-list govuk-list--bullet">
<li>Download the <%= govuk_link_to "bulk lettings template", @form.template_path %></li>
<li>Export the data from your housing management system, matching the template</li>
<li>If you cannot export it in this format, you may have to input it manually</li>
</ul>
<h2 class="govuk-heading-m">Check your data</h2>
<ul class="govuk-list govuk-list--bullet">
<li>Check data is complete and formatted correctly, using data specifications (opens in a new tab)</li>
</ul>
<h2 class="govuk-heading-m">Save your file</h2>
<ul class="govuk-list govuk-list--bullet">
<li>Save the file (CSV format <strong>only</strong>)</li>
</ul>
<%= f.govuk_submit %>
<% end %>
</div>
</div>

17
app/views/bulk_upload_lettings_logs/forms/upload_your_file.html.erb

@ -0,0 +1,17 @@
<% content_for :before_content do %>
<%= govuk_back_link href: @form.back_path %>
<% end %>
<%= form_with model: @form, scope: :form, url: bulk_upload_lettings_log_path(id: "upload-your-file"), method: :patch do |f| %>
<%= f.govuk_error_summary %>
<div>
Upload your file goes here
</div>
<div>
year selected <%= @form.year %>
</div>
<%= f.govuk_submit %>
<% end %>

16
app/views/bulk_upload_lettings_logs/forms/year.html.erb

@ -0,0 +1,16 @@
<% content_for :before_content do %>
<%= govuk_back_link href: @form.back_path %>
<% end %>
<%= form_with model: @form, scope: :form, url: bulk_upload_lettings_log_path(id: "year"), method: :patch do |f| %>
<%= f.govuk_error_summary %>
<%= f.govuk_collection_radio_buttons :year,
@form.options,
:id,
:name,
legend: { text: "Which year are you uploading data for?", size: "l" },
caption: { text: "Upload lettings logs in bulk", size: "l" } %>
<%= f.govuk_submit %>
<% end %>

33
app/views/bulk_upload_sales_logs/forms/prepare_your_file.html.erb

@ -0,0 +1,33 @@
<% content_for :before_content do %>
<%= govuk_back_link href: @form.back_path %>
<% end %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<%= form_with model: @form, scope: :form, url: bulk_upload_sales_log_path(id: "prepare-your-file"), method: :patch do |f| %>
<%= f.hidden_field :year %>
<span class="govuk-caption-l">Upload sales logs in bulk (<%= @form.year_combo %>)</span>
<h1 class="govuk-heading-l">Prepare your file</h1>
<h2 class="govuk-heading-m">Create your file</h2>
<ul class="govuk-list govuk-list--bullet">
<li>Download the <%= govuk_link_to "bulk sales template", @form.template_path %></li>
<li>Export the data from your housing management system, matching the template</li>
<li>If you cannot export it in this format, you may have to input it manually</li>
</ul>
<h2 class="govuk-heading-m">Check your data</h2>
<ul class="govuk-list govuk-list--bullet">
<li>Check data is complete and formatted correctly, using data specifications (opens in a new tab)</li>
</ul>
<h2 class="govuk-heading-m">Save your file</h2>
<ul class="govuk-list govuk-list--bullet">
<li>Save the file (CSV format <strong>only</strong>)</li>
</ul>
<%= f.govuk_submit %>
<% end %>
</div>
</div>

17
app/views/bulk_upload_sales_logs/forms/upload_your_file.html.erb

@ -0,0 +1,17 @@
<% content_for :before_content do %>
<%= govuk_back_link href: @form.back_path %>
<% end %>
<%= form_with model: @form, scope: :form, url: bulk_upload_sales_log_path(id: "upload-your-file"), method: :patch do |f| %>
<%= f.govuk_error_summary %>
<div>
Upload your file goes here
</div>
<div>
year selected <%= @form.year %>
</div>
<%= f.govuk_submit %>
<% end %>

16
app/views/bulk_upload_sales_logs/forms/year.html.erb

@ -0,0 +1,16 @@
<% content_for :before_content do %>
<%= govuk_back_link href: @form.back_path %>
<% end %>
<%= form_with model: @form, scope: :form, url: bulk_upload_sales_log_path(id: "year"), method: :patch do |f| %>
<%= f.govuk_error_summary %>
<%= f.govuk_collection_radio_buttons :year,
@form.options,
:id,
:name,
legend: { text: "Which year are you uploading data for?", size: "l" },
caption: { text: "Upload sales logs in bulk", size: "l" } %>
<%= f.govuk_submit %>
<% end %>

48
app/views/cookies/_banner.html.erb

@ -0,0 +1,48 @@
<div class="govuk-cookie-banner js-cookie-banner" role="region" aria-label="Cookies on CORE">
<div class="govuk-cookie-banner__message govuk-width-container js-cookie-banner__form">
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<h2 class="govuk-cookie-banner__heading govuk-heading-m">Cookies on CORE</h2>
<div class="govuk-cookie-banner__content">
<p class="govuk-body">We use some essential cookies to make this service work.</p>
<p class="govuk-body">We’d like to use analytics cookies so we can understand how you use the service and make improvements.</p>
</div>
</div>
</div>
<%= form_with url: cookies_path, class: "js-cookie-form", method: :put do |f| %>
<div class="govuk-button-group">
<button type="submit" class="govuk-button" name="cookies_form[analytics_consent]" value="on">
Accept analytics cookies
</button>
<button type="submit" class="govuk-button" name="cookies_form[analytics_consent]" value="off">
Reject analytics cookies
</button>
<%= govuk_link_to "View cookies", cookies_path %>
</div>
<% end %>
</div>
<div class="govuk-cookie-banner__message govuk-width-container js-cookie-banner__success" role="alert" hidden>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<div class="govuk-cookie-banner__content">
<p class="govuk-body">
<span class="js-cookie-message">
You’ve set your cookie preferences.
</span>
You can <%= govuk_link_to "change your cookie settings", cookies_path %> at any time.
</p>
</div>
</div>
</div>
<div class="govuk-button-group">
<button class="govuk-button js-hide-cookie-banner">
Hide this message
</button>
</div>
</div>
</div>

24
app/views/layouts/application.html.erb

@ -25,15 +25,15 @@
<% gtm_container = get_gtm_container %>
<% gtm_id = get_gtm_id %>
<!-- Google Tag Manager (doesn't store personal info until permission given) -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','<%= gtm_container %>');</script>
<!-- End Google Tag Manager -->
<% if cookies[:accept_analytics_cookies] == "on" %>
<!-- Google Tag Manager (doesn't store personal info until permission given) -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','<%= gtm_container %>');</script>
<!-- End Google Tag Manager -->
<script async src="https://www.googletagmanager.com/gtag/js?id=<%= gtm_id %>"></script>
<% else %>
<script>
@ -72,13 +72,17 @@
document.body.className = ((document.body.className) ? document.body.className + " js-enabled" : "js-enabled");
</script>
<!-- Google Tag Manager (noscript) -->
<% if cookies[:accept_analytics_cookies] %>
<% if cookies[:accept_analytics_cookies] == "on" %>
<!-- Google Tag Manager (noscript) -->
<noscript>
<iframe src="https://www.googletagmanager.com/ns.html?id=<% gtm_container %>" height="0" width="0" style="display:none;visibility:hidden"></iframe>
</noscript>
<% end %>
<% unless cookies[:accept_analytics_cookies] || current_page?(cookies_path) %>
<%= render "cookies/banner" %>
<% end %>
<%= govuk_skip_link %>
<%= govuk_header(

2
app/views/locations/deactivate_confirm.html.erb

@ -4,7 +4,7 @@
<% end %>
<h1 class="govuk-heading-l">
<span class="govuk-caption-l"><%= @location.postcode %></span>
This change will affect <%= @location.lettings_logs.count %> logs
This change will affect <%= @affected_logs.count %> logs
</h1>
<%= govuk_warning_text text: I18n.t("warnings.location.deactivate.review_logs") %>
<%= f.hidden_field :confirm, value: true %>

135
app/views/locations/index.html.erb

@ -11,57 +11,104 @@
<%= render partial: "organisations/headings", locals: { main: @scheme.service_name, sub: nil } %>
<%= render SubNavigationComponent.new(items: scheme_items(request.path, @scheme.id, "Locations")) %>
<% if FeatureToggle.location_toggle_enabled? %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<% end %>
<%= render SubNavigationComponent.new(items: scheme_items(request.path, @scheme.id, "Locations")) %>
<h2 class="govuk-visually-hidden">Locations</h2>
<h2 class="govuk-visually-hidden">Locations</h2>
<%= render SearchComponent.new(current_user:, search_label: "Search by location name or postcode", value: @searched) %>
<%= render SearchComponent.new(current_user:, search_label: "Search by location name or postcode", value: @searched) %>
<%= govuk_section_break(visible: true, size: "m") %>
<%= govuk_section_break(visible: true, size: "m") %>
<% if FeatureToggle.location_toggle_enabled? %>
</div>
</div>
<% end %>
<%= govuk_table do |table| %>
<%= table.caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %>
<%= render(SearchResultCaptionComponent.new(searched: @searched, count: @pagy.count, item_label:, total_count: @total_count, item: "locations", path: request.path)) %>
<% end %>
<%= table.head do |head| %>
<%= head.row do |row| %>
<% row.cell(header: true, text: "Code", html_attributes: {
scope: "col",
}) %>
<% row.cell(header: true, text: "Postcode", html_attributes: {
scope: "col",
}) %>
<% row.cell(header: true, text: "Units", html_attributes: {
scope: "col",
}) %>
<% row.cell(header: true, text: "Common unit type", html_attributes: {
scope: "col",
}) %>
<% row.cell(header: true, text: "Mobility type", html_attributes: {
scope: "col",
}) %>
<% row.cell(header: true, text: "Local authority", html_attributes: {
scope: "col",
}) %>
<% row.cell(header: true, text: "Available from", html_attributes: {
scope: "col",
}) %>
<% end %>
<% end %>
<% @locations.each do |location| %>
<%= table.body do |body| %>
<%= body.row do |row| %>
<% row.cell(text: location.id) %>
<% row.cell(text: simple_format(location_cell_postcode(location, "/schemes/#{@scheme.id}/locations/#{location.id}"), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %>
<% row.cell(text: location.units) %>
<% row.cell(text: simple_format("<span>#{location.type_of_unit}</span>")) %>
<% row.cell(text: location.mobility_type) %>
<% row.cell(text: simple_format(location_cell_location_admin_district(location, "/schemes/#{@scheme.id}/locations/#{location.id}/edit-local-authority"), wrapper_tag: "div")) %>
<% row.cell(text: location.startdate&.to_formatted_s(:govuk_date)) %>
<% if FeatureToggle.location_toggle_enabled? %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<%= govuk_table do |table| %>
<%= table.caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %>
<%= render(SearchResultCaptionComponent.new(searched: @searched, count: @pagy.count, item_label:, total_count: @total_count, item: "locations", path: request.path)) %>
<% end %>
<%= table.head do |head| %>
<%= head.row do |row| %>
<% row.cell(header: true, text: "Postcode", html_attributes: {
scope: "col",
}) %>
<% row.cell(header: true, text: "Location code", html_attributes: {
scope: "col",
}) %>
<% row.cell(header: true, text: "Status", html_attributes: {
scope: "col",
}) %>
<% end %>
<% end %>
<% @locations.each do |location| %>
<%= table.body do |body| %>
<%= body.row do |row| %>
<% row.cell(text: simple_format(location_cell_postcode(location, scheme_location_path(@scheme, location)), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %>
<% row.cell(text: location.id) %>
<% row.cell(text: status_tag(location.status)) %>
<% end %>
<% end %>
<% end %>
<% end %>
<%= govuk_button_link_to "Add a location", new_scheme_location_path(@scheme), secondary: true %>
</div>
</div>
<% else %>
<%= govuk_table do |table| %>
<%= table.caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %>
<%= render(SearchResultCaptionComponent.new(searched: @searched, count: @pagy.count, item_label:, total_count: @total_count, item: "locations", path: request.path)) %>
<% end %>
<%= table.head do |head| %>
<%= head.row do |row| %>
<% row.cell(header: true, text: "Code", html_attributes: {
scope: "col",
}) %>
<% row.cell(header: true, text: "Postcode", html_attributes: {
scope: "col",
}) %>
<% row.cell(header: true, text: "Units", html_attributes: {
scope: "col",
}) %>
<% row.cell(header: true, text: "Common unit type", html_attributes: {
scope: "col",
}) %>
<% row.cell(header: true, text: "Mobility type", html_attributes: {
scope: "col",
}) %>
<% row.cell(header: true, text: "Local authority", html_attributes: {
scope: "col",
}) %>
<% row.cell(header: true, text: "Available from", html_attributes: {
scope: "col",
}) %>
<% end %>
<% end %>
<% @locations.each do |location| %>
<%= table.body do |body| %>
<%= body.row do |row| %>
<% row.cell(text: location.id) %>
<% row.cell(text: simple_format(location_cell_postcode(location, scheme_location_path(@scheme, location)), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %>
<% row.cell(text: location.units) %>
<% row.cell do %>
<span><%= simple_format(location.type_of_unit) %></span>
<% end %>
<% row.cell(text: location.mobility_type) %>
<% row.cell(text: simple_format(location_cell_location_admin_district(location, scheme_location_edit_local_authority_path(@scheme, location)), wrapper_tag: "div")) %>
<% row.cell(text: location.startdate&.to_formatted_s(:govuk_date)) %>
<% end %>
<% end %>
<% end %>
<% end %>
<%= govuk_button_link_to "Add a location", new_scheme_location_path(@scheme), secondary: true %>
<% end %>
<%= govuk_button_link_to "Add a location", new_scheme_location_path(scheme_id: @scheme.id), secondary: true %>
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "locations" } %>

12
app/views/logs/index.html.erb

@ -10,14 +10,18 @@
<% end %>
<div class="app-filter-layout" data-controller="filter-layout">
<div class="govuk-button-group app-filter-toggle">
<div class="govuk-button-group app-filter-toggle govuk-!-margin-bottom-6">
<% if current_page?(controller: 'lettings_logs', action: 'index') %>
<%= govuk_button_to "Create a new lettings log", lettings_logs_path %>
<%= govuk_button_to "Create a new lettings log", lettings_logs_path, class: "govuk-!-margin-right-6" %>
<% end %>
<% if FeatureToggle.sales_log_enabled? && current_page?(controller: 'sales_logs', action: 'index') %>
<%= govuk_button_to "Create a new sales log", sales_logs_path %>
<%= govuk_button_to "Create a new sales log", sales_logs_path, class: "govuk-!-margin-right-6" %>
<% end %>
<% if FeatureToggle.bulk_upload_logs? %>
<%= govuk_button_link_to "Upload #{log_type_for_controller(controller)} logs in bulk", bulk_upload_path_for_controller(controller, id: "start"), secondary: true %>
<% end %>
<%#= govuk_link_to "Upload logs", bulk_upload_lettings_logs_path %>
</div>
<%= render partial: "log_filters" %>

22
app/views/schemes/_scheme_list.html.erb

@ -5,31 +5,31 @@
<% end %>
<%= table.head do |head| %>
<%= head.row do |row| %>
<% row.cell(header: true, text: "Code", html_attributes: {
scope: "col",
}) %>
<% row.cell(header: true, text: "Scheme", html_attributes: {
scope: "col",
}) %>
<% row.cell(header: true, text: "Locations", html_attributes: {
scope: "col",
}) %>
<% row.cell(header: true, text: "Support provided by", html_attributes: {
<% row.cell(header: true, text: "Code", html_attributes: {
scope: "col",
}) %>
<% row.cell(header: true, text: "Created", html_attributes: {
<% row.cell(header: true, text: "Locations", html_attributes: {
scope: "col",
}) %>
<% if FeatureToggle.scheme_toggle_enabled? %>
<% row.cell(header: true, text: "Status", html_attributes: {
scope: "col",
}) %>
<% end %>
<% end %>
<% end %>
<% @schemes.each do |scheme| %>
<%= table.body do |body| %>
<%= body.row do |row| %>
<% row.cell(text: scheme.id_to_display) %>
<% row.cell(text: simple_format(scheme_cell(scheme), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %>
<% row.cell(text: scheme.id_to_display) %>
<% row.cell(text: scheme.locations&.count) %>
<% row.cell(text: scheme.managing_organisation&.name) %>
<% row.cell(text: scheme.confirmed? ? scheme.created_at.to_formatted_s(:govuk_date) : govuk_tag(colour: "grey", text: "Incomplete")) %>
<% if FeatureToggle.scheme_toggle_enabled? %>
<% row.cell(text: status_tag(scheme.status)) %>
<% end %>
<% end %>
<% end %>
<% end %>

6
app/views/schemes/deactivate_confirm.html.erb

@ -1,14 +1,12 @@
<% title = "Deactivate #{@scheme.service_name}" %>
<% content_for :title, title %>
<%= form_with model: @scheme_deactivation_period, url: scheme_deactivate_path(@scheme), method: "patch", local: true do |f| %>
<% content_for :before_content do %>
<%= govuk_back_link(href: :back) %>
<% end %>
<h1 class="govuk-heading-l">
<span class="govuk-caption-l"><%= @scheme.service_name %></span>
This change will affect <%= @scheme.lettings_logs.count %> logs
This change will affect <%= @affected_logs.count %> logs
</h1>
<%= govuk_warning_text text: I18n.t("warnings.scheme.deactivation.review_logs") %>
<%= govuk_warning_text text: I18n.t("warnings.scheme.deactivate.review_logs") %>
<%= f.hidden_field :confirm, value: true %>
<%= f.hidden_field :deactivation_date, value: @deactivation_date %>
<%= f.hidden_field :deactivation_date_type, value: @deactivation_date_type %>

34
app/views/schemes/show.html.erb

@ -10,25 +10,33 @@
<%= render partial: "organisations/headings", locals: { main: @scheme.service_name, sub: nil } %>
<%= render SubNavigationComponent.new(items: scheme_items(request.path, @scheme.id, "Locations")) %>
<% if FeatureToggle.location_toggle_enabled? %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<% end %>
<%= render SubNavigationComponent.new(items: scheme_items(request.path, @scheme.id, "Locations")) %>
<h2 class="govuk-visually-hidden">Scheme</h2>
<h2 class="govuk-visually-hidden">Scheme</h2>
<%= govuk_summary_list do |summary_list| %>
<% display_scheme_attributes(@scheme).each do |attr| %>
<% next if current_user.data_coordinator? && attr[:name] == ("Housing stock owned by") %>
<%= summary_list.row do |row| %>
<% row.key { attr[:name].eql?("Registered under Care Standards Act 2000") ? "Registered under Care Standards Act 2000" : attr[:name].to_s.humanize } %>
<% row.value { attr[:name].eql?("Status") ? status_tag(attr[:value]) : details_html(attr) } %>
<% row.action(text: "Change", href: scheme_edit_name_path(scheme_id: @scheme.id)) if attr[:edit] %>
<% end %>
<% end %>
<%= govuk_summary_list do |summary_list| %>
<% display_scheme_attributes(@scheme, current_user).each do |attr| %>
<%= summary_list.row do |row| %>
<% row.key { attr[:name] } %>
<% row.value { details_html(attr) } %>
<% row.action(text: "Change", href: scheme_edit_name_path(scheme_id: @scheme.id)) if attr[:edit] %>
<% end %>
<% end %>
<% end %>
<% if FeatureToggle.location_toggle_enabled? %>
</div>
</div>
<% end %>
<% if FeatureToggle.scheme_toggle_enabled? %>
<% if @scheme.active? %>
<% if @scheme.active? || @scheme.reactivating_soon? %>
<%= govuk_button_link_to "Deactivate this scheme", scheme_new_deactivation_path(@scheme), warning: true %>
<% else %>
<%= govuk_button_link_to "Reactivate this scheme", scheme_reactivate_path(@scheme) %>
<%= govuk_button_link_to "Reactivate this scheme", scheme_new_reactivation_path(@scheme) %>
<% end %>
<% end %>

18
app/views/schemes/toggle_active.html.erb

@ -1,29 +1,31 @@
<% title = "#{action.humanize} #{@scheme.service_name}" %>
<% content_for :title, title %>
<% content_for :before_content do %>
<%= govuk_back_link(
text: "Back",
href: scheme_details_path(@scheme),
) %>
<% end %>
<%= form_with model: @scheme_deactivation_period, url: scheme_new_deactivation_path(@scheme), method: "patch", local: true do |f| %>
<%= form_with model: @scheme_deactivation_period, url: toggle_scheme_form_path(action, @scheme), method: "patch", local: true do |f| %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<% collection_start_date = FormHandler.instance.current_collection_start_date %>
<%= f.govuk_error_summary %>
<%= f.govuk_radio_buttons_fieldset :deactivation_date_type,
legend: { text: I18n.t("questions.scheme.deactivation.apply_from") },
<%= f.govuk_radio_buttons_fieldset date_type_question(action),
legend: { text: I18n.t("questions.scheme.toggle_active.apply_from") },
caption: { text: title },
hint: { text: I18n.t("hints.scheme.deactivation", date: collection_start_date.to_formatted_s(:govuk_date)) } do %>
<%= govuk_warning_text text: I18n.t("warnings.scheme.deactivation.existing_logs") %>
<%= f.govuk_radio_button :deactivation_date_type,
hint: { text: I18n.t("hints.scheme.toggle_active", date: collection_start_date.to_formatted_s(:govuk_date)) } do %>
<%= govuk_warning_text text: I18n.t("warnings.scheme.#{action}.existing_logs") %>
<%= f.govuk_radio_button date_type_question(action),
"default",
label: { text: "From the start of the current collection period (#{collection_start_date.to_formatted_s(:govuk_date)})" } %>
<%= f.govuk_radio_button :deactivation_date_type,
<%= f.govuk_radio_button date_type_question(action),
"other",
label: { text: "For tenancies starting after a certain date" },
**basic_conditional_html_attributes({ "deactivation_date" => %w[other] }, "scheme") do %>
<%= f.govuk_date_field :deactivation_date,
<%= f.govuk_date_field date_question(action),
legend: { text: "Date", size: "m" },
hint: { text: "For example, 27 3 2022" },
width: 20 %>

0
config/environments/review_app.rb → config/environments/review.rb

6
config/forms/2022_2023.json

@ -5929,7 +5929,7 @@
"description": "",
"questions": {
"housingneeds_type": {
"header": "What type of access need do they have?",
"header": "What type of access needs do they have?",
"hint_text": "",
"type": "radio",
"check_answer_label": "Disabled access needs",
@ -5947,7 +5947,7 @@
"value": true
},
"3": {
"value": "None of the above"
"value": "None of the listed options"
}
}
},
@ -7470,7 +7470,7 @@
"household_charge": {
"check_answer_label": "Does the household pay rent or charges?",
"header": "Does the household pay rent or other charges for the accommodation?",
"hint_text": "",
"hint_text": "If rent is charged on the property then answer Yes to this question, even if the tenants do not pay it themselves.",
"type": "radio",
"answer_options": {
"0": {

4
config/initializers/feature_toggle.rb

@ -22,4 +22,8 @@ class FeatureToggle
def self.managing_for_other_user_enabled?
!Rails.env.production?
end
def self.bulk_upload_logs?
!Rails.env.production?
end
end

42
config/locales/en.yml

@ -38,6 +38,18 @@ en:
create_password: "Create a password to finish setting up your account"
reset_password: "Reset your password"
activemodel:
errors:
models:
forms/bulk_upload_lettings/year:
attributes:
year:
blank: You must select a collection period to upload for
forms/bulk_upload_sales/year:
attributes:
year:
blank: You must select a collection period to upload for
activerecord:
errors:
models:
@ -120,6 +132,14 @@ en:
before_scheme_end_date: "The tenancy start date must be before the end date for this supported housing scheme"
after_void_date: "Enter a tenancy start date that is after the void date"
after_major_repair_date: "Enter a tenancy start date that is after the major repair date"
location:
deactivated: "The location %{postcode} was deactivated on %{date} and was not available on the day you entered."
reactivating_soon: "The location %{postcode} was deactivated on %{deactivation_date} and is not available on the date you entered. It reactivates on %{date}"
activating_soon: "The location %{postcode} is not available until %{date}. Enter a tenancy start date after %{date}"
scheme:
deactivated: "%{name} was deactivated on %{date} and was not available on the day you entered"
reactivating_soon: "%{name} was deactivated on %{deactivation_date} and is not available on the date you entered. It reactivates on %{date}"
activating_soon: "%{name} is not available until %{date}. Enter a tenancy start date after %{date}"
property:
mrcdate:
@ -313,10 +333,16 @@ en:
missing: "You must show the DLUHC privacy notice to the tenant before you can submit this log."
scheme:
deactivation_date:
toggle_date:
not_selected: "Select one of the options"
invalid: "Enter a valid day, month and year"
out_of_range: "The date must be on or after the %{date}"
reactivation:
before_deactivation: "This scheme was deactivated on %{date}. The reactivation date must be on or after deactivation date"
deactivation:
during_deactivated_period: "The scheme is already deactivated during this date, please enter a different date"
location:
toggle_date:
@ -324,8 +350,8 @@ en:
invalid: "Enter a valid day, month and year"
out_of_range: "The date must be on or after the %{date}"
reactivation:
before_deactivation: "This location was deactivated on %{date}\nThe reactivation date must be on or after deactivation date"
deactivation:
before_deactivation: "This location was deactivated on %{date}. The reactivation date must be on or after deactivation date"
deactivation:
during_deactivated_period: "The location is already deactivated during this date, please enter a different date"
soft_validations:
@ -381,7 +407,7 @@ en:
toggle_active:
apply_from: "When should this change apply?"
scheme:
deactivation:
toggle_active:
apply_from: "When should this change apply?"
descriptions:
location:
@ -397,19 +423,21 @@ en:
units: "A unit can be a bedroom in a shared house or flat, or a house with 4 bedrooms. Do not include bedrooms used for wardens, managers, volunteers or sleep-in staff."
toggle_active: "If the date is before %{date}, select ‘From the start of the current collection period’ because the previous period has now closed."
scheme:
deactivation: "If the date is before %{date}, select ‘From the start of the current collection period’ because the previous period has now closed."
toggle_active: "If the date is before %{date}, select ‘From the start of the current collection period’ because the previous period has now closed."
warnings:
location:
deactivate:
existing_logs: "It will not be possible to add logs with this location if their tenancy start date is on or after the date you enter. Any existing logs may be affected."
review_logs: "Your data providers will need to review these logs and answer a few questions again. We’ll email each log creator with a list of logs that need updating."
reactivate:
reactivate:
existing_logs: "You’ll be able to add logs with this location if their tenancy start date is on or after the date you enter."
scheme:
deactivation:
deactivate:
existing_logs: "It will not be possible to add logs with this scheme if their tenancy start date is on or after the date you enter. Any existing logs may be affected."
review_logs: "Your data providers will need to review these logs and answer a few questions again. We’ll email each log creator with a list of logs that need updating."
reactivate:
existing_logs: "You’ll be able to add logs with this scheme if their tenancy start date is on or after the date you enter."
test:
one_argument: "This is based on the tenant’s work situation: %{ecstat1}"

16
config/routes.rb

@ -52,8 +52,10 @@ Rails.application.routes.draw do
get "new-deactivation", to: "schemes#new_deactivation"
get "deactivate-confirm", to: "schemes#deactivate_confirm"
get "reactivate", to: "schemes#reactivate"
get "new-reactivation", to: "schemes#new_reactivation"
patch "new-deactivation", to: "schemes#new_deactivation"
patch "deactivate", to: "schemes#deactivate"
patch "reactivate", to: "schemes#reactivate"
resources :locations do
get "edit-name", to: "locations#edit_name"
@ -109,6 +111,12 @@ Rails.application.routes.draw do
get "csv-download", to: "lettings_logs#download_csv"
post "email-csv", to: "lettings_logs#email_csv"
get "csv-confirmation", to: "lettings_logs#csv_confirmation"
resources :bulk_upload_lettings_logs, path: "bulk-upload-logs" do
collection do
get :start
end
end
end
member do
@ -128,6 +136,14 @@ Rails.application.routes.draw do
end
resources :sales_logs, path: "/sales-logs" do
collection do
resources :bulk_upload_sales_logs, path: "bulk-upload-logs" do
collection do
get :start
end
end
end
FormHandler.instance.sales_forms.each do |_key, form|
form.pages.map do |page|
get page.id.to_s.dasherize, to: "form#show_page"

5
db/migrate/20221122122311_delete_relationship_type.rb

@ -0,0 +1,5 @@
class DeleteRelationshipType < ActiveRecord::Migration[7.0]
def change
remove_column :organisation_relationships, :relationship_type, :integer, null: false
end
end

10
db/migrate/20221122130928_add_org_relation_indexes.rb

@ -0,0 +1,10 @@
class AddOrgRelationIndexes < ActiveRecord::Migration[7.0]
def change
add_index :organisation_relationships, :child_organisation_id
add_index :organisation_relationships, :parent_organisation_id
add_index :organisation_relationships, %i[parent_organisation_id child_organisation_id], unique: true, name: "index_org_rel_parent_child_uniq"
add_foreign_key :organisation_relationships, :organisations, column: :parent_organisation_id
add_foreign_key :organisation_relationships, :organisations, column: :child_organisation_id
end
end

7
db/migrate/20221124102329_add_mortgage1_to_sales.rb

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

6
db/migrate/20221125142847_add_buyer2_to_sales.rb

@ -0,0 +1,6 @@
class AddBuyer2ToSales < ActiveRecord::Migration[7.0]
change_table :sales_logs, bulk: true do |t|
t.column :income2, :int
t.column :income2nk, :int
end
end

17
db/schema.rb

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2022_11_17_103855) do
ActiveRecord::Schema[7.0].define(version: 2022_11_25_142847) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -286,7 +286,9 @@ ActiveRecord::Schema[7.0].define(version: 2022_11_17_103855) do
t.integer "parent_organisation_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "relationship_type", null: false
t.index ["child_organisation_id"], name: "index_organisation_relationships_on_child_organisation_id"
t.index ["parent_organisation_id", "child_organisation_id"], name: "index_org_rel_parent_child_uniq", unique: true
t.index ["parent_organisation_id"], name: "index_organisation_relationships_on_parent_organisation_id"
end
create_table "organisation_rent_periods", force: :cascade do |t|
@ -372,15 +374,18 @@ ActiveRecord::Schema[7.0].define(version: 2022_11_17_103855) do
t.integer "la_known"
t.integer "income1"
t.integer "income1nk"
t.integer "details_known_2"
t.integer "details_known_3"
t.integer "details_known_4"
t.integer "age4"
t.integer "age4_known"
t.integer "age5"
t.integer "age5_known"
t.integer "age6"
t.integer "age6_known"
t.integer "details_known_2"
t.integer "details_known_3"
t.integer "details_known_4"
t.integer "inc1mort"
t.integer "income2"
t.integer "income2nk"
t.index ["created_by_id"], name: "index_sales_logs_on_created_by_id"
t.index ["managing_organisation_id"], name: "index_sales_logs_on_managing_organisation_id"
t.index ["owning_organisation_id"], name: "index_sales_logs_on_owning_organisation_id"
@ -477,6 +482,8 @@ ActiveRecord::Schema[7.0].define(version: 2022_11_17_103855) do
add_foreign_key "lettings_logs", "organisations", column: "owning_organisation_id", on_delete: :cascade
add_foreign_key "lettings_logs", "schemes"
add_foreign_key "locations", "schemes"
add_foreign_key "organisation_relationships", "organisations", column: "child_organisation_id"
add_foreign_key "organisation_relationships", "organisations", column: "parent_organisation_id"
add_foreign_key "sales_logs", "organisations", column: "owning_organisation_id", on_delete: :cascade
add_foreign_key "schemes", "organisations", column: "managing_organisation_id"
add_foreign_key "schemes", "organisations", column: "owning_organisation_id", on_delete: :cascade

12
db/seeds.rb

@ -67,25 +67,21 @@ unless Rails.env.test?
end
end
OrganisationRelationship.create!(
OrganisationRelationship.find_or_create_by!(
child_organisation: org,
parent_organisation: housing_provider1,
relationship_type: OrganisationRelationship::OWNING,
)
OrganisationRelationship.create!(
OrganisationRelationship.find_or_create_by!(
child_organisation: org,
parent_organisation: housing_provider2,
relationship_type: OrganisationRelationship::OWNING,
)
OrganisationRelationship.create!(
OrganisationRelationship.find_or_create_by!(
child_organisation: managing_agent1,
parent_organisation: org,
relationship_type: OrganisationRelationship::MANAGING,
)
OrganisationRelationship.create!(
OrganisationRelationship.find_or_create_by!(
child_organisation: managing_agent2,
parent_organisation: org,
relationship_type: OrganisationRelationship::MANAGING,
)
if (Rails.env.development? || Rails.env.review?) && User.count.zero?

51
docs/infrastructure.md

@ -122,6 +122,57 @@ After a sucessful deployment a comment will be added to the pull request with th
Once a pull request has been closed the review app infrastructure will be tore down to save on any costs. Should you wish to re-open a closed pull request the review app will be spun up again.
### How to fix review app deployment failures
One reason a review app deployment might fail is that it is attempting to run migrations which conflict with data in the database. For example you might have introduced a unique constraint, but the database associated with the review app has duplicate data in it that would violate this constraint, and so the migration cannot be run. There are two main ways to remedy this:
**Method 1 - Edit database via console**
1. Log in to Cloud Foundry
```bash
cf login -a api.london.cloud.service.gov.uk -u <your_username>
```
* Your username should be the email address you signed up to GOVUK PaaS with.
* Choose the dev environment whilst logging in.
2. If you were already logged in then Cloud Foundry, then instead just target the dev environment
```bash
cf target -o dluhc-core -s dev
```
3. Find the name of your app
```bash
cf apps
```
* The app name will be in this format: `dluhc-core-review-<pull-request-number>`.
4. Open a console for your app
```bash
cf ssh <app-name-here> -t -c "/tmp/lifecycle/launcher /home/vcap/app 'rails console' ''"
```
5. Edit the database as appropriate, e.g. delete dodgy data and recreate correctly
**Method 2 - Nuke and restart**
1. Find the name of your app
```bash
cf apps
```
* The app name will be in this format: `dluhc-core-review-<pull-request-number>`.
2. Delete the app
```bash
cf delete <app-name-here>
```
3. Find the name of the matching Postgres service
```bash
cf services
```
* The service name will be in this format: `dluhc-core-review-<pull-request-number>-postgres`.
4. Delete the service
```bash
cf delete-service <service-name-here>
```
* Use `cf services` or `cf service <service-name-here>` to check the operation status.
* There's no need to delete the Redis service.
5. Re-run the whole review app pipeline in GitHub
* If it fails it's likely that the deletion from the previous step hadn't completed yet. So just wait a few minutes and re-run the pipeline again.
## Setting up Infrastructure for a new environment
### Staging

BIN
public/files/bulk-upload-lettings-template-v1.xlsx

Binary file not shown.

BIN
public/files/bulk-upload-sales-template-v1.xlsx

Binary file not shown.

2
spec/components/check_answers_summary_list_card_component_spec.rb

@ -3,7 +3,7 @@ require "rails_helper"
RSpec.describe CheckAnswersSummaryListCardComponent, type: :component do
context "when given a set of questions" do
let(:user) { FactoryBot.build(:user) }
let(:log) { FactoryBot.build(:lettings_log, :completed, age2: 99) }
let(:log) { FactoryBot.build(:lettings_log, :completed, age2: 99, startdate: Time.zone.local(2021, 5, 1)) }
let(:subsection_id) { "household_characteristics" }
let(:subsection) { log.form.get_subsection(subsection_id) }
let(:questions) { subsection.applicable_questions(log) }

4
spec/factories/lettings_log.rb

@ -60,7 +60,7 @@ FactoryBot.define do
illness { 1 }
preg_occ { 2 }
startertenancy { 1 }
tenancylength { 5 }
tenancylength { nil }
tenancy { 1 }
ppostcode_full { Faker::Address.postcode }
rsnvac { 6 }
@ -134,7 +134,7 @@ FactoryBot.define do
property_relet { 0 }
mrcdate { Time.zone.local(2020, 5, 5, 10, 36, 49) }
incref { 0 }
startdate { Time.utc(2022, 2, 2, 10, 36, 49) }
startdate { Time.zone.today }
armedforces { 1 }
builtype { 1 }
unitletas { 2 }

2
spec/factories/location.rb

@ -7,7 +7,7 @@ FactoryBot.define do
mobility_type { %w[A M N W X].sample }
location_code { "E09000033" }
location_admin_district { "Westminster" }
startdate { Faker::Date.between(from: 6.months.ago, to: Time.zone.today) }
startdate { nil }
confirmed { true }
scheme
trait :export do

8
spec/factories/organisation_relationship.rb

@ -2,13 +2,5 @@ FactoryBot.define do
factory :organisation_relationship do
child_organisation { FactoryBot.create(:organisation) }
parent_organisation { FactoryBot.create(:organisation) }
trait :owning do
relationship_type { OrganisationRelationship::OWNING }
end
trait :managing do
relationship_type { OrganisationRelationship::MANAGING }
end
end
end

3
spec/factories/sales_log.rb

@ -52,6 +52,9 @@ FactoryBot.define do
age6 { 40 }
income1nk { 0 }
income1 { 10_000 }
inc1mort { 1 }
income2nk { 0 }
income2 { 10_000 }
la_known { "1" }
la { "E09000003" }
end

2
spec/factories/scheme.rb

@ -12,7 +12,7 @@ FactoryBot.define do
owning_organisation { FactoryBot.create(:organisation) }
managing_organisation { FactoryBot.create(:organisation) }
confirmed { true }
created_at { Time.zone.now }
created_at { Time.zone.local(2021, 4, 1) }
trait :export do
sensitive { 1 }
registered_under_care_act { 1 }

1
spec/factories/scheme_deactivation_period.rb

@ -1,5 +1,6 @@
FactoryBot.define do
factory :scheme_deactivation_period do
deactivation_date { Time.zone.local(2022, 4, 1) }
reactivation_date { nil }
end
end

51
spec/features/bulk_upload_lettings_logs_spec.rb

@ -0,0 +1,51 @@
require "rails_helper"
RSpec.describe "Bulk upload lettings log" do
let(:user) { create(:user) }
before do
sign_in user
end
context "when during crossover period" do
it "shows journey with year option" do
Timecop.freeze(2023, 6, 1) do
visit("/lettings-logs")
expect(page).to have_link("Upload lettings logs in bulk")
click_link("Upload lettings logs in bulk")
expect(page).to have_content("Which year")
click_button("Continue")
expect(page).to have_content("You must select a collection period to upload for")
choose("2022/2023")
click_button("Continue")
click_link("Back")
expect(page.find_field("form-year-2022-field")).to be_checked
click_button("Continue")
expect(page).to have_content("Upload lettings logs in bulk (2022/23)")
click_button("Continue")
expect(page).to have_content("Upload your file")
end
end
end
context "when not it crossover period" do
it "shows journey with year option" do
Timecop.freeze(2023, 10, 1) do
visit("/lettings-logs")
expect(page).to have_link("Upload lettings logs in bulk")
click_link("Upload lettings logs in bulk")
expect(page).to have_content("Upload lettings logs in bulk (2022/23)")
click_button("Continue")
expect(page).to have_content("Upload your file")
end
end
end
end

51
spec/features/bulk_upload_sales_logs_spec.rb

@ -0,0 +1,51 @@
require "rails_helper"
RSpec.describe "Bulk upload sales log" do
let(:user) { create(:user) }
before do
sign_in user
end
context "when during crossover period" do
it "shows journey with year option" do
Timecop.freeze(2023, 6, 1) do
visit("/sales-logs")
expect(page).to have_link("Upload sales logs in bulk")
click_link("Upload sales logs in bulk")
expect(page).to have_content("Which year")
click_button("Continue")
expect(page).to have_content("You must select a collection period to upload for")
choose("2022/2023")
click_button("Continue")
click_link("Back")
expect(page.find_field("form-year-2022-field")).to be_checked
click_button("Continue")
expect(page).to have_content("Upload sales logs in bulk (2022/23)")
click_button("Continue")
expect(page).to have_content("Upload your file")
end
end
end
context "when not it crossover period" do
it "shows journey with year option" do
Timecop.freeze(2023, 10, 1) do
visit("/sales-logs")
expect(page).to have_link("Upload sales logs in bulk")
click_link("Upload sales logs in bulk")
expect(page).to have_content("Upload sales logs in bulk (2022/23)")
click_button("Continue")
expect(page).to have_content("Upload your file")
end
end
end
end

3
spec/features/form/check_answers_page_spec.rb

@ -7,7 +7,7 @@ RSpec.describe "Form Check Answers Page" do
let(:subsection) { "household-characteristics" }
let(:conditional_subsection) { "conditional-question" }
let(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let(:location) { FactoryBot.create(:location, scheme:, mobility_type: "N") }
let(:location) { FactoryBot.create(:location, scheme:, mobility_type: "N", startdate: Time.zone.local(2021, 4, 1)) }
let(:lettings_log) do
FactoryBot.create(
@ -36,6 +36,7 @@ RSpec.describe "Form Check Answers Page" do
:completed,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
startdate: Time.zone.local(2021, 5, 1),
)
end
let(:id) { lettings_log.id }

1
spec/features/form/validations_spec.rb

@ -28,6 +28,7 @@ RSpec.describe "validations" do
managing_organisation: user.organisation,
status: 1,
declaration: nil,
startdate: Time.zone.local(2021, 5, 1),
)
end
let(:id) { lettings_log.id }

18
spec/features/schemes_spec.rb

@ -195,11 +195,27 @@ RSpec.describe "Schemes scheme Features" do
expect(page).to have_link("Locations")
end
context "when I click locations link" do
context "when I click locations link and the new locations layout feature toggle is enabled" do
before do
click_link("Locations")
end
it "shows details of those locations" do
locations.each do |location|
expect(page).to have_content(location.id)
expect(page).to have_content(location.postcode)
expect(page).to have_content(location.name)
expect(page).to have_content("Active")
end
end
end
context "when I click locations link and the new locations layout feature toggle is disabled" do
before do
allow(FeatureToggle).to receive(:location_toggle_enabled?).and_return(false)
click_link("Locations")
end
it "shows details of those locations" do
locations.each do |location|
expect(page).to have_content(location.id)

2
spec/fixtures/files/lettings_logs_download.csv vendored

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

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

2
spec/fixtures/files/lettings_logs_download_non_support.csv vendored

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

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

56
spec/helpers/locations_helper_spec.rb

@ -59,8 +59,8 @@ RSpec.describe LocationsHelper do
end
it "returns one active period without to date" do
expect(active_periods(location).count).to eq(1)
expect(active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: nil)
expect(location_active_periods(location).count).to eq(1)
expect(location_active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: nil)
end
it "ignores reactivations that were deactivated on the same day" do
@ -68,8 +68,8 @@ RSpec.describe LocationsHelper do
FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 4), location:)
location.reload
expect(active_periods(location).count).to eq(1)
expect(active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5))
expect(location_active_periods(location).count).to eq(1)
expect(location_active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5))
end
it "returns sequential non reactivated active periods" do
@ -77,19 +77,19 @@ RSpec.describe LocationsHelper do
FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 7, 6), location:)
location.reload
expect(active_periods(location).count).to eq(2)
expect(active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5))
expect(active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 6, 4), to: Time.zone.local(2022, 7, 6))
expect(location_active_periods(location).count).to eq(2)
expect(location_active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5))
expect(location_active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 6, 4), to: Time.zone.local(2022, 7, 6))
end
it "returns sequential reactivated active periods" do
FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 5, 5), reactivation_date: Time.zone.local(2022, 6, 4), location:)
FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 7, 6), reactivation_date: Time.zone.local(2022, 8, 5), location:)
location.reload
expect(active_periods(location).count).to eq(3)
expect(active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5))
expect(active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 6, 4), to: Time.zone.local(2022, 7, 6))
expect(active_periods(location).third).to have_attributes(from: Time.zone.local(2022, 8, 5), to: nil)
expect(location_active_periods(location).count).to eq(3)
expect(location_active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5))
expect(location_active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 6, 4), to: Time.zone.local(2022, 7, 6))
expect(location_active_periods(location).third).to have_attributes(from: Time.zone.local(2022, 8, 5), to: nil)
end
it "returns non sequential non reactivated active periods" do
@ -97,19 +97,19 @@ RSpec.describe LocationsHelper do
FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 5, 5), reactivation_date: nil, location:)
location.reload
expect(active_periods(location).count).to eq(2)
expect(active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5))
expect(active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 8, 5), to: nil)
expect(location_active_periods(location).count).to eq(2)
expect(location_active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5))
expect(location_active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 8, 5), to: nil)
end
it "returns non sequential reactivated active periods" do
FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 7, 6), reactivation_date: Time.zone.local(2022, 8, 5), location:)
FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 5, 5), reactivation_date: Time.zone.local(2022, 6, 4), location:)
location.reload
expect(active_periods(location).count).to eq(3)
expect(active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5))
expect(active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 6, 4), to: Time.zone.local(2022, 7, 6))
expect(active_periods(location).third).to have_attributes(from: Time.zone.local(2022, 8, 5), to: nil)
expect(location_active_periods(location).count).to eq(3)
expect(location_active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5))
expect(location_active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 6, 4), to: Time.zone.local(2022, 7, 6))
expect(location_active_periods(location).third).to have_attributes(from: Time.zone.local(2022, 8, 5), to: nil)
end
it "returns correct active periods when reactivation happends during a deactivated period" do
@ -117,9 +117,9 @@ RSpec.describe LocationsHelper do
FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 4, 6), reactivation_date: Time.zone.local(2022, 7, 7), location:)
location.reload
expect(active_periods(location).count).to eq(2)
expect(active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 4, 6))
expect(active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 11, 11), to: nil)
expect(location_active_periods(location).count).to eq(2)
expect(location_active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 4, 6))
expect(location_active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 11, 11), to: nil)
end
it "returns correct active periods when a full deactivation period happens during another deactivation period" do
@ -127,9 +127,9 @@ RSpec.describe LocationsHelper do
FactoryBot.create(:location_deactivation_period, deactivation_date: Time.zone.local(2022, 4, 6), reactivation_date: Time.zone.local(2022, 7, 7), location:)
location.reload
expect(active_periods(location).count).to eq(2)
expect(active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 4, 6))
expect(active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 7, 7), to: nil)
expect(location_active_periods(location).count).to eq(2)
expect(location_active_periods(location).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 4, 6))
expect(location_active_periods(location).second).to have_attributes(from: Time.zone.local(2022, 7, 7), to: nil)
end
end
@ -139,12 +139,12 @@ RSpec.describe LocationsHelper do
it "returns correct display attributes" do
attributes = [
{ name: "Postcode", value: location.postcode },
{ name: "Local authority", value: location.location_admin_district },
{ name: "Location name", value: location.name, edit: true },
{ name: "Local authority", value: location.location_admin_district },
{ name: "Total number of units at this location", value: location.units },
{ name: "Common type of unit", value: location.type_of_unit },
{ name: "Mobility type", value: location.mobility_type },
{ name: "Code", value: location.location_code },
{ name: "Location code", value: location.location_code },
{ name: "Availability", value: "Active from 1 April 2022" },
{ name: "Status", value: :active },
]
@ -154,11 +154,11 @@ RSpec.describe LocationsHelper do
context "when viewing availability" do
context "with no deactivations" do
it "displays created_at as availability date if startdate is not present" do
it "displays previous collection start date as availability date if created_at is earlier than collection start date" do
location.update!(startdate: nil)
availability_attribute = display_location_attributes(location).find { |x| x[:name] == "Availability" }[:value]
expect(availability_attribute).to eq("Active from #{location.created_at.to_formatted_s(:govuk_date)}")
expect(availability_attribute).to eq("Active from 1 April 2021")
end
it "displays current collection start date as availability date if created_at is later than collection start date" do

273
spec/helpers/schemes_helper_spec.rb

@ -1,49 +1,276 @@
require "rails_helper"
RSpec.describe SchemesHelper do
describe "Active periods" do
let(:scheme) { FactoryBot.create(:scheme, created_at: Time.zone.today) }
before do
Timecop.freeze(2022, 10, 10)
end
after do
Timecop.unfreeze
end
it "returns one active period without to date" do
expect(scheme_active_periods(scheme).count).to eq(1)
expect(scheme_active_periods(scheme).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: nil)
end
it "ignores reactivations that were deactivated on the same day" do
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 5, 5), reactivation_date: Time.zone.local(2022, 6, 4), scheme:)
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 4), scheme:)
scheme.reload
expect(scheme_active_periods(scheme).count).to eq(1)
expect(scheme_active_periods(scheme).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5))
end
it "returns sequential non reactivated active periods" do
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 5, 5), reactivation_date: Time.zone.local(2022, 6, 4), scheme:)
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 7, 6), scheme:)
scheme.reload
expect(scheme_active_periods(scheme).count).to eq(2)
expect(scheme_active_periods(scheme).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5))
expect(scheme_active_periods(scheme).second).to have_attributes(from: Time.zone.local(2022, 6, 4), to: Time.zone.local(2022, 7, 6))
end
it "returns sequential reactivated active periods" do
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 5, 5), reactivation_date: Time.zone.local(2022, 6, 4), scheme:)
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 7, 6), reactivation_date: Time.zone.local(2022, 8, 5), scheme:)
scheme.reload
expect(scheme_active_periods(scheme).count).to eq(3)
expect(scheme_active_periods(scheme).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5))
expect(scheme_active_periods(scheme).second).to have_attributes(from: Time.zone.local(2022, 6, 4), to: Time.zone.local(2022, 7, 6))
expect(scheme_active_periods(scheme).third).to have_attributes(from: Time.zone.local(2022, 8, 5), to: nil)
end
it "returns non sequential non reactivated active periods" do
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 7, 6), reactivation_date: Time.zone.local(2022, 8, 5), scheme:)
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 5, 5), reactivation_date: nil, scheme:)
scheme.reload
expect(scheme_active_periods(scheme).count).to eq(2)
expect(scheme_active_periods(scheme).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5))
expect(scheme_active_periods(scheme).second).to have_attributes(from: Time.zone.local(2022, 8, 5), to: nil)
end
it "returns non sequential reactivated active periods" do
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 7, 6), reactivation_date: Time.zone.local(2022, 8, 5), scheme:)
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 5, 5), reactivation_date: Time.zone.local(2022, 6, 4), scheme:)
scheme.reload
expect(scheme_active_periods(scheme).count).to eq(3)
expect(scheme_active_periods(scheme).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 5, 5))
expect(scheme_active_periods(scheme).second).to have_attributes(from: Time.zone.local(2022, 6, 4), to: Time.zone.local(2022, 7, 6))
expect(scheme_active_periods(scheme).third).to have_attributes(from: Time.zone.local(2022, 8, 5), to: nil)
end
it "returns correct active periods when reactivation happends during a deactivated period" do
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 5, 5), reactivation_date: Time.zone.local(2022, 11, 11), scheme:)
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 4, 6), reactivation_date: Time.zone.local(2022, 7, 7), scheme:)
scheme.reload
expect(scheme_active_periods(scheme).count).to eq(2)
expect(scheme_active_periods(scheme).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 4, 6))
expect(scheme_active_periods(scheme).second).to have_attributes(from: Time.zone.local(2022, 11, 11), to: nil)
end
it "returns correct active periods when a full deactivation period happens during another deactivation period" do
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 5, 5), reactivation_date: Time.zone.local(2022, 6, 11), scheme:)
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 4, 6), reactivation_date: Time.zone.local(2022, 7, 7), scheme:)
scheme.reload
expect(scheme_active_periods(scheme).count).to eq(2)
expect(scheme_active_periods(scheme).first).to have_attributes(from: Time.zone.local(2022, 4, 1), to: Time.zone.local(2022, 4, 6))
expect(scheme_active_periods(scheme).second).to have_attributes(from: Time.zone.local(2022, 7, 7), to: nil)
end
end
include TagHelper
describe "display_scheme_attributes" do
let!(:scheme) { FactoryBot.create(:scheme, created_at: Time.zone.local(2022, 8, 8)) }
let(:owning_organisation) { FactoryBot.create(:organisation, name: "Acme LTD Owning") }
let(:managing_organisation) { FactoryBot.create(:organisation, name: "Acme LTD Managing") }
let!(:scheme) do
FactoryBot.create(:scheme,
service_name: "Test service_name",
sensitive: 0,
scheme_type: 7,
registered_under_care_act: 3,
owning_organisation:,
managing_organisation:,
arrangement_type: "V",
primary_client_group: "S",
has_other_client_group: 1,
secondary_client_group: "I",
support_type: 4,
intended_stay: "P",
created_at: Time.zone.local(2022, 4, 1))
end
let!(:scheme_where_managing_organisation_is_owning_organisation) { FactoryBot.create(:scheme, arrangement_type: "D") }
let(:support_user) { FactoryBot.create(:user, :support) }
let(:coordinator_user) { FactoryBot.create(:user, :data_coordinator) }
it "returns correct display attributes for a support user" do
attributes = [
{ name: "Scheme code", value: "S#{scheme.id}" },
{ name: "Name", value: "Test service_name", edit: true },
{ name: "Confidential information", value: "No", edit: true },
{ name: "Type of scheme", value: "Housing for older people" },
{ name: "Registered under Care Standards Act 2000", value: "Yes – registered care home providing personal care" },
{ name: "Housing stock owned by", value: "Acme LTD Owning", edit: true },
{ name: "Support services provided by", value: "A registered charity or voluntary organisation" },
{ name: "Organisation providing support", value: "Acme LTD Managing" },
{ name: "Primary client group", value: "Rough sleepers" },
{ name: "Has another client group", value: "Yes" },
{ name: "Secondary client group", value: "Refugees (permanent)" },
{ name: "Level of support given", value: "High level" },
{ name: "Intended length of stay", value: "Permanent" },
{ name: "Availability", value: "Active from 1 April 2022" },
{ name: "Status", value: status_tag(:active) },
]
expect(display_scheme_attributes(scheme, support_user)).to eq(attributes)
end
it "returns correct display attributes" do
it "returns correct display attributes for a coordinator user" do
attributes = [
{ name: "Scheme code", value: scheme.id_to_display },
{ name: "Name", value: scheme.service_name, edit: true },
{ name: "Confidential information", value: scheme.sensitive, edit: true },
{ name: "Type of scheme", value: scheme.scheme_type },
{ name: "Registered under Care Standards Act 2000", value: scheme.registered_under_care_act },
{ name: "Housing stock owned by", value: scheme.owning_organisation.name, edit: true },
{ name: "Support services provided by", value: scheme.arrangement_type },
{ name: "Primary client group", value: scheme.primary_client_group },
{ name: "Has another client group", value: scheme.has_other_client_group },
{ name: "Secondary client group", value: scheme.secondary_client_group },
{ name: "Level of support given", value: scheme.support_type },
{ name: "Intended length of stay", value: scheme.intended_stay },
{ name: "Availability", value: "Active from 8 August 2022" },
{ name: "Status", value: :active },
{ name: "Scheme code", value: "S#{scheme.id}" },
{ name: "Name", value: "Test service_name", edit: true },
{ name: "Confidential information", value: "No", edit: true },
{ name: "Type of scheme", value: "Housing for older people" },
{ name: "Registered under Care Standards Act 2000", value: "Yes – registered care home providing personal care" },
{ name: "Support services provided by", value: "A registered charity or voluntary organisation" },
{ name: "Organisation providing support", value: "Acme LTD Managing" },
{ name: "Primary client group", value: "Rough sleepers" },
{ name: "Has another client group", value: "Yes" },
{ name: "Secondary client group", value: "Refugees (permanent)" },
{ name: "Level of support given", value: "High level" },
{ name: "Intended length of stay", value: "Permanent" },
{ name: "Availability", value: "Active from 1 April 2022" },
{ name: "Status", value: status_tag(:active) },
]
expect(display_scheme_attributes(scheme)).to eq(attributes)
expect(display_scheme_attributes(scheme, coordinator_user)).to eq(attributes)
end
context "when the scheme toggle is disabled" do
it "doesn't show the scheme status" do
allow(FeatureToggle).to receive(:scheme_toggle_enabled?).and_return(false)
attributes = display_scheme_attributes(scheme, support_user).find { |x| x[:name] == "Status" }
expect(attributes).to be_nil
end
end
context "when the managing organisation is the owning organisation" do
it "doesn't show the organisation providing support" do
attributes = display_scheme_attributes(scheme_where_managing_organisation_is_owning_organisation, support_user).find { |x| x[:name] == "Organisation providing support" }
expect(attributes).to be_nil
end
end
context "when viewing availability" do
context "with are no deactivations" do
context "with no deactivations" do
it "displays created_at as availability date" do
availability_attribute = display_scheme_attributes(scheme).find { |x| x[:name] == "Availability" }[:value]
availability_attribute = display_scheme_attributes(scheme, support_user).find { |x| x[:name] == "Availability" }[:value]
expect(availability_attribute).to eq("Active from #{scheme.created_at.to_formatted_s(:govuk_date)}")
end
it "displays current collection start date as availability date if created_at is later than collection start date" do
scheme.update!(created_at: Time.zone.local(2022, 4, 16))
availability_attribute = display_scheme_attributes(scheme, support_user).find { |x| x[:name] == "Availability" }[:value]
expect(availability_attribute).to eq("Active from 1 April 2022")
end
end
context "with previous deactivations" do
context "and all reactivated deactivations" do
before do
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 8, 10), reactivation_date: Time.zone.local(2022, 9, 1), scheme:)
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 9, 15), reactivation_date: Time.zone.local(2022, 9, 28), scheme:)
scheme.reload
end
it "displays the timeline of availability" do
availability_attribute = display_scheme_attributes(scheme, support_user).find { |x| x[:name] == "Availability" }[:value]
expect(availability_attribute).to eq("Active from 1 April 2022 to 9 August 2022\nDeactivated on 10 August 2022\nActive from 1 September 2022 to 14 September 2022\nDeactivated on 15 September 2022\nActive from 28 September 2022")
end
end
context "and non reactivated deactivation" do
before do
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 8, 10), reactivation_date: Time.zone.local(2022, 9, 1), scheme:)
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 9, 15), reactivation_date: nil, scheme:)
scheme.reload
end
it "displays the timeline of availability" do
availability_attribute = display_scheme_attributes(scheme, support_user).find { |x| x[:name] == "Availability" }[:value]
expect(availability_attribute).to eq("Active from 1 April 2022 to 9 August 2022\nDeactivated on 10 August 2022\nActive from 1 September 2022 to 14 September 2022\nDeactivated on 15 September 2022")
end
end
end
context "with out of order deactivations" do
context "and all reactivated deactivations" do
before do
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 9, 24), reactivation_date: Time.zone.local(2022, 9, 28), scheme:)
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 15), reactivation_date: Time.zone.local(2022, 6, 18), scheme:)
scheme.reload
end
it "displays the timeline of availability" do
availability_attribute = display_scheme_attributes(scheme, support_user).find { |x| x[:name] == "Availability" }[:value]
expect(availability_attribute).to eq("Active from 1 April 2022 to 14 June 2022\nDeactivated on 15 June 2022\nActive from 18 June 2022 to 23 September 2022\nDeactivated on 24 September 2022\nActive from 28 September 2022")
end
end
context "and one non reactivated deactivation" do
before do
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 9, 24), reactivation_date: Time.zone.local(2022, 9, 28), scheme:)
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 15), reactivation_date: nil, scheme:)
scheme.reload
end
it "displays the timeline of availability" do
availability_attribute = display_scheme_attributes(scheme, support_user).find { |x| x[:name] == "Availability" }[:value]
expect(availability_attribute).to eq("Active from 1 April 2022 to 14 June 2022\nDeactivated on 15 June 2022\nActive from 28 September 2022")
end
end
end
context "with multiple out of order deactivations" do
context "and one non reactivated deactivation" do
before do
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 9, 24), reactivation_date: Time.zone.local(2022, 9, 28), scheme:)
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 10, 24), reactivation_date: Time.zone.local(2022, 10, 28), scheme:)
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 6, 15), reactivation_date: nil, scheme:)
scheme.reload
end
it "displays the timeline of availability" do
availability_attribute = display_scheme_attributes(scheme, support_user).find { |x| x[:name] == "Availability" }[:value]
expect(availability_attribute).to eq("Active from 1 April 2022 to 14 June 2022\nDeactivated on 15 June 2022\nActive from 28 September 2022 to 23 October 2022\nDeactivated on 24 October 2022\nActive from 28 October 2022")
end
end
end
context "with intersecting deactivations" do
before do
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 8, 10), reactivation_date: Time.zone.local(2022, 9, 1), scheme:)
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 9, 15), reactivation_date: nil, scheme:)
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 10, 10), reactivation_date: Time.zone.local(2022, 12, 1), scheme:)
FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2022, 11, 11), reactivation_date: Time.zone.local(2022, 12, 11), scheme:)
scheme.reload
end
it "displays the timeline of availability" do
availability_attribute = display_scheme_attributes(scheme).find { |x| x[:name] == "Availability" }[:value]
availability_attribute = display_scheme_attributes(scheme, support_user).find { |x| x[:name] == "Availability" }[:value]
expect(availability_attribute).to eq("Active from 8 August 2022 to 9 August 2022\nDeactivated on 10 August 2022\nActive from 1 September 2022 to 14 September 2022\nDeactivated on 15 September 2022")
expect(availability_attribute).to eq("Active from 1 April 2022 to 9 October 2022\nDeactivated on 10 October 2022\nActive from 11 December 2022")
end
end
end

9
spec/helpers/tag_helper_spec.rb

@ -11,6 +11,15 @@ RSpec.describe TagHelper do
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("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>")
expect(status_tag("incomplete", "app-tag--small")).to eq("<strong class=\"govuk-tag govuk-tag--red app-tag--small\">Incomplete</strong>")
expect(status_tag("deactivating_soon", "app-tag--small")).to eq("<strong class=\"govuk-tag govuk-tag--yellow app-tag--small\">Deactivating soon</strong>")
expect(status_tag("activating_soon", "app-tag--small")).to eq("<strong class=\"govuk-tag govuk-tag--blue app-tag--small\">Activating soon</strong>")
expect(status_tag("reactivating_soon", "app-tag--small")).to eq("<strong class=\"govuk-tag govuk-tag--blue app-tag--small\">Reactivating soon</strong>")
expect(status_tag("deactivated", "app-tag--small")).to eq("<strong class=\"govuk-tag govuk-tag--grey app-tag--small\">Deactivated</strong>")
end
end
end

3
spec/jobs/email_csv_job_spec.rb

@ -33,7 +33,8 @@ describe EmailCsvJob do
:completed,
owning_organisation: organisation,
managing_organisation: organisation,
created_by: user)
created_by: user,
startdate: Time.zone.local(2021, 5, 1))
allow(Storage::S3Service).to receive(:new).and_return(storage_service)
allow(storage_service).to receive(:write_file)

7
spec/models/form/lettings/pages/housing_provider_spec.rb

@ -79,7 +79,6 @@ RSpec.describe Form::Lettings::Pages::HousingProvider, type: :model do
before do
create(
:organisation_relationship,
:owning,
child_organisation: user.organisation,
parent_organisation: housing_provider,
)
@ -101,13 +100,11 @@ RSpec.describe Form::Lettings::Pages::HousingProvider, type: :model do
before do
create(
:organisation_relationship,
:owning,
child_organisation: user.organisation,
parent_organisation: housing_provider1,
)
create(
:organisation_relationship,
:owning,
child_organisation: user.organisation,
parent_organisation: housing_provider2,
)
@ -140,8 +137,8 @@ RSpec.describe Form::Lettings::Pages::HousingProvider, type: :model do
context "with >0 housing_providers" do
before do
create(:organisation_relationship, :owning, child_organisation: user.organisation)
create(:organisation_relationship, :owning, child_organisation: user.organisation)
create(:organisation_relationship, child_organisation: user.organisation)
create(:organisation_relationship, child_organisation: user.organisation)
end
it "is shown" do

10
spec/models/form/lettings/pages/managing_organisation_spec.rb

@ -76,8 +76,8 @@ RSpec.describe Form::Lettings::Pages::ManagingOrganisation, type: :model do
context "with >1 managing_agents" do
before do
create(:organisation_relationship, :managing, parent_organisation: log.owning_organisation)
create(:organisation_relationship, :managing, parent_organisation: log.owning_organisation)
create(:organisation_relationship, parent_organisation: log.owning_organisation)
create(:organisation_relationship, parent_organisation: log.owning_organisation)
end
it "is shown" do
@ -91,7 +91,6 @@ RSpec.describe Form::Lettings::Pages::ManagingOrganisation, type: :model do
before do
create(
:organisation_relationship,
:managing,
child_organisation: managing_agent,
parent_organisation: log.owning_organisation,
)
@ -128,8 +127,8 @@ RSpec.describe Form::Lettings::Pages::ManagingOrganisation, type: :model do
context "with >1 managing_agents" do
before do
create(:organisation_relationship, :managing, parent_organisation: user.organisation)
create(:organisation_relationship, :managing, parent_organisation: user.organisation)
create(:organisation_relationship, parent_organisation: user.organisation)
create(:organisation_relationship, parent_organisation: user.organisation)
end
it "is shown" do
@ -143,7 +142,6 @@ RSpec.describe Form::Lettings::Pages::ManagingOrganisation, type: :model do
before do
create(
:organisation_relationship,
:managing,
child_organisation: managing_agent,
parent_organisation: user.organisation,
)

6
spec/models/form/lettings/questions/housing_provider_spec.rb

@ -107,7 +107,7 @@ RSpec.describe Form::Lettings::Questions::HousingProvider, type: :model do
context "when housing providers != 0" do
before do
create(:organisation_relationship, :owning, child_organisation: user.organisation)
create(:organisation_relationship, child_organisation: user.organisation)
end
it "is visible in check answers" do
@ -133,8 +133,8 @@ RSpec.describe Form::Lettings::Questions::HousingProvider, type: :model do
context "when housing providers >= 2" do
before do
create(:organisation_relationship, :owning, child_organisation: user.organisation)
create(:organisation_relationship, :owning, child_organisation: user.organisation)
create(:organisation_relationship, child_organisation: user.organisation)
create(:organisation_relationship, child_organisation: user.organisation)
end
it "is visible in check answers" do

33
spec/models/form/lettings/questions/managing_organisation_spec.rb

@ -56,8 +56,29 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do
let(:user) { create(:user, :data_coordinator, organisation: create(:organisation, holds_own_stock: true)) }
let(:log) { create(:lettings_log) }
let!(:org_rel1) { create(:organisation_relationship, :managing, parent_organisation: user.organisation) }
let!(:org_rel2) { create(:organisation_relationship, :managing, parent_organisation: user.organisation) }
let!(:org_rel1) { create(:organisation_relationship, parent_organisation: user.organisation) }
let!(:org_rel2) { create(:organisation_relationship, parent_organisation: user.organisation) }
let(:options) do
{
"" => "Select an option",
user.organisation.id => "#{user.organisation.name} (Your organisation)",
org_rel1.child_organisation.id => org_rel1.child_organisation.name,
org_rel2.child_organisation.id => org_rel2.child_organisation.name,
}
end
it "shows managing agents with own org at the top" do
expect(question.displayed_answer_options(log, user)).to eq(options)
end
end
context "when user not support and does not own stock" do
let(:user) { create(:user, :data_coordinator, organisation: create(:organisation, holds_own_stock: false)) }
let(:log) { create(:lettings_log) }
let!(:org_rel1) { create(:organisation_relationship, parent_organisation: user.organisation) }
let!(:org_rel2) { create(:organisation_relationship, parent_organisation: user.organisation) }
let(:options) do
{
@ -98,8 +119,8 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do
let(:user) { create(:user, :support) }
let(:log_owning_org) { create(:organisation, holds_own_stock: false) }
let(:log) { create(:lettings_log, owning_organisation: log_owning_org) }
let!(:org_rel1) { create(:organisation_relationship, :managing, parent_organisation: log_owning_org) }
let!(:org_rel2) { create(:organisation_relationship, :managing, parent_organisation: log_owning_org) }
let!(:org_rel1) { create(:organisation_relationship, parent_organisation: log_owning_org) }
let!(:org_rel2) { create(:organisation_relationship, parent_organisation: log_owning_org) }
let(:options) do
{
@ -118,8 +139,8 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do
let(:user) { create(:user, :support) }
let(:log_owning_org) { create(:organisation, holds_own_stock: true) }
let(:log) { create(:lettings_log, owning_organisation: log_owning_org) }
let!(:org_rel1) { create(:organisation_relationship, :managing, parent_organisation: log_owning_org) }
let!(:org_rel2) { create(:organisation_relationship, :managing, parent_organisation: log_owning_org) }
let!(:org_rel1) { create(:organisation_relationship, parent_organisation: log_owning_org) }
let!(:org_rel2) { create(:organisation_relationship, parent_organisation: log_owning_org) }
let(:options) do
{

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

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

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

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

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

@ -0,0 +1,40 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::Buyer1Mortgage, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("inc1mort")
end
it "has the correct header" do
expect(question.header).to eq("Was buyer 1's income used for a mortgage application")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Buyer 1's income used for mortgage application")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
})
end
end

55
spec/models/form/sales/questions/buyer2_income_known_spec.rb

@ -0,0 +1,55 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::Buyer2IncomeKnown, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("income2nk")
end
it "has the correct header" do
expect(question.header).to eq("Do you know buyer 2’s annual income?")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Buyer 2’s gross annual income")
end
it "has the correct type" do
expect(question.type).to eq("radio")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({
"0" => { "value" => "Yes" },
"1" => { "value" => "No" },
})
end
it "has correct conditional for" do
expect(question.conditional_for).to eq({
"income2" => [0],
})
end
it "has the correct guidance_partial" do
expect(question.guidance_partial).to eq("what_counts_as_income_sales")
end
it "has the correct guidance position", :aggregate_failures do
expect(question.bottom_guidance?).to eq(true)
expect(question.top_guidance?).to eq(false)
end
end

53
spec/models/form/sales/questions/buyer2_income_spec.rb

@ -0,0 +1,53 @@
require "rails_helper"
RSpec.describe Form::Sales::Questions::Buyer2Income, type: :model do
subject(:question) { described_class.new(question_id, question_definition, page) }
let(:question_id) { nil }
let(:question_definition) { nil }
let(:page) { instance_double(Form::Page) }
it "has correct page" do
expect(question.page).to eq(page)
end
it "has the correct id" do
expect(question.id).to eq("income2")
end
it "has the correct header" do
expect(question.header).to eq("Buyer 2’s gross annual income")
end
it "has the correct check_answer_label" do
expect(question.check_answer_label).to eq("Buyer 2’s gross annual income")
end
it "has the correct type" do
expect(question.type).to eq("numeric")
end
it "is not marked as derived" do
expect(question.derived?).to be false
end
it "has the correct hint" do
expect(question.hint_text).to be_nil
end
it "has correct width" do
expect(question.width).to eq(5)
end
it "has correct step" do
expect(question.step).to eq(1)
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

2
spec/models/form/sales/sections/finances_spec.rb

@ -14,7 +14,7 @@ RSpec.describe Form::Sales::Sections::Finances, type: :model do
it "has correct subsections" do
expect(section.subsections.map(&:id)).to eq(
%w[
income_benefits_and_outgoings
income_benefits_and_savings
],
)
end

8
spec/models/form/sales/subsections/income_benefits_and_outgoings_spec.rb → spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb

@ -1,6 +1,6 @@
require "rails_helper"
RSpec.describe Form::Sales::Subsections::IncomeBenefitsAndOutgoings, type: :model do
RSpec.describe Form::Sales::Subsections::IncomeBenefitsAndSavings, type: :model do
subject(:subsection) { described_class.new(subsection_id, subsection_definition, section) }
let(:subsection_id) { nil }
@ -15,16 +15,18 @@ RSpec.describe Form::Sales::Subsections::IncomeBenefitsAndOutgoings, type: :mode
expect(subsection.pages.map(&:id)).to eq(
%w[
buyer_1_income
buyer_1_mortgage
buyer_2_income
],
)
end
it "has the correct id" do
expect(subsection.id).to eq("income_benefits_and_outgoings")
expect(subsection.id).to eq("income_benefits_and_savings")
end
it "has the correct label" do
expect(subsection.label).to eq("Income, benefits and outgoings")
expect(subsection.label).to eq("Income, benefits and savings")
end
it "has correct depends on" do

4
spec/models/form_handler_spec.rb

@ -61,14 +61,14 @@ RSpec.describe FormHandler do
it "is able to load a current sales form" do
form = form_handler.get_form("current_sales")
expect(form).to be_a(Form)
expect(form.pages.count).to eq(44)
expect(form.pages.count).to eq(46)
expect(form.name).to eq("2022_2023_sales")
end
it "is able to load a previous sales form" do
form = form_handler.get_form("previous_sales")
expect(form).to be_a(Form)
expect(form.pages.count).to eq(44)
expect(form.pages.count).to eq(46)
expect(form.name).to eq("2021_2022_sales")
end
end

34
spec/models/form_spec.rb

@ -235,4 +235,38 @@ RSpec.describe Form, type: :model do
expect(form.sections[1].class).to eq(Form::Sales::Sections::PropertyInformation)
end
end
describe "#in_crossover_period?" do
context "when now not specified" do
context "when after end period" do
subject(:form) { described_class.new(nil, 2022, [], "sales") }
it "returns false" do
Timecop.freeze(2023, 8, 1) do
expect(form).not_to be_in_crossover_period
end
end
end
context "when during crossover" do
subject(:form) { described_class.new(nil, 2022, [], "sales") }
it "returns true" do
Timecop.freeze(2023, 6, 1) do
expect(form).to be_in_crossover_period
end
end
end
context "when before crossover" do
subject(:form) { described_class.new(nil, 2022, [], "sales") }
it "returns false" do
Timecop.freeze(2023, 1, 1) do
expect(form).not_to be_in_crossover_period
end
end
end
end
end
end

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save