Browse Source

fixing cached files

pull/726/head
Ted 3 years ago
parent
commit
fe3f228e60
  1. 2
      .dockerignore
  2. 8
      Dockerfile_dev
  3. 4
      Gemfile.lock
  4. 5
      app/controllers/case_logs_controller.rb
  5. 17
      app/controllers/locations_controller.rb
  6. 2
      app/controllers/organisations_controller.rb
  7. 18
      app/controllers/schemes_controller.rb
  8. 4
      app/helpers/tab_nav_helper.rb
  9. 4
      app/helpers/tasklist_helper.rb
  10. 4
      app/models/case_log.rb
  11. 26
      app/models/derived_variables/case_log_variables.rb
  12. 2
      app/models/form/setup/questions/scheme_id.rb
  13. 2
      app/models/form/subsection.rb
  14. 3
      app/models/location.rb
  15. 6
      app/models/organisation.rb
  16. 16
      app/models/scheme.rb
  17. 2
      app/models/validations/local_authority_validations.rb
  18. 1
      app/models/validations/property_validations.rb
  19. 1
      app/services/imports/case_logs_import_service.rb
  20. 5
      app/services/postcode_service.rb
  21. 2
      app/views/case_logs/_tasklist.html.erb
  22. 4
      app/views/layouts/application.html.erb
  23. 5
      app/views/locations/edit.html.erb
  24. 26
      app/views/locations/edit_name.html.erb
  25. 14
      app/views/locations/index.html.erb
  26. 2
      app/views/schemes/_scheme_list.html.erb
  27. 6
      app/views/schemes/check_answers.html.erb
  28. 2
      app/views/schemes/confirm_secondary.html.erb
  29. 23
      app/views/schemes/details.html.erb
  30. 46
      app/views/schemes/edit_name.html.erb
  31. 20
      app/views/schemes/new.html.erb
  32. 3
      app/views/schemes/primary_client_group.html.erb
  33. 3
      app/views/schemes/secondary_client_group.html.erb
  34. 6
      app/views/schemes/show.html.erb
  35. 3
      app/views/schemes/support.html.erb
  36. 27
      config/forms/2021_2022.json
  37. 1
      config/initializers/postcode_regex.rb
  38. 3
      config/locales/en.yml
  39. 5
      config/routes.rb
  40. 8
      db/migrate/20220705130923_rename_scheme_organisations.rb
  41. 7
      db/migrate/20220706104313_add_unit_type_sh.rb
  42. 21
      db/schema.rb
  43. 8
      db/seeds.rb
  44. 6
      docs/developer_setup.md
  45. 86
      docs/form/form.md
  46. 28
      docs/form/page.md
  47. 82
      docs/form/question.md
  48. 26
      docs/form/section.md
  49. 28
      docs/form/subsection.md
  50. 4
      docs/form_builder.md
  51. 4
      docs/form_runner.md
  52. 8
      docs/schemes.md
  53. 2
      spec/factories/location.rb
  54. 2
      spec/factories/scheme.rb
  55. 2
      spec/features/form/tasklist_page_spec.rb
  56. 124
      spec/features/schemes_spec.rb
  57. 290
      spec/fixtures/complete_case_log.json
  58. 4
      spec/fixtures/exports/case_logs.csv
  59. 1
      spec/fixtures/exports/case_logs.xml
  60. 8
      spec/helpers/navigation_items_helper_spec.rb
  61. 3
      spec/helpers/tab_nav_helper_spec.rb
  62. 2
      spec/helpers/tasklist_helper_spec.rb
  63. 72
      spec/models/case_log_spec.rb
  64. 6
      spec/models/form/setup/questions/scheme_id_spec.rb
  65. 2
      spec/models/organisation_spec.rb
  66. 2
      spec/models/scheme_spec.rb
  67. 152
      spec/requests/locations_controller_spec.rb
  68. 8
      spec/requests/organisations_controller_spec.rb
  69. 148
      spec/requests/schemes_controller_spec.rb
  70. 9
      spec/services/postcode_service_spec.rb
  71. 108
      yarn.lock

2
.dockerignore

@ -11,8 +11,6 @@ storage
public/assets
.byebug_history
config/master.key
public/packs
public/packs-test
node_modules

8
Dockerfile_dev

@ -23,6 +23,14 @@ RUN bundle install ${BUNDLE_FLAGS}
COPY package.json yarn.lock /app/
RUN yarn install --frozen-lockfile
# Install gecko driver for Capybara tests
RUN apk add firefox
RUN wget https://github.com/mozilla/geckodriver/releases/download/v0.31.0/geckodriver-v0.31.0-linux64.tar.gz \
&& tar -xvzf geckodriver-v0.31.0-linux64.tar.gz \
&& rm geckodriver-v0.31.0-linux64.tar.gz \
&& chmod +x geckodriver \
&& mv geckodriver /usr/local/bin/
# Copy all files to /app (except what is defined in .dockerignore)
COPY . /app/

4
Gemfile.lock

@ -82,7 +82,7 @@ GEM
public_suffix (>= 2.0.2, < 5.0)
ast (2.4.2)
aws-eventstream (1.2.0)
aws-partitions (1.602.0)
aws-partitions (1.603.0)
aws-sdk-core (3.131.2)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.525.0)
@ -168,7 +168,7 @@ GEM
ffi (1.15.5)
globalid (1.0.0)
activesupport (>= 5.0)
govuk-components (3.1.2)
govuk-components (3.1.3)
actionpack (>= 6.1)
activemodel (>= 6.1)
html-attributes-utils (~> 0.9, >= 0.9.2)

5
app/controllers/case_logs_controller.rb

@ -14,12 +14,13 @@ class CaseLogsController < ApplicationController
all_logs = current_user.case_logs
unpaginated_filtered_logs = filtered_case_logs(filtered_collection(all_logs, search_term))
respond_to do |format|
format.html do
@pagy, @case_logs = pagy(unpaginated_filtered_logs)
@searched = search_term.presence
@total_count = all_logs.size
end
respond_to do |format|
format.html
format.csv do
send_data unpaginated_filtered_logs.to_csv, filename: "logs-#{Time.zone.now}.csv"
end

17
app/controllers/locations_controller.rb

@ -27,9 +27,18 @@ class LocationsController < ApplicationController
def edit; end
def edit_name; end
def update
page = params[:location][:page]
if @location.update(location_params)
case page
when "edit"
location_params[:add_another_location] == "Yes" ? redirect_to(new_location_path(@location.scheme)) : redirect_to(scheme_check_answers_path(@scheme, anchor: "locations"))
when "edit-name"
redirect_to(locations_path(@scheme))
end
else
render :edit, status: :unprocessable_entity
end
@ -38,7 +47,7 @@ class LocationsController < ApplicationController
private
def find_scheme
@scheme = if %w[new create index].include?(action_name)
@scheme = if %w[new create index edit_name].include?(action_name)
Scheme.find(params[:id])
else
@location.scheme
@ -46,7 +55,7 @@ private
end
def find_location
@location = Location.find(params[:id])
@location = params[:location_id].present? ? Location.find(params[:location_id]) : Location.find(params[:id])
end
def authenticate_scope!
@ -54,14 +63,14 @@ private
end
def authenticate_action!
if %w[new edit update create index].include?(action_name) && !((current_user.organisation == @scheme.organisation) || current_user.support?)
if %w[new edit update create index edit_name].include?(action_name) && !((current_user.organisation == @scheme.owning_organisation) || current_user.support?)
render_not_found and return
end
end
def location_params
required_params = params.require(:location).permit(:postcode, :name, :total_units, :type_of_unit, :wheelchair_adaptation, :add_another_location).merge(scheme_id: @scheme.id)
required_params[:postcode] = required_params[:postcode].delete(" ").upcase.encode("ASCII", "UTF-8", invalid: :replace, undef: :replace, replace: "") if required_params[:postcode]
required_params[:postcode] = PostcodeService.clean(required_params[:postcode]) if required_params[:postcode]
required_params
end
end

2
app/controllers/organisations_controller.rb

@ -17,7 +17,7 @@ class OrganisationsController < ApplicationController
end
def schemes
all_schemes = Scheme.where(organisation: @organisation)
all_schemes = Scheme.where(owning_organisation: @organisation)
@pagy, @schemes = pagy(filtered_collection(all_schemes, search_term))
@searched = search_term.presence

18
app/controllers/schemes_controller.rb

@ -29,8 +29,8 @@ class SchemesController < ApplicationController
if @scheme.save
render "schemes/primary_client_group"
else
@scheme.errors.add(:organisation_id, message: @scheme.errors[:organisation])
@scheme.errors.delete(:organisation)
@scheme.errors.add(:owning_organisation_id, message: @scheme.errors[:organisation])
@scheme.errors.delete(:owning_organisation)
render :new, status: :unprocessable_entity
end
end
@ -79,6 +79,10 @@ class SchemesController < ApplicationController
render "schemes/check_answers"
end
def edit_name
render "schemes/edit_name"
end
private
def confirm_secondary_page?(page)
@ -97,14 +101,16 @@ private
new_location_path
when "details"
scheme_primary_client_group_path(@scheme)
when "edit-name"
scheme_path(@scheme)
end
end
def scheme_params
required_params = params.require(:scheme).permit(:service_name,
:sensitive,
:organisation_id,
:stock_owning_organisation_id,
:owning_organisation_id,
:managing_organisation_id,
:scheme_type,
:registered_under_care_act,
:id,
@ -116,7 +122,7 @@ private
required_params[:sensitive] = required_params[:sensitive].to_i if required_params[:sensitive]
if current_user.data_coordinator?
required_params[:organisation_id] = current_user.organisation_id
required_params[:owning_organisation_id] = current_user.organisation_id
end
required_params
end
@ -132,7 +138,7 @@ private
def authenticate_scope!
head :unauthorized and return unless current_user.data_coordinator? || current_user.support?
if %w[show locations primary_client_group confirm_secondary_client_group secondary_client_group support details check_answers].include?(action_name) && !((current_user.organisation == @scheme.organisation) || current_user.support?)
if %w[show locations primary_client_group confirm_secondary_client_group secondary_client_group support details check_answers edit_name].include?(action_name) && !((current_user.organisation == @scheme.owning_organisation) || current_user.support?)
render_not_found and return
end
end

4
app/helpers/tab_nav_helper.rb

@ -6,9 +6,9 @@ module TabNavHelper
[govuk_link_to(link_text, user), "<span class=\"govuk-visually-hidden\">User </span><span class=\"govuk-!-font-weight-regular app-!-colour-muted\">#{user.email}</span>"].join("\n")
end
def location_cell(location)
def location_cell(location, link)
link_text = location.postcode
[govuk_link_to(link_text, "/schemes/#{location.scheme.id}/locations/#{location.id}/edit", method: :patch), "<span class=\"govuk-visually-hidden\">Location </span><span class=\"govuk-!-font-weight-regular app-!-colour-muted\">#{location.name}</span>"].join("\n")
[govuk_link_to(link_text, link, method: :patch), "<span class=\"govuk-visually-hidden\">Location </span><span class=\"govuk-!-font-weight-regular app-!-colour-muted\">#{location.name}</span>"].join("\n")
end
def scheme_cell(scheme)

4
app/helpers/tasklist_helper.rb

@ -6,9 +6,9 @@ module TasklistHelper
end
def get_subsections_count(case_log, status = :all)
return case_log.form.subsections.count if status == :all
return case_log.form.subsections.count { |subsection| subsection.applicable_questions(case_log).count.positive? } if status == :all
case_log.form.subsections.count { |subsection| subsection.status(case_log) == status }
case_log.form.subsections.count { |subsection| subsection.status(case_log) == status && subsection.applicable_questions(case_log).count.positive? }
end
def next_page_or_check_answers(subsection, case_log, current_user)

4
app/models/case_log.rb

@ -585,12 +585,12 @@ private
def get_inferred_la(postcode)
# Avoid network calls when postcode is invalid
return unless postcode.match(Validations::PropertyValidations::POSTCODE_REGEXP)
return unless postcode.match(POSTCODE_REGEXP)
postcode_lookup = nil
begin
# URI encoding only supports ASCII characters
ascii_postcode = postcode.encode("ASCII", "UTF-8", invalid: :replace, undef: :replace, replace: "")
ascii_postcode = PostcodeService.clean(postcode)
Timeout.timeout(5) { postcode_lookup = PIO.lookup(ascii_postcode) }
rescue Timeout::Error
Rails.logger.warn("Postcodes.io lookup timed out")

26
app/models/derived_variables/case_log_variables.rb

@ -1,5 +1,13 @@
module DerivedVariables::CaseLogVariables
RENT_TYPE_MAPPING = { 0 => 1, 1 => 2, 2 => 2, 3 => 3, 4 => 3, 5 => 3 }.freeze
TYPE_OF_UNIT_MAP = {
"Self-contained flat or bedsit" => 1,
"Self-contained flat or bedsit with common facilities" => 2,
"Shared flat" => 3,
"Shared house or hostel" => 4,
"Bungalow" => 5,
"Self-contained house" => 6,
}.freeze
def supported_housing_schemes_enabled?
FeatureToggle.supported_housing_schemes_enabled?
@ -8,7 +16,8 @@ module DerivedVariables::CaseLogVariables
def scheme_has_multiple_locations?
return false unless scheme
scheme.locations.size > 1
@scheme_locations_count ||= scheme.locations.size
@scheme_locations_count > 1
end
def set_derived_fields!
@ -67,9 +76,22 @@ module DerivedVariables::CaseLogVariables
self.new_old = new_or_existing_tenant
self.vacdays = property_vacant_days
if is_supported_housing? && (scheme && scheme.locations.size == 1)
if is_supported_housing? && scheme
if scheme.locations.size == 1
self.location = scheme.locations.first
end
if location
self.la = location.county
self.postcode_full = location.postcode
self.unittype_sh = TYPE_OF_UNIT_MAP[location.type_of_unit]
self.builtype = form.questions.find { |x| x.id == "builtype" }.answer_options.find { |_key, value| value["value"] == location.type_of_building }.first
wheelchair_adaptation_map = { 1 => 1, 0 => 2 }
self.wchair = wheelchair_adaptation_map[location.wheelchair_adaptation.to_i]
end
if is_renewal?
self.voiddate = startdate
end
end
end
private

2
app/models/form/setup/questions/scheme_id.rb

@ -22,7 +22,7 @@ class Form::Setup::Questions::SchemeId < ::Form::Question
def displayed_answer_options(case_log)
return {} unless case_log.created_by
user_org_scheme_ids = Scheme.select(:id).where(organisation_id: case_log.created_by.organisation_id).map(&:id)
user_org_scheme_ids = Scheme.select(:id).where(owning_organisation_id: case_log.created_by.organisation_id).map(&:id)
answer_options.select do |k, _v|
user_org_scheme_ids.include?(k.to_i)
end

2
app/models/form/subsection.rb

@ -34,7 +34,7 @@ class Form::Subsection
qs = applicable_questions(case_log)
qs_optional_removed = qs.reject { |q| case_log.optional_fields.include?(q.id) }
return :not_started if qs.all? { |question| case_log[question.id].blank? || question.read_only? || question.derived? }
return :not_started if qs.count.positive? && qs.all? { |question| case_log[question.id].blank? || question.read_only? || question.derived? }
return :completed if qs_optional_removed.all? { |question| question.completed?(case_log) }
:in_progress

3
app/models/location.rb

@ -1,5 +1,4 @@
class Location < ApplicationRecord
include Validations::PropertyValidations
validate :validate_postcode
belongs_to :scheme
@ -36,7 +35,7 @@ class Location < ApplicationRecord
private
def validate_postcode
if postcode.nil? || !postcode&.match(Validations::PropertyValidations::POSTCODE_REGEXP)
if postcode.nil? || !postcode&.match(POSTCODE_REGEXP)
error_message = I18n.t("validations.postcode")
errors.add :postcode, error_message
end

6
app/models/organisation.rb

@ -4,8 +4,10 @@ class Organisation < ApplicationRecord
has_many :managed_case_logs, class_name: "CaseLog", foreign_key: "managing_organisation_id"
has_many :data_protection_confirmations
has_many :organisation_rent_periods
has_many :owned_schemes, class_name: "Scheme", foreign_key: "stock_owning_organisation_id", dependent: :destroy
has_many :managed_schemes, class_name: "Scheme", dependent: :destroy
has_many :owned_schemes, class_name: "Scheme", foreign_key: "owning_organisation_id", dependent: :destroy
has_many :managed_schemes, class_name: "Scheme", foreign_key: "managing_organisation_id", dependent: :destroy
scope :search_by_name, ->(name) { where("name ILIKE ?", "%#{name}%") }
scope :search_by, ->(param) { search_by_name(param) }

16
app/models/scheme.rb

@ -1,6 +1,6 @@
class Scheme < ApplicationRecord
belongs_to :organisation
belongs_to :stock_owning_organisation, optional: true, class_name: "Organisation"
belongs_to :owning_organisation, class_name: "Organisation"
belongs_to :managing_organisation, optional: true, class_name: "Organisation"
has_many :locations
has_many :case_logs
@ -95,8 +95,8 @@ class Scheme < ApplicationRecord
{ name: "Service code", value: id_to_display },
{ name: "Name", value: service_name },
{ name: "Confidential information", value: sensitive },
{ name: "Housing stock owned by", value: stock_owning_organisation&.name },
{ name: "Managed by", value: organisation.name },
{ name: "Housing stock owned by", value: owning_organisation.name },
{ name: "Managed by", value: managing_organisation&.name },
{ name: "Type of scheme", value: scheme_type },
{ name: "Registered under Care Standards Act 2000", value: registered_under_care_act },
]
@ -130,10 +130,10 @@ class Scheme < ApplicationRecord
def display_attributes
[
{ name: "Service code", value: id_to_display },
{ name: "Name", value: service_name },
{ name: "Confidential information", value: sensitive },
{ name: "Housing stock owned by", value: stock_owning_organisation&.name },
{ name: "Managed by", value: organisation.name },
{ name: "Name", value: service_name, edit: true },
{ name: "Confidential information", value: sensitive, edit: true },
{ name: "Housing stock owned by", value: owning_organisation.name, edit: true },
{ name: "Managed by", value: managing_organisation&.name },
{ name: "Type of scheme", value: scheme_type },
{ name: "Registered under Care Standards Act 2000", value: registered_under_care_act },
{ name: "Primary client group", value: primary_client_group },

2
app/models/validations/local_authority_validations.rb

@ -1,6 +1,4 @@
module Validations::LocalAuthorityValidations
POSTCODE_REGEXP = Validations::PropertyValidations::POSTCODE_REGEXP
def validate_previous_accommodation_postcode(record)
postcode = record.ppostcode_full
if record.previous_postcode_known? && (postcode.blank? || !postcode.match(POSTCODE_REGEXP))

1
app/models/validations/property_validations.rb

@ -1,7 +1,6 @@
module Validations::PropertyValidations
# Validations methods need to be called 'validate_<page_name>' to run on model save
# or 'validate_' to run on submit as well
POSTCODE_REGEXP = /^(([A-Z]{1,2}[0-9][A-Z0-9]?|ASCN|STHL|TDCU|BBND|[BFS]IQQ|PCRN|TKCA) ?[0-9][A-Z]{2}|BFPO ?[0-9]{1,4}|(KY[0-9]|MSR|VG|AI)[ -]?[0-9]{4}|[A-Z]{2} ?[0-9]{2}|GE ?CX|GIR ?0A{2}|SAN ?TA1)$/i
def validate_property_number_of_times_relet(record)
return unless record.offered

1
app/services/imports/case_logs_import_service.rb

@ -417,7 +417,6 @@ module Imports
end
end
POSTCODE_REGEXP = Validations::PropertyValidations::POSTCODE_REGEXP
def compose_postcode(xml_doc, outcode, incode)
outcode_value = string_or_nil(xml_doc, outcode)
incode_value = string_or_nil(xml_doc, incode)

5
app/services/postcode_service.rb

@ -0,0 +1,5 @@
class PostcodeService
def self.clean(postcode)
postcode.encode("ASCII", "UTF-8", invalid: :replace, undef: :replace, replace: "").delete(" ").upcase
end
end

2
app/views/case_logs/_tasklist.html.erb

@ -9,6 +9,7 @@
<% end %>
<ul class="app-task-list__items">
<% section.subsections.map do |subsection| %>
<% if subsection.applicable_questions(@case_log).count > 0 || !subsection.enabled?(@case_log) %>
<% subsection_status = subsection.status(@case_log) %>
<li class="app-task-list__item">
<span class="app-task-list__task-name" id="<%= subsection.id.dasherize %>">
@ -17,6 +18,7 @@
<%= status_tag(subsection_status, "app-task-list__tag") %>
</li>
<% end %>
<% end %>
</ul>
</li>
<% end %>

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

@ -33,6 +33,10 @@
console.log(<%= session.to_json.html_safe %>)
</script>
<% end %>
<% if Rails.env.production? %>
<script defer data-domain="<%= ENV["APP_HOST"].split("https://")[1] %>" src="https://plausible.io/js/plausible.js"></script>
<% end %>
</head>
<body class="govuk-template__body app-template--wide">

5
app/views/locations/edit.html.erb

@ -30,7 +30,6 @@
autofocus: true %>
<% type_of_units_selection = Location.type_of_units.keys.map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %>
<%= f.govuk_collection_radio_buttons :type_of_unit,
type_of_units_selection,
:id,
@ -38,7 +37,6 @@
legend: { text: "What is this type of scheme?", size: "m" } %>
<% wheelchair_user_selection = Location.wheelchair_adaptations.keys.map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %>
<%= f.govuk_collection_radio_buttons :wheelchair_adaptation,
wheelchair_user_selection,
:id,
@ -49,7 +47,6 @@
<%= govuk_section_break(visible: true, size: "m") %>
<% another_location_selection = %w[Yes no].map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %>
<%= f.govuk_collection_radio_buttons :add_another_location,
another_location_selection,
:id,
@ -57,6 +54,8 @@
inline: true,
legend: { text: "Do you want to add another location?", size: "m" } %>
<%= f.hidden_field :page, value: "edit" %>
<%= f.govuk_submit "Save and continue" %>
</div>
</div>

26
app/views/locations/edit_name.html.erb

@ -0,0 +1,26 @@
<% content_for :title, "Location name for #{@location.postcode}" %>
<% content_for :before_content do %>
<%= govuk_back_link(
text: "Back",
href: "/schemes/#{@scheme.id}/locations",
) %>
<% end %>
<%= render partial: "organisations/headings", locals: { main: "Location name for #{@location.postcode}", sub: @scheme.service_name } %>
<%= form_for(@location, method: :patch, url: location_path(location_id: @location.id)) do |f| %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<%= f.govuk_error_summary %>
<%= f.govuk_text_field :name,
label: { hidden: true },
hint: { text: "This is how you refer to this location within your organisation" } %>
<%= f.hidden_field :page, value: "edit-name" %>
<%= f.govuk_submit "Save and continue" %>
</div>
</div>
<% end %>

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

@ -1,20 +1,20 @@
<% title = @scheme.service_name %>
<% content_for :title, title %>
<% content_for :before_content do %>
<%= govuk_back_link(
text: "Back",
href: "/schemes/#{@scheme.id}",
) %>
<% end %>
<%= render partial: "organisations/headings", locals: { main: @scheme.service_name, sub: nil } %>
<% location_caption = @scheme.locations.count.eql?(1) ? "1 location" : "#{@scheme.locations.count} locations" %>
<%= render SubNavigationComponent.new(items: scheme_items(request.path, @scheme.id, location_caption)) %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-three-quarters">
<%= render SubNavigationComponent.new(items: scheme_items(request.path, @scheme.id, "Locations")) %>
<%= govuk_table do |table| %>
<%= table.caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %>
<strong><%= @scheme.locations.count %></strong> <%= @scheme.locations.count.eql?(1) ? "location" : "locations" %>.
<strong><%= @scheme.locations.count %></strong> <%= @scheme.locations.count.eql?(1) ? "location" : "locations" %>
<% end %>
<%= table.head do |head| %>
<%= head.row do |row| %>
@ -36,14 +36,12 @@
<%= table.body do |body| %>
<%= body.row do |row| %>
<% row.cell(text: location.id) %>
<% row.cell(text: location.postcode) %>
<% row.cell(text: simple_format(location_cell(location, "/schemes/#{@scheme.id}/locations/#{location.id}/edit-name"), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %>
<% row.cell(text: location.total_units) %>
<% row.cell(text: simple_format("<span>#{location.type_of_unit}</span>#{location.wheelchair_adaptation == 'Yes' ? "\n<span class=\"govuk-!-font-weight-regular app-!-colour-muted\">With wheelchair adaptations</span>" : ''}")) %>
<% end %>
<% end %>
<% end %>
<% end %>
</div>
</div>
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "locations" } %>

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

@ -24,7 +24,7 @@
<%= 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.organisation.name) %>
<% row.cell(text: scheme.managing_organisation&.name) %>
<% row.cell(text: scheme.created_at.to_formatted_s(:govuk_date)) %>
<% end %>
<% end %>

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

@ -2,15 +2,13 @@
<%= render partial: "organisations/headings", locals: { main: "Check your changes before creating this scheme", sub: @scheme.service_name } %>
<% location_caption = @scheme.locations.count.eql?(1) ? "1 location" : "#{@scheme.locations.count} locations" %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-three-quarters-from-desktop">
<%= govuk_tabs(title: "Check your answers before creating this scheme") do |component| %>
<% component.tab(label: "Scheme") do %>
<%= govuk_summary_list do |summary_list| %>
<% @scheme.check_details_attributes.each do |attr| %>
<% next if current_user.data_coordinator? && attr[:name] == ("Managed by") %>
<% next if current_user.data_coordinator? && attr[:name] == ("owned by") %>
<%= summary_list.row do |row| %>
<% row.key { attr[:name].to_s } %>
<% row.value { details_html(attr) } %>
@ -89,7 +87,7 @@
<%= table.body do |body| %>
<%= body.row do |row| %>
<% row.cell(text: location.id) %>
<% row.cell(text: simple_format(location_cell(location), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %>
<% row.cell(text: simple_format(location_cell(location, "/schemes/#{@scheme.id}/locations/#{location.id}/edit"), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %>
<% row.cell(text: location.total_units) %>
<% row.cell(text: simple_format("<span>#{location.type_of_unit}</span>#{location.wheelchair_adaptation == 'Yes' ? "\n<span class=\"govuk-!-font-weight-regular app-!-colour-muted\">With wheelchair adaptations</span>" : ''}")) %>
<% end %>

2
app/views/schemes/confirm_secondary.html.erb

@ -12,9 +12,7 @@
<%= form_for(@scheme, method: :patch) do |f| %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<% selection = [OpenStruct.new(id: "Yes", name: "Yes"), OpenStruct.new(id: "No", name: "No")] %>
<%= f.govuk_collection_radio_buttons :has_other_client_group,
selection,
:id,

23
app/views/schemes/details.html.erb

@ -16,38 +16,41 @@
<%= f.govuk_text_field :service_name,
label: { text: "Scheme name", size: "m" },
hint: { text: "This is how you’ll refer to this supported housing scheme within your organisation. For example, the name could relate to the address or location. You’ll be able to see the client group when selecting it." } %>
hint: { text: "This is how you refer to this supported housing scheme within your organisation. For example, the name could relate to the address or location. You’ll be able to see the client group when selecting it." } %>
<%= f.govuk_check_boxes_fieldset :sensitive,
legend: nil do %>
<%= f.govuk_check_box :sensitive,
1,
0,
checked: @scheme.sensitive?,
multiple: false,
checked: @scheme.sensitive == "Yes",
label: { text: "This scheme contains confidential information" } %>
<% end %>
<% null_option = [OpenStruct.new(id: "", name: "Select an option")] %>
<% organisations = Organisation.all.map { |org| OpenStruct.new(id: org.id, name: org.name) } %>
<% stock_org_answer_options = null_option + organisations %>
<% managing_org_answer_options = null_option + organisations %>
<%= f.govuk_collection_select :stock_owning_organisation_id,
stock_org_answer_options,
<%= f.govuk_collection_select :managing_organisation_id,
managing_org_answer_options,
:id,
:name,
label: { text: "Which organisation owns the housing stock for this scheme?", size: "m" },
label: { text: "Which organisation manages this scheme?", size: "m" },
options: { required: true },
"data-controller": %w[accessible-autocomplete conditional-filter] %>
<% if current_user.support? %>
<%= f.govuk_collection_select :organisation_id,
<%= f.govuk_collection_select :owning_organisation_id,
organisations,
:id,
:name,
label: { text: "Which organisation manages this scheme?", size: "m" },
options: { required: true },
label: { text: "Which organisation owns the housing stock for this scheme?", size: "m" },
"data-controller": %w[accessible-autocomplete conditional-filter] %>
<% end %>
<% if current_user.data_coordinator? %>
<%= f.hidden_field :organisation_id, value: current_user.organisation.id %>
<%= f.hidden_field :owning_organisation_id, value: current_user.organisation.id %>
<% end %>
<% scheme_types_selection = Scheme.scheme_types.keys.excluding("Missing").map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %>

46
app/views/schemes/edit_name.html.erb

@ -0,0 +1,46 @@
<% content_for :title, "Scheme details" %>
<% content_for :before_content do %>
<%= govuk_back_link(
text: "Back",
href: :back,
) %>
<% end %>
<%= render partial: "organisations/headings", locals: { main: "Scheme details", sub: @scheme.service_name } %>
<%= form_for(@scheme, method: :patch) do |f| %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<%= f.govuk_error_summary %>
<%= f.govuk_text_field :service_name,
label: { text: "Scheme name", size: "m" },
hint: { text: "This is how you refer to this supported housing scheme within your organisation. For example, the name could relate to the address or location. You’ll be able to see the client group when selecting it." } %>
<%= f.govuk_check_boxes_fieldset :sensitive,
legend: nil do %>
<%= f.govuk_check_box :sensitive,
1,
0,
multiple: false,
checked: @scheme.sensitive == "Yes",
label: { text: "This scheme contains confidential information" } %>
<% end %>
<% organisations = Organisation.all.map { |org| OpenStruct.new(id: org.id, name: org.name) } %>
<% if current_user.support? %>
<%= f.govuk_collection_select :owning_organisation_id,
organisations,
:id,
:name,
label: { text: "Which organisation owns the housing stock for this scheme?", size: "m" },
"data-controller": %w[accessible-autocomplete conditional-filter] %>
<% end %>
<%= f.hidden_field :page, value: "edit-name" %>
<%= f.govuk_submit "Save changes" %>
</div>
</div>
<% end %>

20
app/views/schemes/new.html.erb

@ -20,38 +20,39 @@
label: { text: "Scheme name", size: "m" },
hint: { text: "This is how you refer to this supported housing scheme within your organisation. For example, the name could relate to the address or location. You’ll be able to see the client group when selecting it." } %>
<%= f.govuk_check_boxes_fieldset :sensitive,
legend: nil do %>
<%= f.govuk_check_box :sensitive,
"Yes",
checked: @scheme.sensitive?,
1,
0,
multiple: false,
label: { text: "This scheme contains confidential information" } %>
<% end %>
<% null_option = [OpenStruct.new(id: "", name: "Select an option")] %>
<% organisations = Organisation.all.map { |org| OpenStruct.new(id: org.id, name: org.name) } %>
<% answer_options = null_option + organisations %>
<%= f.govuk_collection_select :stock_owning_organisation_id,
<%= f.govuk_collection_select :managing_organisation_id,
answer_options,
:id,
:name,
label: { text: "Which organisation owns the housing stock for this scheme?", size: "m" },
label: { text: "Which organisation manages this scheme?", size: "m" },
"data-controller": %w[accessible-autocomplete conditional-filter] %>
<% if current_user.support? %>
<%= f.govuk_collection_select :organisation_id,
<%= f.govuk_collection_select :owning_organisation_id,
answer_options,
:id,
:name,
label: { text: "Which organisation manages this scheme?", size: "m" },
label: { text: "Which organisation owns the housing stock for this scheme?", size: "m" },
"data-controller": %w[accessible-autocomplete conditional-filter] %>
<% end %>
<% if current_user.data_coordinator? %>
<%= f.hidden_field :organisation_id, value: current_user.organisation.id %>
<%= f.hidden_field :owning_organisation_id, value: current_user.organisation.id %>
<% end %>
<% scheme_types_selection = Scheme.scheme_types.keys.excluding("Missing").map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %>
<%= f.govuk_collection_radio_buttons :scheme_type,
scheme_types_selection,
:id,
@ -59,7 +60,6 @@
legend: { text: "What is this type of scheme?", size: "m" } %>
<% care_acts_selection = Scheme.registered_under_care_acts.keys.reverse.map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %>
<%= f.govuk_collection_radio_buttons :registered_under_care_act,
care_acts_selection,
:id,

3
app/views/schemes/primary_client_group.html.erb

@ -14,9 +14,6 @@
<div class="govuk-grid-column-two-thirds">
<%= f.govuk_error_summary %>
<legend class="govuk-fieldset__legend">
</legend>
<% primary_client_group_selection = Scheme.primary_client_groups.keys.excluding("Missing").map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %>
<%= f.govuk_collection_radio_buttons :primary_client_group,
primary_client_group_selection,

3
app/views/schemes/secondary_client_group.html.erb

@ -14,9 +14,6 @@
<div class="govuk-grid-column-two-thirds">
<%= f.govuk_error_summary %>
<legend class="govuk-fieldset__legend">
</legend>
<% secondary_client_group_selection = Scheme.secondary_client_groups.keys.excluding("Missing").map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %>
<%= f.govuk_collection_radio_buttons :secondary_client_group,
secondary_client_group_selection,

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

@ -10,15 +10,17 @@
<%= render partial: "organisations/headings", locals: { main: @scheme.service_name, sub: nil } %>
<%= render SubNavigationComponent.new(items: scheme_items(request.path, @scheme.id, @scheme.locations.count.eql?(1) ? "1 location" : "#{@scheme.locations.count} locations")) %>
<%= render SubNavigationComponent.new(items: scheme_items(request.path, @scheme.id, "Locations")) %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<div class="govuk-grid-column-three-quarters-from-desktop">
<%= govuk_summary_list do |summary_list| %>
<% @scheme.display_attributes.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 { details_html(attr) } %>
<% row.action(text: "Change", href: scheme_edit_name_path(scheme_id: @scheme.id)) if attr[:edit] %>
<% end %>
<% end %>
<% end %>

3
app/views/schemes/support.html.erb

@ -14,9 +14,6 @@
<div class="govuk-grid-column-two-thirds">
<%= f.govuk_error_summary %>
<legend class="govuk-fieldset__legend">
</legend>
<% support_type_selection = Scheme.support_types.keys.excluding("Missing").map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %>
<%= f.govuk_collection_radio_buttons :support_type,
support_type_selection,

27
config/forms/2021_2022.json

@ -54,7 +54,12 @@
"value": "Not known"
}
}
},
"depends_on": [
{
"needstype": 1
}
]
},
"property_local_authority": {
"header": "",
@ -388,7 +393,8 @@
},
"depends_on": [
{
"is_la_inferred": false
"is_la_inferred": false,
"needstype": 1
}
]
},
@ -617,7 +623,12 @@
}
}
}
},
"depends_on": [
{
"needstype": 1
}
]
},
"property_building_type": {
"header": "",
@ -637,7 +648,12 @@
}
}
}
},
"depends_on": [
{
"needstype": 1
}
]
},
"property_wheelchair_accessible": {
"header": "",
@ -657,7 +673,12 @@
}
}
}
},
"depends_on": [
{
"needstype": 1
}
]
},
"property_number_of_bedrooms": {
"header": "",
@ -6840,7 +6861,9 @@
},
"net_income_value_check": {
"depends_on": [{ "net_income_soft_validation_triggered?": true }],
"title_text": { "translation": "soft_validations.net_income.title_text" },
"title_text": {
"translation": "soft_validations.net_income.title_text"
},
"informative_text": {
"translation": "soft_validations.net_income.hint_text",
"arguments": [

1
config/initializers/postcode_regex.rb

@ -0,0 +1 @@
POSTCODE_REGEXP = /^(([A-Z]{1,2}[0-9][A-Z0-9]?|ASCN|STHL|TDCU|BBND|[BFS]IQQ|PCRN|TKCA) ?[0-9][A-Z]{2}|BFPO ?[0-9]{1,4}|(KY[0-9]|MSR|VG|AI)[ -]?[0-9]{4}|[A-Z]{2} ?[0-9]{2}|GE ?CX|GIR ?0A{2}|SAN ?TA1)$/i

3
config/locales/en.yml

@ -43,8 +43,9 @@ en:
models:
scheme:
attributes:
organisation:
owning_organisation_id:
required: "Enter the existing organisation’s name"
invalid: "Enter the existing organisation’s name"
validations:
organisation:

5
config/routes.rb

@ -42,9 +42,12 @@ Rails.application.routes.draw do
get "support", to: "schemes#support"
get "details", to: "schemes#details"
get "check-answers", to: "schemes#check_answers"
get "edit-name", to: "schemes#edit_name"
member do
resources :locations
resources :locations do
get "edit-name", to: "locations#edit_name"
end
end
end

8
db/migrate/20220705130923_rename_scheme_organisations.rb

@ -0,0 +1,8 @@
class RenameSchemeOrganisations < ActiveRecord::Migration[7.0]
def change
change_table :schemes, bulk: true do |t|
t.rename :organisation_id, :owning_organisation_id
t.rename :stock_owning_organisation_id, :managing_organisation_id
end
end
end

7
db/migrate/20220706104313_add_unit_type_sh.rb

@ -0,0 +1,7 @@
class AddUnitTypeSh < ActiveRecord::Migration[7.0]
def change
change_table :case_logs, bulk: true do |t|
t.integer :unittype_sh
end
end
end

21
db/schema.rb

@ -10,7 +10,11 @@
#
# It's strongly recommended that you check this file into your version control system.
<<<<<<< HEAD
ActiveRecord::Schema[7.0].define(version: 2022_07_07_133909) do
=======
ActiveRecord::Schema[7.0].define(version: 2022_07_06_104313) do
>>>>>>> 34b57577d7a407bdbb6d5b17a0883c984d66ed78
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -200,7 +204,11 @@ ActiveRecord::Schema[7.0].define(version: 2022_07_07_133909) do
t.integer "vacdays"
t.bigint "scheme_id"
t.bigint "location_id"
<<<<<<< HEAD
t.bigint "organisations_id"
=======
t.integer "unittype_sh"
>>>>>>> 34b57577d7a407bdbb6d5b17a0883c984d66ed78
t.index ["created_by_id"], name: "index_case_logs_on_created_by_id"
t.index ["location_id"], name: "index_case_logs_on_location_id"
t.index ["managing_organisation_id"], name: "index_case_logs_on_managing_organisation_id"
@ -302,7 +310,7 @@ ActiveRecord::Schema[7.0].define(version: 2022_07_07_133909) do
create_table "schemes", force: :cascade do |t|
t.string "service_name"
t.bigint "organisation_id", null: false
t.bigint "owning_organisation_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "primary_client_group"
@ -314,9 +322,9 @@ ActiveRecord::Schema[7.0].define(version: 2022_07_07_133909) do
t.string "intended_stay"
t.datetime "end_date"
t.integer "has_other_client_group"
t.bigint "stock_owning_organisation_id"
t.index ["organisation_id"], name: "index_schemes_on_organisation_id"
t.index ["stock_owning_organisation_id"], name: "index_schemes_on_stock_owning_organisation_id"
t.bigint "managing_organisation_id"
t.index ["managing_organisation_id"], name: "index_schemes_on_managing_organisation_id"
t.index ["owning_organisation_id"], name: "index_schemes_on_owning_organisation_id"
end
create_table "users", force: :cascade do |t|
@ -378,7 +386,12 @@ ActiveRecord::Schema[7.0].define(version: 2022_07_07_133909) do
add_foreign_key "case_logs", "organisations", column: "organisations_id"
add_foreign_key "case_logs", "schemes"
add_foreign_key "locations", "schemes"
<<<<<<< HEAD
add_foreign_key "schemes", "organisations"
add_foreign_key "schemes", "organisations", column: "stock_owning_organisation_id"
add_foreign_key "users", "organisations", column: "organisations_id"
=======
add_foreign_key "schemes", "organisations", column: "managing_organisation_id"
add_foreign_key "schemes", "organisations", column: "owning_organisation_id"
>>>>>>> 34b57577d7a407bdbb6d5b17a0883c984d66ed78
end

8
db/seeds.rb

@ -79,7 +79,7 @@ unless Rails.env.test?
intended_stay: "M",
primary_client_group: "O",
secondary_client_group: "H",
organisation: org,
owning_organisation: org,
created_at: Time.zone.now,
)
@ -92,7 +92,7 @@ unless Rails.env.test?
intended_stay: "S",
primary_client_group: "D",
secondary_client_group: "E",
organisation: org,
owning_organisation: org,
created_at: Time.zone.now,
)
@ -105,7 +105,7 @@ unless Rails.env.test?
intended_stay: "X",
primary_client_group: "G",
secondary_client_group: "R",
organisation: dummy_org,
owning_organisation: dummy_org,
created_at: Time.zone.now,
)
@ -115,7 +115,7 @@ unless Rails.env.test?
postcode: "CU193AA",
name: "Rectory Road",
type_of_unit: 4,
type_of_building: "Purpose-built",
type_of_building: "Purpose built",
county: "Mid Sussex",
wheelchair_adaptation: 0,
)

6
docs/developer_setup.md

@ -193,3 +193,9 @@ We recommend using [RBenv](https://github.com/rbenv/rbenv) to manage Ruby versio
If this is not needed you can run `docker-compose up` as normal
The Rails server will start on <http://localhost:8080>.
5. To run the test suite in docker:
```bash
docker-compose run --rm app /bin/bash -c ' RAILS_ENV=test rspec'
```

86
docs/form/form.md

@ -0,0 +1,86 @@
## Form Definition
The current system is built around a form definition written in JSON. At the top level every form will expect to have the following attributes:
- Form type: this is to define whether the form is a lettings form or a sales form. The questions will differ between the types.
- Start date: the start of the collection window for the form, this will usually be in April.
- End date: the end date of the collection window for the form, this will usually be in July, a year after the start date.
- Sections: the sections in the form, this block is where the bulk of the form definition will be.
An example of this might look like the following:
```JSON
{
"form_type": "lettings",
"start_date": "2021-04-01T00:00:00.000+01:00",
"end_date": "2022-07-01T00:00:00.000+01:00",
"sections": {
...
}
}
```
Note that the end date of one form will overlap the start date of another to allow for late submissions. This means that every year there will be a period of time in which two forms are running simultaneously.
### How is the form split up?
A summary of how the form is split up is as follows:
- A form is divided up into one or more sections.
- Each section can have one or more subsections.
- Each subsection can have one or more pages.
- Each page can have one or more questions.
More information about these form elements can be found in the following links:
- [Section](docs/form/section.md)
- [Subsection](docs/form/subsection.md)
- [Page](docs/form/page.md)
- [Question](docs/form/question.md)
### The Form Model, Views and Controller
Rails uses the Model, View, Controller (MVC) pattern which we follow.
#### The Form Model
There is no need to manually initialise a form object as this is handled by the FormHandler class at boot time. If a new form needs to be added then a JSON file containing the form definition should be added to `config/forms` where the FormHandler will be able to locate it and instantiate it.
A form has the following attributes:
- name: The name of the form
- setup_sections: The setup section (this is not defined in the JSON, for more information see this)
- form_definition: The parsed form JSON
- form_sections: The sections found within the form definition JSON
- type: The type of form (this is used to indicate if the form is for a sale or a letting)
- sections: The combination of the setup section with those found in the JSON definition
- subsections: The subsections of the form (these live under the sections)
- pages: The pages of the form (these live under the subsections)
- questions: The questions of the form (these live under the pages)
- start_date: The start date of the form, in iso8601 format
- end_date: The end date of the form, in iso8601 format
#### The Form Views
The main view used for rendering the form is the `app/views/form/page.html.erb` view as the Form contains multiple pages (which live in subsections within sections). This page view then renders the appropriate partials for the question types of the questions on the current page.
We currently have views for the following question types:
- Numerical
- Date
- Checkbox
- Radio
- Select
- Text
- Textarea
- Interruption screen
Interruption screen questions are radio questions used for soft validation of fields. They usually have yes and no options for a user to confirm a value is correct.
#### The Form Controller
The form controller handles the form submission as well as the rendering of the check answers page and the review page.
### The FormHandler helper class
The FormHandler helper is a helper that loads all of the defined forms and initialises them as Form objects. It can also be used to get specific forms if needed.

28
docs/form/page.md

@ -0,0 +1,28 @@
## Page
Pages are under the subsection level of the form definition. A example page might look something like this:
```JSON
"property_postcode": {
"header": "",
"description": "",
"questions": {
...
},
"depends_on": [
{
"needstype": 1
}
]
}
```
In the above example the the subsection has the id `property_postcode`. This id is used for the url of the web page, but the underscore is replaced with a hash, so the url for this page would be `[environment-url]/logs/[log-id]/property-postcode` e.g. on staging this url might look like the following: `https://dluhc-core-staging.london.cloudapps.digital/logs/1234/property-postcode`.
The header is optional but if provided is used for the heading displayed on the page.
The description is optional but if provided is used for a paragraph displayed under the page header.
It's worth noting that like subsections a page can also have a `depends_on` which contains the set of conditions that must be met for the section to be accessibile to a data provider. If the conditions are not met then the page is not routed to as part of the form flow. The `depends_on` for a page will usually depend on answers given to questions, most likely to be questions in the setup section. In the above example the page is dependent on the answer to the `needstype` question being `1`, which corresponds to picking `General needs` on that question as displayed to the data provider.
Pages can contain one or more questions.

82
docs/form/question.md

@ -0,0 +1,82 @@
## Question
Questions are under the page level of the form definition. A example question might look something like this:
```JSON
"postcode_known": {
"check_answer_label": "Do you know the property postcode?",
"header": "Do you know the property’s postcode?",
"hint_text": "",
"type": "radio",
"answer_options": {
"1": {
"value": "Yes"
},
"0": {
"value": "No"
}
},
"conditional_for": {
"postcode_full": [1]
},
"hidden_in_check_answers": true
}
```
In the above example the the question has the id `postcode_known`.
The `check_answer_label` contains the text that will be displayed in the label of the table on the check answers page.
The header is text that is displayed for the question.
Hint text is optional, but if provided it sits under the header and is normally given to provide the data inputters with guidance when answering the question, for example it might inform them about terms used in the question.
The type is question type, which is used to determine the view rendered for the question. In the above example the question is a radio type so the `app/views/form/_radio_question.html.erb` partial will be rendered on the page when this question is displayed to the user.
The `conditional_for` contains the value needed to be selected by the data inputter in order to display another question that appears on the same page. In the example above the `postcode_full` question depends on the answer to `postcode_known` being selected as `1` or `Yes`, this would then display the `postcode_full` underneath the `Yes` option on the page, allowing the provide the provide the postcode if they have indicated they know it. If the user has JavaScript enabled then this realtime conditional display is handled by the `app/frontend/controllers/conditional_question_controller.js` file.
the `hidden_in_check_answers` is used to hide a value from displaying on the check answers page. You only need to provide this if you want to set it to true in order to hide the value for some reason e.g. it's one of two questions appearing on a page and the other question is displayed on the check answers page. It's also worth noting that you can declare this as a with a `depends_on` which can be useful for conditionally displaying values on the check answers page. For example:
```JSON
"hidden_in_check_answers": {
"depends_on": [
{
"age6_known": 0
},
{
"age6_known": 1
}
]
}
```
Would mean the question the above is attached to would be hidden in the check answers page if the value of age6_known is either `0` or `1`.
The answer the data inputter provides to some questions allows us to infer the values of other questions we might have asked in the form, allowing us to save the data inputters some time. An example of how this might look is as follows:
```JSON
"postcode_full": {
"check_answer_label": "Postcode",
"header": "What is the property’s postcode?",
"hint_text": "",
"type": "text",
"width": 5,
"inferred_answers": {
"la": {
"is_la_inferred": true
}
},
"inferred_check_answers_value": {
"condition": {
"postcode_known": 0
},
"value": "Not known"
}
}
```
In the above example the width is an optional attribute and can be provided for text type questions to determine the width of the text box on the page when when the question is displayed to a user (this allows you to match the width of the text box on the page to that of the design for a question).
The above example links to the first example as both of these questions would be on the same page. The `inferred_check_answers_value` is what should be displayed on the check answers page for this question if we infer it. If the value of `postcode_known` was given as `0` (which is a no), as seen in the condition part of `inferred_check_answers_value` then we can infer that the data inputter does not know the postcode and so we would display the value of `Not known` on the check answers page for the postcode.
In the above example the `inferred_answers` refers to a question where we can infer the answer based on the answer of this question. In this case the `la` question can be inferred from the postcode value given by the data inputter as we are able to lookup the local authority based on the postcode given. We then set a property on the case log `is_la_inferred` to true to indicate that this is an answer we've inferred.

26
docs/form/section.md

@ -0,0 +1,26 @@
## Section
Sections are under the top level of the form definition. A example section might look something like this:
```JSON
"sections": {
"tenancy_and_property": {
"label": "Property and tenancy information",
"subsections": {
"property_information": {
...
},
"tenancy_information": {
...
}
}
},
...
}
```
In the above example the section id would be `tenancy_and_property` and its subsections would be `property_information` and `tenancy_information`.
The label contains the text that users will see for that section in the tasklist page of a case log.
Sections can contain one or more subsections.

28
docs/form/subsection.md

@ -0,0 +1,28 @@
## Subsection
Subsections are under the section level of the form definition. A example subsection might look something like this:
```JSON
"property_information": {
"label": "Property information",
"depends_on": [
{
"setup": "completed"
}
],
"pages": {
"property_postcode": {
...
},
"property_local_authority": {
...
}
}
}
```
In the above example the the subsection has the id `property_information`. The `depends_on` contains the set of conditions that must be met for the section to be accessibile to a data provider, in this example subsection depends on the completion of the setup section/subsection (note that this is a common condition as the answers provided to questions in the setup subsection often have an impact on what questions are asked of the data provider in later subsections of the form).
The label contains the text that users will see for that subsection in the tasklist page of a case log.
The pages of the subsection in the example would be `property_postcode` and `property_local_authority`. Subsections can contain one or more pages.

4
docs/form_builder.md

@ -181,6 +181,10 @@ rake form_definition:validate_all
This will validate all forms in directories `["config/forms", "spec/fixtures/forms"]`
## Form models and definition
For information about the form model and related models (section, subsection, page, question) and how these relate to each other follow [this link](docs/form/form.md)
## Improvements that could be made
- JSON schema definition could be expanded such that we can better automatically validate that a given config is valid and internally consistent

4
docs/form_runner.md

@ -19,3 +19,7 @@ ERB templates:
Routes for each form page are generated by looping over each Page instance in each Form instance held by the form handler and defining a `GET` path. The corresponding controller method is also auto-generated with meta-programming via the same looping in `app/controllers/form_controller.rb`
All form pages submit to the same controller method (`app/controllers/form_controller.rb#submit_form`) which validates and persists the data, and then redirects to the next form page that identifies as `routed_to` given the current case log state.
## Form models and definition
For information about the form model and related models (section, subsection, page, question) and how these relate to each other follow [this link](docs/form/form.md)

8
docs/schemes.md

@ -1,9 +1,7 @@
# Supported housing schemes
## Schemes
A supported housing scheme (or service) provides shared or self-contained housing for a particular client group, for example younger or vulnerable people. A scheme can be run at multiple locations, and a single location may contain multiple units (for example bedrooms in shared houses or a bungalow with 3 bedrooms).
Groups of similar properties in the same location, intended for similar tenants with the same type of support needs, managed in the same way. As some of the information we need about a new tenancy is the same for all new tenancies in the ‘scheme’, users can set up a ‘scheme’ in the CORE system by completing the information once. In Supported Housing forms, the user just supplies the appropriate scheme. This means providers do not have to complete identical information multiple times in each CORE form. Effectively we model these as templates or predefined answer sets.
Logs for supported housing will share a number of similar characteristics at this location. Additional data also needs to be collected specifically regarding the supported housing scheme, such as the type of client groups served and type of support provided.
## Management groups
Schemes are often managed together as part of a ‘management group’. An organisation may have multiple management groups, and each management group may have multiple schemes. For Supported Housing logs, users must select the management group first, then select scheme.
Asking these questions would require data inputters to re-enter the same information repeatedly and answer more questions than those asked for general needs lettings. Schemes exist in CORE to reduce this burden, and effectively act as predefined answer sets.

2
spec/factories/location.rb

@ -4,7 +4,7 @@ FactoryBot.define do
postcode { Faker::Address.postcode.delete(" ") }
name { Faker::Address.street_name }
type_of_unit { Faker::Number.within(range: 1..6) }
type_of_building { Faker::Lorem.word }
type_of_building { "Purpose built" }
wheelchair_adaptation { 0 }
county { Faker::Address.state }
scheme

2
spec/factories/scheme.rb

@ -8,7 +8,7 @@ FactoryBot.define do
intended_stay { %w[M P S V X].sample }
primary_client_group { %w[O H M L A G F B D E I S N R Q P X].sample }
secondary_client_group { %w[O H M L A G F B D E I S N R Q P X].sample }
organisation
owning_organisation { FactoryBot.create(:organisation) }
created_at { Time.zone.now }
end
end

2
spec/features/form/tasklist_page_spec.rb

@ -54,7 +54,7 @@ RSpec.describe "Task List" do
it "shows number of completed sections if one section is completed" do
visit("/logs/#{setup_completed_log.id}")
expect(page).to have_content("1 of 9 sections completed.")
expect(page).to have_content("1 of 8 sections completed.")
end
it "show skip link for next incomplete section" do

124
spec/features/schemes_spec.rb

@ -4,8 +4,8 @@ RSpec.describe "Schemes scheme Features" do
context "when viewing list of schemes" do
context "when I am signed as a coordinator user and there are schemes in the database" do
let!(:user) { FactoryBot.create(:user, :data_coordinator, last_sign_in_at: Time.zone.now) }
let!(:schemes) { FactoryBot.create_list(:scheme, 5, organisation: user.organisation) }
let!(:scheme_to_search) { FactoryBot.create(:scheme, organisation: user.organisation) }
let!(:schemes) { FactoryBot.create_list(:scheme, 5, owning_organisation: user.organisation) }
let!(:scheme_to_search) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
before do
visit("/logs")
@ -198,12 +198,12 @@ RSpec.describe "Schemes scheme Features" do
it "shows service and locations tab" do
expect(page).to have_link("Scheme")
expect(page).to have_link("#{scheme.locations.count} locations")
expect(page).to have_link("Locations")
end
context "when I click locations link" do
before do
click_link("#{scheme.locations.count} locations")
click_link("Locations")
end
it "shows details of those locations" do
@ -253,8 +253,8 @@ RSpec.describe "Schemes scheme Features" do
check "This scheme contains confidential information"
choose "Direct access hostel"
choose "Yes – registered care home providing nursing care"
select organisation.name, from: "scheme-organisation-id-field"
select organisation.name, from: "scheme-stock-owning-organisation-id-field"
select organisation.name, from: "scheme-managing-organisation-id-field"
select organisation.name, from: "scheme-owning-organisation-id-field"
click_button "Save and continue"
end
@ -652,9 +652,9 @@ RSpec.describe "Schemes scheme Features" do
expect(page).to have_content "Supported housing schemes"
expect(page).to have_content scheme.id_to_display
expect(page).to have_content scheme.service_name
expect(page).to have_content scheme.organisation.name
expect(page).to have_content scheme.stock_owning_organisation.name
expect(page).to have_content "#{scheme.organisation.name} has been created."
expect(page).to have_content scheme.owning_organisation.name
expect(page).to have_content scheme.managing_organisation.name
expect(page).to have_content "#{scheme.owning_organisation.name} has been created."
end
end
end
@ -665,5 +665,111 @@ RSpec.describe "Schemes scheme Features" do
end
end
end
context "when editing a scheme" do
context "when I visit schemes page" do
before do
visit("schemes")
end
it "shows list of links to schemes" do
schemes.each do |scheme|
expect(page).to have_link(scheme.service_name)
expect(page).to have_content(scheme.primary_client_group)
end
end
context "when I click to see individual scheme" do
let(:scheme) { schemes.first }
let!(:location) { FactoryBot.create(:location, scheme:) }
before do
click_link(scheme.service_name)
end
it "shows me details about the selected scheme" do
expect(page).to have_content(schemes.first.id_to_display)
expect(page).to have_content(schemes.first.service_name)
expect(page).to have_content(schemes.first.sensitive)
expect(page).to have_content(schemes.first.scheme_type)
expect(page).to have_content(schemes.first.registered_under_care_act)
expect(page).to have_content(schemes.first.primary_client_group)
expect(page).to have_content(schemes.first.secondary_client_group)
expect(page).to have_content(schemes.first.support_type)
expect(page).to have_content(schemes.first.intended_stay)
end
context "when I click to change scheme name" do
before do
click_link("Change", href: "/schemes/#{scheme.id}/edit-name", match: :first)
end
it "shows available fields to edit" do
expect(page).to have_current_path("/schemes/#{scheme.id}/edit-name")
expect(page).to have_content "Scheme details"
end
context "when I edit details" do
before do
fill_in "Scheme name", with: "FooBar"
check "This scheme contains confidential information"
click_button "Save changes"
end
it "lets me see amended details on the show page" do
expect(page).to have_content "FooBar"
expect(page).to have_current_path("/schemes/#{scheme.id}")
end
end
end
context "when I click to see locations" do
before do
click_link "Locations"
end
it "I see location details" do
expect(page).to have_content scheme.locations.first.id
expect(page).to have_current_path("/schemes/#{scheme.id}/locations")
end
context "when I click to change location name" do
before do
click_link(location.postcode)
end
it "shows available fields to edit" do
expect(page).to have_current_path("/schemes/#{scheme.id}/locations/#{location.id}/edit-name")
expect(page).to have_content "Location name for #{location.postcode}"
end
context "when I press the back button" do
before do
click_link "Back"
end
it "I see location details" do
expect(page).to have_content scheme.locations.first.id
expect(page).to have_current_path("/schemes/#{scheme.id}/locations")
end
end
context "and I change the location name" do
before do
fill_in "location-name-field", with: "NewName"
click_button "Save and continue"
end
it "returns to locations page and shows the new name" do
expect(page).to have_content location.id
expect(page).to have_content "NewName"
expect(page).to have_current_path("/schemes/#{scheme.id}/locations")
end
end
end
end
end
end
end
end
end

290
spec/fixtures/complete_case_log.json vendored

@ -1,138 +1,186 @@
{
"case_log": {
"tenancycode": "T657",
"age1": 35,
"sex1": "F",
"ethnic": 0,
"national": 0,
"prevten": 6,
"armedforces": 1,
"armed_forces_partner": "",
"tenancycode":"T1245",
"age1":34,
"sex1":"M",
"ethnic":1,
"national":1,
"prevten":3,
"ecstat1":1,
"hhmemb": 8,
"relat2": "P",
"age2": 32,
"sex2": "M",
"ecstat2": 6,
"relat3": "C",
"age3": 12,
"sex3": "M",
"hhmemb":3,
"age2":29,
"sex2":"F",
"ecstat2":2,
"age3":11,
"sex3":"R",
"ecstat3":9,
"relat4": "C",
"age4": 12,
"sex4": "F",
"ecstat4": 9,
"relat5": "C",
"age5": 10,
"sex5": "X",
"ecstat5": 9,
"relat6": "C",
"age6": 5,
"sex6": "R",
"ecstat6": 9,
"age7": 5,
"sex7": "R",
"ecstat7": 9,
"relat8": "C",
"age8": 2,
"sex8": "R",
"ecstat8": 9,
"homeless": 2,
"reason": 1,
"underoccupation_benefitcap": 0,
"leftreg": 1,
"reservist": 0,
"illness": 1,
"preg_occ": 1,
"startdate": "12/12/2021",
"startertenancy": 0,
"tenancylength": 5,
"tenancy": 1,
"landlord": 1,
"previous_la_known": 1,
"la": "Barnet",
"postcode_full": "NW1 5TY",
"property_relet": 0,
"rsnvac": 14,
"property_reference": "P9876",
"age4":null,
"sex4":null,
"ecstat4":null,
"age5":null,
"sex5":null,
"ecstat5":null,
"age6":null,
"sex6":null,
"ecstat6":null,
"age7":null,
"sex7":null,
"ecstat7":null,
"age8":null,
"sex8":null,
"ecstat8":null,
"homeless":1,
"underoccupation_benefitcap":2,
"leftreg":null,
"reservist":null,
"illness":2,
"preg_occ":2,
"startertenancy":2,
"tenancylength":null,
"tenancy":2,
"ppostcode_full":"NW18TR",
"rsnvac":5,
"unittype_gn":7,
"property_building_type": "dummy",
"beds": 3,
"voiddate": "10/10/2020",
"majorrepairs": 1,
"mrcdate": "11/11/2020",
"offered": 2,
"beds":2,
"offered":0,
"wchair":1,
"net_income_known": 1,
"earnings": 150,
"earnings":190,
"incfreq":1,
"benefits": 1,
"hb": 1,
"period": 2,
"brent": 200,
"scharge": 50,
"pscharge": 40,
"supcharg": 35,
"tcharge": 325,
"outstanding_amount": 1,
"layear": 2,
"lawaitlist": 1,
"prevloc": "E07000105",
"ppostcode_full": "SE2 6RT",
"reasonpref": 1,
"cbl": 0,
"chr": 1,
"benefits":3,
"period":3,
"layear":7,
"waityear":2,
"postcode_full":"NW18EE",
"reasonpref":2,
"cbl":1,
"chr":0,
"cap":0,
"hbrentshortfall": 1,
"tshortfall": 12,
"reasonother": null,
"housingneeds_a": 1,
"reasonother":"",
"housingneeds_a":0,
"housingneeds_b":0,
"housingneeds_c": 0,
"housingneeds_c":1,
"housingneeds_f":0,
"housingneeds_g":0,
"housingneeds_h":0,
"accessibility_requirements_prefer_not_to_say": 0,
"illness_type_1": 0,
"illness_type_2": 1,
"illness_type_3": 0,
"illness_type_4": 0,
"illness_type_8": 0,
"illness_type_5": 0,
"illness_type_6": 0,
"illness_type_7": 0,
"illness_type_9": 0,
"illness_type_10": 0,
"condition_effects_prefer_not_to_say": 1,
"rp_homeless": 0,
"rp_insan_unsat": 0,
"rp_medwel": 0,
"rp_hardship": 0,
"rp_dontknow": 0,
"discarded_at": "05/05/2020",
"net_income_value_check": 0,
"property_owner_organisation": "",
"property_manager_organisation": "",
"rent_type": 0,
"intermediate_rent_product_name": "",
"needstype": 1,
"sale_completion_date": "01/01/2020",
"purchaser_code": "",
"propcode": "123",
"postcode": "a1",
"postcod2": "w3",
"illness_type_1":null,
"illness_type_2":null,
"illness_type_3":null,
"illness_type_4":null,
"illness_type_8":null,
"illness_type_5":null,
"illness_type_6":null,
"illness_type_7":null,
"illness_type_9":null,
"illness_type_10":null,
"rp_homeless":null,
"rp_insan_unsat":null,
"rp_medwel":null,
"rp_hardship":null,
"rp_dontknow":null,
"tenancyother":"",
"net_income_value_check":null,
"property_owner_organisation":null,
"property_manager_organisation":null,
"sale_or_letting":null,
"irproduct_other":"",
"purchaser_code":null,
"reason":42,
"propcode":"PT562",
"majorrepairs":1,
"la":"E09000007",
"prevloc":"E09000007",
"hb":9,
"hbrentshortfall":null,
"property_relet":null,
"mrcdate":"2021-05-07T00:00:00.000+01:00",
"incref":null,
"sale_completion_date":null,
"startdate":"2021-06-06T00:00:00.000+01:00",
"armedforces":2,
"first_time_property_let_as_social_housing":0,
"unitletas":1,
"builtype": 0,
"property_wheelchair_accessible": 1,
"void_or_renewal_date": "05/05/2020",
"renewal": 0,
"new_build_handover_date": "01/01/2019",
"has_benefits": 1,
"household_charge": 0,
"is_carehome": 0,
"sheltered": 0,
"builtype":1,
"voiddate":"2021-05-05T00:00:00.000+01:00",
"owning_organisation_id":1,
"managing_organisation_id":1,
"renttype":2,
"needstype":1,
"lettype":7,
"postcode_known":1,
"is_la_inferred":true,
"totchild":1,
"totelder":0,
"totadult":2,
"net_income_known":0,
"nocharge":0,
"is_carehome":null,
"household_charge":null,
"referral":2,
"brent":"350.0",
"scharge":"11.0",
"pscharge":"11.0",
"supcharg":"0.0",
"tcharge":"372.0",
"tshortfall":null,
"chcharge":null,
"declaration":1,
"referral": 1
"ppcodenk":1,
"previous_la_known":null,
"is_previous_la_inferred":true,
"age1_known":0,
"age2_known":0,
"age3_known":0,
"age4_known":null,
"age5_known":null,
"age6_known":null,
"age7_known":null,
"age8_known":null,
"ethnic_group":0,
"ethnic_other":null,
"letting_allocation_unknown":0,
"details_known_2":0,
"details_known_3":0,
"details_known_4":null,
"details_known_5":null,
"details_known_6":null,
"details_known_7":null,
"details_known_8":null,
"rent_type":1,
"has_benefits":0,
"renewal":0,
"wrent":"87.5",
"wscharge":"2.75",
"wpschrge":"2.75",
"wsupchrg":"0.0",
"wtcharge":"93.0",
"wtshortfall":null,
"refused":1,
"housingneeds":2,
"wchchrg":null,
"newprop":2,
"relat2":"P",
"relat3":"C",
"relat4":null,
"relat5":null,
"relat6":null,
"relat7":null,
"relat8":null,
"rent_value_check":null,
"old_form_id":null,
"lar":null,
"irproduct":null,
"old_id":null,
"joint":null,
"created_by_id":2,
"illness_type_0":null,
"retirement_value_check":null,
"tshortfall_known":null,
"sheltered":null,
"pregnancy_value_check":null,
"hhtype":6,
"new_old":1,
"vacdays":30,
"scheme_id":null,
"location_id":null
}
}

4
spec/fixtures/exports/case_logs.csv vendored

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

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

1
spec/fixtures/exports/case_logs.xml vendored

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

8
spec/helpers/navigation_items_helper_spec.rb

@ -190,13 +190,13 @@ RSpec.describe NavigationItemsHelper do
let(:expected_scheme_items) do
[
NavigationItemsHelper::NavigationItem.new("Scheme", "/schemes/1", true),
NavigationItemsHelper::NavigationItem.new("1 location", "/schemes/1/locations", false),
NavigationItemsHelper::NavigationItem.new("Locations", "/schemes/1/locations", false),
]
end
it "returns navigation items with Schemes item set as current" do
expect(primary_items("/schemes/1", current_user)).to eq(expected_navigation_items)
expect(scheme_items("/schemes/1", 1, "1 location")).to eq(expected_scheme_items)
expect(scheme_items("/schemes/1", 1, "Locations")).to eq(expected_scheme_items)
end
end
@ -213,13 +213,13 @@ RSpec.describe NavigationItemsHelper do
let(:expected_scheme_items) do
[
NavigationItemsHelper::NavigationItem.new("Scheme", "/schemes/1", false),
NavigationItemsHelper::NavigationItem.new("1 location", "/schemes/1/locations", true),
NavigationItemsHelper::NavigationItem.new("Locations", "/schemes/1/locations", true),
]
end
it "returns navigation items with Schemes item set as current" do
expect(primary_items("/schemes/1/locations", current_user)).to eq(expected_navigation_items)
expect(scheme_items("/schemes/1/locations", 1, "1 location")).to eq(expected_scheme_items)
expect(scheme_items("/schemes/1/locations", 1, "Locations")).to eq(expected_scheme_items)
end
end

3
spec/helpers/tab_nav_helper_spec.rb

@ -22,8 +22,9 @@ RSpec.describe TabNavHelper do
describe "#location_cell" do
it "returns the location link to the postcode with optional name" do
link = "/schemes/#{location.scheme.id}/locations/#{location.id}/edit"
expected_html = "<a class=\"govuk-link\" rel=\"nofollow\" data-method=\"patch\" href=\"/schemes/#{scheme.id}/locations/#{location.id}/edit\">#{location.postcode}</a>\n<span class=\"govuk-visually-hidden\">Location </span><span class=\"govuk-!-font-weight-regular app-!-colour-muted\">#{location.name}</span>"
expect(location_cell(location)).to match(expected_html)
expect(location_cell(location, link)).to match(expected_html)
end
end

2
spec/helpers/tasklist_helper_spec.rb

@ -17,7 +17,7 @@ RSpec.describe TasklistHelper do
describe "get sections count" do
it "returns the total of sections if no status is given" do
expect(get_subsections_count(empty_case_log)).to eq(9)
expect(get_subsections_count(empty_case_log)).to eq(8)
end
it "returns 0 sections for completed sections if no sections are completed" do

72
spec/models/case_log_spec.rb

@ -1680,7 +1680,12 @@ RSpec.describe CaseLog do
end
context "when a case log is a supported housing log" do
before { case_log.needstype = 2 }
let(:real_2021_2022_form) { Form.new("config/forms/2021_2022.json", "2021_2022") }
before do
case_log.needstype = 2
allow(FormHandler.instance).to receive(:get_form).and_return(real_2021_2022_form)
end
context "and a scheme with a single log is selected" do
let(:scheme) { FactoryBot.create(:scheme) }
@ -1694,6 +1699,71 @@ RSpec.describe CaseLog do
expect(case_log["location_id"]).to eq(location.id)
end
end
context "and not renewal" do
let(:scheme) { FactoryBot.create(:scheme) }
let(:location) { FactoryBot.create(:location, scheme:, county: "E07000041", type_of_unit: 1, type_of_building: "Purpose built", wheelchair_adaptation: 0) }
let!(:supported_housing_case_log) do
described_class.create!({
managing_organisation: owning_organisation,
owning_organisation:,
created_by: created_by_user,
needstype: 2,
scheme_id: scheme.id,
location_id: location.id,
renewal: 0,
})
end
it "correctly infers and saves la" do
record_from_db = ActiveRecord::Base.connection.execute("SELECT la from case_logs WHERE id=#{supported_housing_case_log.id}").to_a[0]
expect(record_from_db["la"]).to eq(location.county)
end
it "correctly infers and saves postcode" do
record_from_db = ActiveRecord::Base.connection.execute("SELECT postcode_full from case_logs WHERE id=#{supported_housing_case_log.id}").to_a[0]
expect(record_from_db["postcode_full"]).to eq(location.postcode)
end
it "correctly infers and saves type of unit" do
record_from_db = ActiveRecord::Base.connection.execute("SELECT unittype_sh from case_logs WHERE id=#{supported_housing_case_log.id}").to_a[0]
expect(record_from_db["unittype_sh"]).to eq(1)
end
it "correctly infers and saves type of building" do
record_from_db = ActiveRecord::Base.connection.execute("SELECT builtype from case_logs WHERE id=#{supported_housing_case_log.id}").to_a[0]
expect(record_from_db["builtype"]).to eq(1)
end
it "correctly infers and saves wchair" do
record_from_db = ActiveRecord::Base.connection.execute("SELECT wchair from case_logs WHERE id=#{supported_housing_case_log.id}").to_a[0]
expect(record_from_db["wchair"]).to eq(2)
end
end
context "and renewal" do
let(:scheme) { FactoryBot.create(:scheme) }
let(:location) { FactoryBot.create(:location, scheme:) }
let!(:supported_housing_case_log) do
described_class.create!({
managing_organisation: owning_organisation,
owning_organisation:,
created_by: created_by_user,
needstype: 2,
scheme_id: scheme.id,
location_id: location.id,
renewal: 1,
startdate: Time.zone.now,
})
end
it "correcly infers and saves the renewal date" do
record_from_db = ActiveRecord::Base.connection.execute("SELECT voiddate from case_logs where id=#{supported_housing_case_log.id}").to_a[0]
expect(record_from_db["voiddate"].to_i).to eq(supported_housing_case_log.startdate.to_i)
end
end
end
end

6
spec/models/form/setup/questions/scheme_id_spec.rb

@ -42,12 +42,12 @@ RSpec.describe Form::Setup::Questions::SchemeId, type: :model do
context "when a user is signed in" do
let(:organisation) { FactoryBot.create(:organisation) }
let(:organisation_2) { FactoryBot.create(:organisation) }
let(:user) { FactoryBot.create(:user, organisation_id: organisation.id) }
let(:scheme) { FactoryBot.create(:scheme, organisation_id: organisation.id) }
let(:user) { FactoryBot.create(:user, organisation:) }
let(:scheme) { FactoryBot.create(:scheme, owning_organisation: organisation) }
let(:case_log) { FactoryBot.create(:case_log, created_by: user) }
before do
FactoryBot.create(:scheme, organisation_id: organisation_2.id)
FactoryBot.create(:scheme, owning_organisation: organisation_2)
end
it "has the correct answer_options based on the schemes the user's organisation owns or manages" do

2
spec/models/organisation_spec.rb

@ -4,7 +4,7 @@ RSpec.describe Organisation, type: :model do
describe "#new" do
let(:user) { FactoryBot.create(:user) }
let!(:organisation) { user.organisation }
let!(:scheme) { FactoryBot.create(:scheme, organisation:, stock_owning_organisation: organisation) }
let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: organisation, managing_organisation: organisation) }
it "has expected fields" do
expect(organisation.attribute_names).to include("name", "phone", "provider_type")

2
spec/models/scheme_spec.rb

@ -5,7 +5,7 @@ RSpec.describe Scheme, type: :model do
let(:scheme) { FactoryBot.create(:scheme) }
it "belongs to an organisation" do
expect(scheme.organisation).to be_a(Organisation)
expect(scheme.owning_organisation).to be_a(Organisation)
end
describe "scopes" do

152
spec/requests/locations_controller_spec.rb

@ -3,7 +3,7 @@ require "rails_helper"
RSpec.describe LocationsController, type: :request do
let(:page) { Capybara::Node::Simple.new(response.body) }
let(:user) { FactoryBot.create(:user, :support) }
let!(:scheme) { FactoryBot.create(:scheme, organisation: user.organisation) }
let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
describe "#new" do
context "when not signed in" do
@ -29,7 +29,7 @@ RSpec.describe LocationsController, type: :request do
context "when signed in as a data coordinator" do
let(:user) { FactoryBot.create(:user, :data_coordinator) }
let!(:scheme) { FactoryBot.create(:scheme, organisation: user.organisation) }
let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
before do
sign_in user
@ -89,7 +89,7 @@ RSpec.describe LocationsController, type: :request do
context "when signed in as a data coordinator" do
let(:user) { FactoryBot.create(:user, :data_coordinator) }
let!(:scheme) { FactoryBot.create(:scheme, organisation: user.organisation) }
let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ" } } }
before do
@ -105,7 +105,7 @@ RSpec.describe LocationsController, type: :request do
end
it "creates a new location for scheme with valid params" do
expect(Location.last.scheme.organisation_id).to eq(user.organisation_id)
expect(Location.last.scheme.owning_organisation_id).to eq(user.organisation_id)
expect(Location.last.name).to eq("Test")
expect(Location.last.postcode).to eq("ZZ11ZZ")
expect(Location.last.total_units).to eq(5)
@ -151,7 +151,7 @@ RSpec.describe LocationsController, type: :request do
end
it "creates a new location for scheme with valid params" do
expect(Location.last.scheme.organisation_id).to eq(user.organisation_id)
expect(Location.last.scheme.owning_organisation_id).to eq(user.organisation_id)
expect(Location.last.name).to eq("Test")
expect(Location.last.total_units).to eq(5)
expect(Location.last.type_of_unit).to eq("Bungalow")
@ -170,7 +170,7 @@ RSpec.describe LocationsController, type: :request do
end
it "creates a new location for scheme with valid params" do
expect(Location.last.scheme.organisation_id).to eq(user.organisation_id)
expect(Location.last.scheme.owning_organisation_id).to eq(user.organisation_id)
expect(Location.last.name).to eq("Test")
expect(Location.last.total_units).to eq(5)
expect(Location.last.type_of_unit).to eq("Bungalow")
@ -189,7 +189,7 @@ RSpec.describe LocationsController, type: :request do
end
it "creates a new location for scheme with valid params" do
expect(Location.last.scheme.organisation_id).to eq(user.organisation_id)
expect(Location.last.scheme.owning_organisation_id).to eq(user.organisation_id)
expect(Location.last.name).to eq("Test")
expect(Location.last.total_units).to eq(5)
expect(Location.last.type_of_unit).to eq("Bungalow")
@ -322,7 +322,7 @@ RSpec.describe LocationsController, type: :request do
context "when signed in as a data coordinator" do
let(:user) { FactoryBot.create(:user, :data_coordinator) }
let!(:scheme) { FactoryBot.create(:scheme, organisation: user.organisation) }
let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let!(:location) { FactoryBot.create(:location, scheme:) }
before do
@ -348,7 +348,7 @@ RSpec.describe LocationsController, type: :request do
context "when signed in as a support user" do
let(:user) { FactoryBot.create(:user, :support) }
let!(:scheme) { FactoryBot.create(:scheme, organisation: user.organisation) }
let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let!(:location) { FactoryBot.create(:location, scheme:) }
before do
@ -388,9 +388,9 @@ RSpec.describe LocationsController, type: :request do
context "when signed in as a data coordinator" do
let(:user) { FactoryBot.create(:user, :data_coordinator) }
let!(:scheme) { FactoryBot.create(:scheme, organisation: user.organisation) }
let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let!(:location) { FactoryBot.create(:location, scheme:) }
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ" } } }
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ", page: "edit" } } }
before do
sign_in user
@ -404,7 +404,7 @@ RSpec.describe LocationsController, type: :request do
end
it "updates existing location for scheme with valid params" do
expect(Location.last.scheme.organisation_id).to eq(user.organisation_id)
expect(Location.last.scheme.owning_organisation_id).to eq(user.organisation_id)
expect(Location.last.name).to eq("Test")
expect(Location.last.postcode).to eq("ZZ11ZZ")
expect(Location.last.total_units).to eq(5)
@ -412,8 +412,22 @@ RSpec.describe LocationsController, type: :request do
expect(Location.last.wheelchair_adaptation).to eq("No")
end
context "when updating from edit-name page" do
let(:params) { { location: { name: "Test", page: "edit-name" } } }
it "updates existing location for scheme with valid params and redirects to correct page" do
follow_redirect!
expect(response).to have_http_status(:ok)
expect(page).to have_content("Locations")
end
it "updates existing location for scheme with valid params" do
expect(Location.last.name).to eq("Test")
end
end
context "when postcode is submitted with lower case" do
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "zz1 1zz" } } }
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "zz1 1zz", page: "edit" } } }
it "updates existing location for scheme with postcode " do
expect(Location.last.postcode).to eq("ZZ11ZZ")
@ -423,7 +437,7 @@ RSpec.describe LocationsController, type: :request do
context "when trying to update location for a scheme that belongs to another organisation" do
let(:another_scheme) { FactoryBot.create(:scheme) }
let(:another_location) { FactoryBot.create(:location) }
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ" } } }
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ", page: "edit" } } }
it "displays the new page with an error message" do
patch "/schemes/#{another_scheme.id}/locations/#{another_location.id}", params: params
@ -432,7 +446,7 @@ RSpec.describe LocationsController, type: :request do
end
context "when required postcode param is invalid" do
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "invalid" } } }
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "invalid", page: "edit" } } }
it "displays the new page with an error message" do
expect(response).to have_http_status(:unprocessable_entity)
@ -441,7 +455,7 @@ RSpec.describe LocationsController, type: :request do
end
context "when do you want to add another location is selected as yes" do
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "Yes", postcode: "ZZ1 1ZZ" } } }
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "Yes", postcode: "ZZ1 1ZZ", page: "edit" } } }
it "updates existing location for scheme with valid params and redirects to correct page" do
follow_redirect!
@ -450,7 +464,7 @@ RSpec.describe LocationsController, type: :request do
end
it "updates existing location for scheme with valid params" do
expect(Location.last.scheme.organisation_id).to eq(user.organisation_id)
expect(Location.last.scheme.owning_organisation_id).to eq(user.organisation_id)
expect(Location.last.name).to eq("Test")
expect(Location.last.total_units).to eq(5)
expect(Location.last.type_of_unit).to eq("Bungalow")
@ -459,7 +473,7 @@ RSpec.describe LocationsController, type: :request do
end
context "when do you want to add another location is selected as no" do
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ" } } }
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ", page: "edit" } } }
it "updates existing location for scheme with valid params and redirects to correct page" do
follow_redirect!
@ -468,7 +482,7 @@ RSpec.describe LocationsController, type: :request do
end
it "updates existing location for scheme with valid params" do
expect(Location.last.scheme.organisation_id).to eq(user.organisation_id)
expect(Location.last.scheme.owning_organisation_id).to eq(user.organisation_id)
expect(Location.last.name).to eq("Test")
expect(Location.last.total_units).to eq(5)
expect(Location.last.type_of_unit).to eq("Bungalow")
@ -477,7 +491,7 @@ RSpec.describe LocationsController, type: :request do
end
context "when do you want to add another location is not selected" do
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", postcode: "ZZ1 1ZZ" } } }
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", postcode: "ZZ1 1ZZ", page: "edit" } } }
it "updates existing location for scheme with valid params and redirects to correct page" do
follow_redirect!
@ -486,7 +500,7 @@ RSpec.describe LocationsController, type: :request do
end
it "updates existing location for scheme with valid params" do
expect(Location.last.scheme.organisation_id).to eq(user.organisation_id)
expect(Location.last.scheme.owning_organisation_id).to eq(user.organisation_id)
expect(Location.last.name).to eq("Test")
expect(Location.last.total_units).to eq(5)
expect(Location.last.type_of_unit).to eq("Bungalow")
@ -497,9 +511,9 @@ RSpec.describe LocationsController, type: :request do
context "when signed in as a support user" do
let(:user) { FactoryBot.create(:user, :data_coordinator) }
let!(:scheme) { FactoryBot.create(:scheme, organisation: user.organisation) }
let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let!(:location) { FactoryBot.create(:location, scheme:) }
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ" } } }
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ", page: "edit" } } }
before do
allow(user).to receive(:need_two_factor_authentication?).and_return(false)
@ -521,8 +535,22 @@ RSpec.describe LocationsController, type: :request do
expect(Location.last.wheelchair_adaptation).to eq("No")
end
context "when updating from edit-name page" do
let(:params) { { location: { name: "Test", page: "edit-name" } } }
it "updates existing location for scheme with valid params and redirects to correct page" do
follow_redirect!
expect(response).to have_http_status(:ok)
expect(page).to have_content("Locations")
end
it "updates existing location for scheme with valid params" do
expect(Location.last.name).to eq("Test")
end
end
context "when postcode is submitted with lower case" do
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "zz1 1zz" } } }
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "zz1 1zz", page: "edit" } } }
it "updates a location for scheme with postcode " do
expect(Location.last.postcode).to eq("ZZ11ZZ")
@ -530,7 +558,7 @@ RSpec.describe LocationsController, type: :request do
end
context "when required postcode param is missing" do
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "invalid" } } }
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "invalid", page: "edit" } } }
it "displays the new page with an error message" do
expect(response).to have_http_status(:unprocessable_entity)
@ -539,7 +567,7 @@ RSpec.describe LocationsController, type: :request do
end
context "when do you want to add another location is selected as yes" do
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "Yes", postcode: "ZZ1 1ZZ" } } }
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "Yes", postcode: "ZZ1 1ZZ", page: "edit" } } }
it "updates location for scheme with valid params and redirects to correct page" do
follow_redirect!
@ -556,7 +584,7 @@ RSpec.describe LocationsController, type: :request do
end
context "when do you want to add another location is selected as no" do
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ" } } }
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ", page: "edit" } } }
it "updates a location for scheme with valid params and redirects to correct page" do
follow_redirect!
@ -573,7 +601,7 @@ RSpec.describe LocationsController, type: :request do
end
context "when do you want to add another location is not selected" do
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", postcode: "ZZ1 1ZZ" } } }
let(:params) { { location: { name: "Test", total_units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", postcode: "ZZ1 1ZZ", page: "edit" } } }
it "updates a location for scheme with valid params and redirects to correct page" do
follow_redirect!
@ -615,7 +643,7 @@ RSpec.describe LocationsController, type: :request do
context "when signed in as a data coordinator user" do
let(:user) { FactoryBot.create(:user, :data_coordinator) }
let!(:scheme) { FactoryBot.create(:scheme, organisation: user.organisation) }
let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let!(:locations) { FactoryBot.create_list(:location, 3, scheme:) }
before do
@ -781,4 +809,70 @@ RSpec.describe LocationsController, type: :request do
end
end
end
describe "#edit-name" do
context "when not signed in" do
it "redirects to the sign in page" do
get "/schemes/1/locations/1/edit-name"
expect(response).to redirect_to("/account/sign-in")
end
end
context "when signed in as a data provider" do
let(:user) { FactoryBot.create(:user) }
before do
sign_in user
get "/schemes/1/locations/1/edit-name"
end
it "returns 401 unauthorized" do
request
expect(response).to have_http_status(:unauthorized)
end
end
context "when signed in as a data coordinator" do
let(:user) { FactoryBot.create(:user, :data_coordinator) }
let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let!(:location) { FactoryBot.create(:location, scheme:) }
before do
sign_in user
get "/schemes/#{scheme.id}/locations/#{location.id}/edit-name"
end
it "returns a template for a edit-name" do
expect(response).to have_http_status(:ok)
expect(page).to have_content("Location name for #{location.postcode}")
end
context "when trying to edit location name of location that belongs to another organisation" do
let(:another_scheme) { FactoryBot.create(:scheme) }
let(:another_location) { FactoryBot.create(:location, scheme: another_scheme) }
it "displays the new page with an error message" do
get "/schemes/#{another_scheme.id}/locations/#{another_location.id}/edit-name"
expect(response).to have_http_status(:not_found)
end
end
end
context "when signed in as a support user" do
let(:user) { FactoryBot.create(:user, :support) }
let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let!(:location) { FactoryBot.create(:location, scheme:) }
before do
allow(user).to receive(:need_two_factor_authentication?).and_return(false)
sign_in user
get "/schemes/#{scheme.id}/locations/#{location.id}/edit-name"
end
it "returns a template for a new location" do
expect(response).to have_http_status(:ok)
expect(page).to have_content("Location name for #{location.postcode}")
end
end
end
end

8
spec/requests/organisations_controller_spec.rb

@ -43,7 +43,7 @@ RSpec.describe OrganisationsController, type: :request do
context "when support user" do
let(:user) { FactoryBot.create(:user, :support) }
let!(:schemes) { FactoryBot.create_list(:scheme, 5) }
let!(:same_org_scheme) { FactoryBot.create(:scheme, organisation: user.organisation) }
let!(:same_org_scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
before do
allow(user).to receive(:need_two_factor_authentication?).and_return(false)
@ -72,7 +72,7 @@ RSpec.describe OrganisationsController, type: :request do
end
context "when searching" do
let!(:searched_scheme) { FactoryBot.create(:scheme, organisation: user.organisation) }
let!(:searched_scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let(:search_param) { searched_scheme.id }
before do
@ -101,7 +101,7 @@ RSpec.describe OrganisationsController, type: :request do
context "when data coordinator user" do
let(:user) { FactoryBot.create(:user, :data_coordinator) }
let!(:schemes) { FactoryBot.create_list(:scheme, 5) }
let!(:same_org_scheme) { FactoryBot.create(:scheme, organisation: user.organisation) }
let!(:same_org_scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
before do
sign_in user
@ -141,7 +141,7 @@ RSpec.describe OrganisationsController, type: :request do
end
context "when searching" do
let!(:searched_scheme) { FactoryBot.create(:scheme, organisation: user.organisation) }
let!(:searched_scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let(:search_param) { searched_scheme.id_to_display }
before do

148
spec/requests/schemes_controller_spec.rb

@ -203,7 +203,7 @@ RSpec.describe SchemesController, type: :request do
context "when signed in as a data coordinator user" do
let(:user) { FactoryBot.create(:user, :data_coordinator) }
let!(:specific_scheme) { FactoryBot.create(:scheme, organisation: user.organisation) }
let!(:specific_scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
before do
sign_in user
@ -213,7 +213,6 @@ RSpec.describe SchemesController, type: :request do
get "/schemes/#{specific_scheme.id}"
expect(page).to have_content(specific_scheme.id_to_display)
expect(page).to have_content(specific_scheme.service_name)
expect(page).to have_content(specific_scheme.organisation.name)
expect(page).to have_content(specific_scheme.sensitive)
expect(page).to have_content(specific_scheme.id_to_display)
expect(page).to have_content(specific_scheme.service_name)
@ -246,7 +245,7 @@ RSpec.describe SchemesController, type: :request do
it "has page heading" do
expect(page).to have_content(specific_scheme.id_to_display)
expect(page).to have_content(specific_scheme.service_name)
expect(page).to have_content(specific_scheme.organisation.name)
expect(page).to have_content(specific_scheme.owning_organisation.name)
expect(page).to have_content(specific_scheme.sensitive)
expect(page).to have_content(specific_scheme.id_to_display)
expect(page).to have_content(specific_scheme.service_name)
@ -352,7 +351,7 @@ RSpec.describe SchemesController, type: :request do
it "creates a new scheme for user organisation with valid params" do
post "/schemes", params: params
expect(Scheme.last.organisation_id).to eq(user.organisation_id)
expect(Scheme.last.owning_organisation_id).to eq(user.organisation_id)
expect(Scheme.last.service_name).to eq("testy")
expect(Scheme.last.scheme_type).to eq("Foyer")
expect(Scheme.last.sensitive).to eq("Yes")
@ -370,7 +369,7 @@ RSpec.describe SchemesController, type: :request do
context "when signed in as a support user" do
let(:organisation) { FactoryBot.create(:organisation) }
let(:user) { FactoryBot.create(:user, :support) }
let(:params) { { scheme: { service_name: "testy", sensitive: "1", scheme_type: "Foyer", registered_under_care_act: "No", organisation_id: organisation.id } } }
let(:params) { { scheme: { service_name: "testy", sensitive: "1", scheme_type: "Foyer", registered_under_care_act: "No", owning_organisation_id: organisation.id } } }
before do
allow(user).to receive(:need_two_factor_authentication?).and_return(false)
@ -386,7 +385,7 @@ RSpec.describe SchemesController, type: :request do
it "creates a new scheme for user organisation with valid params" do
post "/schemes", params: params
expect(Scheme.last.organisation_id).to eq(organisation.id)
expect(Scheme.last.owning_organisation_id).to eq(organisation.id)
expect(Scheme.last.service_name).to eq("testy")
expect(Scheme.last.scheme_type).to eq("Foyer")
expect(Scheme.last.sensitive).to eq("Yes")
@ -401,12 +400,12 @@ RSpec.describe SchemesController, type: :request do
end
context "when required organisation id param is missing" do
let(:params) { { "scheme" => { "service_name" => "qweqwer", "sensitive" => "Yes", "organisation_id" => "", "scheme_type" => "Foyer", "registered_under_care_act" => "Yes – part registered as a care home" } } }
let(:params) { { "scheme" => { "service_name" => "qweqwer", "sensitive" => "Yes", "owning_organisation_id" => "", "scheme_type" => "Foyer", "registered_under_care_act" => "Yes – part registered as a care home" } } }
it "displays the new page with an error message" do
post "/schemes", params: params
expect(response).to have_http_status(:unprocessable_entity)
expect(page).to have_content(I18n.t("activerecord.errors.models.scheme.attributes.organisation.required"))
expect(page).to have_content(I18n.t("activerecord.errors.models.scheme.attributes.owning_organisation_id.required"))
end
end
end
@ -436,7 +435,7 @@ RSpec.describe SchemesController, type: :request do
context "when signed in as a data coordinator" do
let(:user) { FactoryBot.create(:user, :data_coordinator) }
let(:scheme_to_update) { FactoryBot.create(:scheme, organisation: user.organisation) }
let(:scheme_to_update) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
before do
sign_in user
@ -615,11 +614,27 @@ RSpec.describe SchemesController, type: :request do
end
end
end
context "when editing scheme name details" do
let(:params) { { scheme: { service_name: "testy", sensitive: "1", page: "edit-name" } } }
it "renders scheme show page after successful update" do
follow_redirect!
expect(response).to have_http_status(:ok)
expect(page).to have_content(scheme_to_update.reload.service_name)
end
it "updates a scheme with valid params" do
follow_redirect!
expect(scheme_to_update.reload.service_name).to eq("testy")
expect(scheme_to_update.reload.sensitive).to eq("Yes")
end
end
end
context "when signed in as a support" do
let(:user) { FactoryBot.create(:user, :support) }
let(:scheme_to_update) { FactoryBot.create(:scheme, organisation: user.organisation) }
let(:scheme_to_update) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
before do
allow(user).to receive(:need_two_factor_authentication?).and_return(false)
@ -772,8 +787,8 @@ RSpec.describe SchemesController, type: :request do
scheme_type: "Foyer",
registered_under_care_act: "No",
page: "details",
organisation_id: another_organisation.id,
stock_owning_organisation_id: another_organisation.id } }
owning_organisation_id: another_organisation.id,
managing_organisation_id: another_organisation.id } }
end
it "renders confirm secondary group after successful update" do
@ -788,8 +803,8 @@ RSpec.describe SchemesController, type: :request do
expect(scheme_to_update.reload.scheme_type).to eq("Foyer")
expect(scheme_to_update.reload.sensitive).to eq("Yes")
expect(scheme_to_update.reload.registered_under_care_act).to eq("No")
expect(scheme_to_update.reload.organisation_id).to eq(another_organisation.id)
expect(scheme_to_update.reload.stock_owning_organisation_id).to eq(another_organisation.id)
expect(scheme_to_update.reload.owning_organisation_id).to eq(another_organisation.id)
expect(scheme_to_update.reload.managing_organisation_id).to eq(another_organisation.id)
end
context "when updating from check answers page" do
@ -810,6 +825,29 @@ RSpec.describe SchemesController, type: :request do
end
end
end
context "when editing scheme name details" do
let(:another_organisation) { FactoryBot.create(:organisation) }
let(:params) do
{ scheme: { service_name: "testy",
sensitive: "1",
page: "edit-name",
owning_organisation_id: another_organisation.id } }
end
it "renders scheme show page after successful update" do
follow_redirect!
expect(response).to have_http_status(:ok)
expect(page).to have_content(scheme_to_update.reload.service_name)
expect(scheme_to_update.reload.owning_organisation_id).to eq(another_organisation.id)
end
it "updates a scheme with valid params" do
follow_redirect!
expect(scheme_to_update.reload.service_name).to eq("testy")
expect(scheme_to_update.reload.sensitive).to eq("Yes")
end
end
end
end
@ -837,7 +875,7 @@ RSpec.describe SchemesController, type: :request do
context "when signed in as a data coordinator" do
let(:user) { FactoryBot.create(:user, :data_coordinator) }
let!(:scheme) { FactoryBot.create(:scheme, organisation: user.organisation) }
let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let!(:another_scheme) { FactoryBot.create(:scheme) }
before do
@ -903,7 +941,7 @@ RSpec.describe SchemesController, type: :request do
context "when signed in as a data coordinator" do
let(:user) { FactoryBot.create(:user, :data_coordinator) }
let!(:scheme) { FactoryBot.create(:scheme, organisation: user.organisation) }
let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let!(:another_scheme) { FactoryBot.create(:scheme) }
before do
@ -969,7 +1007,7 @@ RSpec.describe SchemesController, type: :request do
context "when signed in as a data coordinator" do
let(:user) { FactoryBot.create(:user, :data_coordinator) }
let!(:scheme) { FactoryBot.create(:scheme, organisation: user.organisation) }
let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let!(:another_scheme) { FactoryBot.create(:scheme) }
before do
@ -1035,7 +1073,7 @@ RSpec.describe SchemesController, type: :request do
context "when signed in as a data coordinator" do
let(:user) { FactoryBot.create(:user, :data_coordinator) }
let!(:scheme) { FactoryBot.create(:scheme, organisation: user.organisation) }
let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let!(:another_scheme) { FactoryBot.create(:scheme) }
before do
@ -1101,7 +1139,7 @@ RSpec.describe SchemesController, type: :request do
context "when signed in as a data coordinator" do
let(:user) { FactoryBot.create(:user, :data_coordinator) }
let!(:scheme) { FactoryBot.create(:scheme, organisation: user.organisation) }
let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let!(:another_scheme) { FactoryBot.create(:scheme) }
before do
@ -1167,7 +1205,7 @@ RSpec.describe SchemesController, type: :request do
context "when signed in as a data coordinator" do
let(:user) { FactoryBot.create(:user, :data_coordinator) }
let!(:scheme) { FactoryBot.create(:scheme, organisation: user.organisation) }
let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let!(:another_scheme) { FactoryBot.create(:scheme) }
before do
@ -1208,4 +1246,74 @@ RSpec.describe SchemesController, type: :request do
end
end
end
describe "#edit_name" do
context "when not signed in" do
it "redirects to the sign in page" do
get "/schemes/1/edit-name"
expect(response).to redirect_to("/account/sign-in")
end
end
context "when signed in as a data provider" do
let(:user) { FactoryBot.create(:user) }
before do
sign_in user
get "/schemes/1/edit-name"
end
it "returns 401 unauthorized" do
request
expect(response).to have_http_status(:unauthorized)
end
end
context "when signed in as a data coordinator" do
let(:user) { FactoryBot.create(:user, :data_coordinator) }
let!(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let!(:another_scheme) { FactoryBot.create(:scheme) }
before do
sign_in user
get "/schemes/#{scheme.id}/edit-name"
end
it "returns a template for a edit-name" do
expect(response).to have_http_status(:ok)
expect(page).to have_content("Scheme details")
expect(page).to have_content("This scheme contains confidential information")
expect(page).not_to have_content("Which organisation owns the housing stock for this scheme?")
end
context "when attempting to access secondary-client-group scheme page for another organisation" do
before do
get "/schemes/#{another_scheme.id}/edit-name"
end
it "returns 404 not_found" do
request
expect(response).to have_http_status(:not_found)
end
end
end
context "when signed in as a support user" do
let(:user) { FactoryBot.create(:user, :support) }
let!(:scheme) { FactoryBot.create(:scheme) }
before do
allow(user).to receive(:need_two_factor_authentication?).and_return(false)
sign_in user
get "/schemes/#{scheme.id}/edit-name"
end
it "returns a template for a secondary-client-group" do
expect(response).to have_http_status(:ok)
expect(page).to have_content("Scheme details")
expect(page).to have_content("This scheme contains confidential information")
expect(page).to have_content("Which organisation owns the housing stock for this scheme?")
end
end
end
end

9
spec/services/postcode_service_spec.rb

@ -0,0 +1,9 @@
require "rails_helper"
describe PostcodeService do
let(:postcode) { "s r81LS\u00A0" }
it "returns clean postcode" do
expect(described_class.clean(postcode)).to eq "SR81LS"
end
end

108
yarn.lock

@ -86,9 +86,9 @@
"@babel/highlight" "^7.18.6"
"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.6.tgz#8b37d24e88e8e21c499d4328db80577d8882fa53"
integrity sha512-tzulrgDT0QD6U7BJ4TKVk2SDDg7wlP39P9yAx1RfLy7vP/7rsDRlWVfbWxElslu56+r7QOhB2NSDsabYYruoZQ==
version "7.18.8"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.8.tgz#2483f565faca607b8535590e84e7de323f27764d"
integrity sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==
"@babel/core@^7.17.7":
version "7.18.6"
@ -111,7 +111,7 @@
json5 "^2.2.1"
semver "^6.3.0"
"@babel/generator@^7.18.6":
"@babel/generator@^7.18.6", "@babel/generator@^7.18.7":
version "7.18.7"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.7.tgz#2aa78da3c05aadfc82dbac16c99552fc802284bd"
integrity sha512-shck+7VLlY72a2w9c3zYWuE1pwOKEiQHV7GTUbSnhyl5eu3i04t30tBY82ZRWrDfo3gkakCFtevExnxbkf2a3A==
@ -222,9 +222,9 @@
"@babel/types" "^7.18.6"
"@babel/helper-module-transforms@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.6.tgz#57e3ca669e273d55c3cda55e6ebf552f37f483c8"
integrity sha512-L//phhB4al5uucwzlimruukHB3jRd5JGClwRMD/ROrVjXfLqovYnvQrK/JK36WYyVwGGO7OD3kMyVTjx+WVPhw==
version "7.18.8"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.8.tgz#4f8408afead0188cfa48672f9d0e5787b61778c8"
integrity sha512-che3jvZwIcZxrwh63VfnFTUzcAM9v/lznYkkRxIBGMPt1SudOKHAEec0SIRCfiuIzTcF7VGj/CaTT6gY4eWxvA==
dependencies:
"@babel/helper-environment-visitor" "^7.18.6"
"@babel/helper-module-imports" "^7.18.6"
@ -232,8 +232,8 @@
"@babel/helper-split-export-declaration" "^7.18.6"
"@babel/helper-validator-identifier" "^7.18.6"
"@babel/template" "^7.18.6"
"@babel/traverse" "^7.18.6"
"@babel/types" "^7.18.6"
"@babel/traverse" "^7.18.8"
"@babel/types" "^7.18.8"
"@babel/helper-optimise-call-expression@^7.18.6":
version "7.18.6"
@ -327,10 +327,10 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
"@babel/parser@^7.18.6", "@babel/parser@^7.6.0", "@babel/parser@^7.9.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.6.tgz#845338edecad65ebffef058d3be851f1d28a63bc"
integrity sha512-uQVSa9jJUe/G/304lXspfWVpKpK4euFLgGiMQFOCpM/bgcAdeoHwi/OQz23O9GK2osz26ZiXRRV9aV+Yl1O8tw==
"@babel/parser@^7.18.6", "@babel/parser@^7.18.8", "@babel/parser@^7.6.0", "@babel/parser@^7.9.6":
version "7.18.8"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.8.tgz#822146080ac9c62dac0823bb3489622e0bc1cbdf"
integrity sha512-RSKRfYX20dyH+elbJK2uqAkVyucL+xXzhqlMD5/ZXx+dAAwpyB7HsvnHe/ZUGOF+xLr5Wx9/JoXVTj6BQE2/oA==
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
version "7.18.6"
@ -613,9 +613,9 @@
"@babel/helper-plugin-utils" "^7.18.6"
"@babel/plugin-transform-classes@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.6.tgz#3501a8f3f4c7d5697c27a3eedbee71d68312669f"
integrity sha512-XTg8XW/mKpzAF3actL554Jl/dOYoJtv3l8fxaEczpgz84IeeVf+T1u2CSvPHuZbt0w3JkIx4rdn/MRQI7mo0HQ==
version "7.18.8"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.8.tgz#7e85777e622e979c85c701a095280360b818ce49"
integrity sha512-RySDoXdF6hgHSHuAW4aLGyVQdmvEX/iJtjVre52k0pxRq4hzqze+rAVP++NmNv596brBpYmaiKgTZby7ziBnVg==
dependencies:
"@babel/helper-annotate-as-pure" "^7.18.6"
"@babel/helper-environment-visitor" "^7.18.6"
@ -664,9 +664,9 @@
"@babel/helper-plugin-utils" "^7.18.6"
"@babel/plugin-transform-for-of@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.6.tgz#e0fdb813be908e91ccc9ec87b30cc2eabf046f7c"
integrity sha512-WAjoMf4wIiSsy88KmG7tgj2nFdEK7E46tArVtcgED7Bkj6Fg/tG5SbvNIOKxbFS2VFgNh6+iaPswBeQZm4ox8w==
version "7.18.8"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz#6ef8a50b244eb6a0bdbad0c7c61877e4e30097c1"
integrity sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==
dependencies:
"@babel/helper-plugin-utils" "^7.18.6"
@ -755,9 +755,9 @@
"@babel/helper-replace-supers" "^7.18.6"
"@babel/plugin-transform-parameters@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.6.tgz#cbe03d5a4c6385dd756034ac1baa63c04beab8dc"
integrity sha512-FjdqgMv37yVl/gwvzkcB+wfjRI8HQmc5EgOG9iGNvUY1ok+TjsoaMP7IqCDZBhkFcM5f3OPVMs6Dmp03C5k4/A==
version "7.18.8"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz#ee9f1a0ce6d78af58d0956a9378ea3427cccb48a"
integrity sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==
dependencies:
"@babel/helper-plugin-utils" "^7.18.6"
@ -954,26 +954,26 @@
"@babel/parser" "^7.18.6"
"@babel/types" "^7.18.6"
"@babel/traverse@^7.13.0", "@babel/traverse@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.6.tgz#a228562d2f46e89258efa4ddd0416942e2fd671d"
integrity sha512-zS/OKyqmD7lslOtFqbscH6gMLFYOfG1YPqCKfAW5KrTeolKqvB8UelR49Fpr6y93kYkW2Ik00mT1LOGiAGvizw==
"@babel/traverse@^7.13.0", "@babel/traverse@^7.18.6", "@babel/traverse@^7.18.8":
version "7.18.8"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.8.tgz#f095e62ab46abf1da35e5a2011f43aee72d8d5b0"
integrity sha512-UNg/AcSySJYR/+mIcJQDCv00T+AqRO7j/ZEJLzpaYtgM48rMg5MnkJgyNqkzo88+p4tfRvZJCEiwwfG6h4jkRg==
dependencies:
"@babel/code-frame" "^7.18.6"
"@babel/generator" "^7.18.6"
"@babel/generator" "^7.18.7"
"@babel/helper-environment-visitor" "^7.18.6"
"@babel/helper-function-name" "^7.18.6"
"@babel/helper-hoist-variables" "^7.18.6"
"@babel/helper-split-export-declaration" "^7.18.6"
"@babel/parser" "^7.18.6"
"@babel/types" "^7.18.6"
"@babel/parser" "^7.18.8"
"@babel/types" "^7.18.8"
debug "^4.1.0"
globals "^11.1.0"
"@babel/types@^7.18.6", "@babel/types@^7.18.7", "@babel/types@^7.4.4", "@babel/types@^7.6.1", "@babel/types@^7.9.6":
version "7.18.7"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.7.tgz#a4a2c910c15040ea52cdd1ddb1614a65c8041726"
integrity sha512-QG3yxTcTIBoAcQmkCs+wAPYZhu7Dk9rXKacINfNbdJDNERTbLQbHGyVG8q/YGMPeCJRIhSY0+fTc5+xuh6WPSQ==
"@babel/types@^7.18.6", "@babel/types@^7.18.7", "@babel/types@^7.18.8", "@babel/types@^7.4.4", "@babel/types@^7.6.1", "@babel/types@^7.9.6":
version "7.18.8"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.8.tgz#c5af199951bf41ba4a6a9a6d0d8ad722b30cd42f"
integrity sha512-qwpdsmraq0aJ3osLJRApsc2ouSJCdnMeZwB0DhbtHAtRpZNZCdlbRnHIgcRKzdE1g0iOGg644fzjOBcdOz9cPw==
dependencies:
"@babel/helper-validator-identifier" "^7.18.6"
to-fast-properties "^2.0.0"
@ -1045,9 +1045,9 @@
"@jridgewell/trace-mapping" "^0.3.9"
"@jridgewell/resolve-uri@^3.0.3":
version "3.0.8"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz#687cc2bbf243f4e9a868ecf2262318e2658873a1"
integrity sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w==
version "3.1.0"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1":
version "1.1.2"
@ -1228,9 +1228,9 @@
integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==
"@types/node@*", "@types/node@>=10.0.0":
version "18.0.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.1.tgz#e91bd73239b338557a84d1f67f7b9e0f25643870"
integrity sha512-CmR8+Tsy95hhwtZBKJBs0/FFq4XX7sDZHlGGf+0q+BRZfMbOTkzkj0AFAuTyXbObDIoanaBBW0+KEW+m3N16Wg==
version "18.0.3"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.3.tgz#463fc47f13ec0688a33aec75d078a0541a447199"
integrity sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==
"@types/normalize-package-data@^2.4.0":
version "2.4.1"
@ -2384,9 +2384,9 @@ ejs@^3.1.6:
jake "^10.8.5"
electron-to-chromium@^1.4.172:
version "1.4.178"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.178.tgz#3dae6fda486007bb54bbfed420ebd40881a3de45"
integrity sha512-aWuhJXkwIdoQzGR8p2QvR3N0OzdUKZSP8+P/hzuMzNQIPZoEa8HiCGM75bQBHjyz+eKT5PB9dVCzkK/tyQ4B5Q==
version "1.4.184"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.184.tgz#381d4d111fc82d3376ed690dfb621e675f9078a9"
integrity sha512-IADi390FRdvxWfVX3hjzfTDNVHiTqVo9ar53/7em/SfhUG9YcjVhyQecY/XwmBHRKden/wFud7RWOUH7+7LFng==
element-closest@^2.0.2:
version "2.0.2"
@ -3126,9 +3126,9 @@ globjoin@^0.1.4:
integrity sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==
govuk-eleventy-plugin@^2.4.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/govuk-eleventy-plugin/-/govuk-eleventy-plugin-2.7.0.tgz#f767d12a7202a2410dde2e914d2dcdec0b57df4e"
integrity sha512-WlaZR9Ml9atHJUXFFgXqEfrE8RlPNLfm6/rTa22gkZPazV22VrfVtAeqmGMcSWNx0My8neFUM8HeIGI12oSgfg==
version "2.7.2"
resolved "https://registry.yarnpkg.com/govuk-eleventy-plugin/-/govuk-eleventy-plugin-2.7.2.tgz#629e5e0c1c8c99e301e5a3ad72afca2d17d71325"
integrity sha512-CsqK39J0uSodQFzNjCjZv1FdyyZvTpMQ1jIhTu7wohCdVTlnzMgNQojkE5Nn4LKxFsV1OZ9DSzgeqn+aeozXtw==
dependencies:
"@11ty/eleventy" "^1.0.0"
"@11ty/eleventy-navigation" "^0.3.2"
@ -3757,9 +3757,9 @@ jstransformer@1.0.0:
promise "^7.0.1"
"jsx-ast-utils@^2.4.1 || ^3.0.0":
version "3.3.1"
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.1.tgz#a3e0f1cb7e230954eab4dcbce9f6288a78f8ba44"
integrity sha512-pxrjmNpeRw5wwVeWyEAk7QJu2GnBO3uzPFmHCKJJFPKK2Cy0cWL23krGtLdnMmbIi6/FjlrQpPyfQI19ByPOhQ==
version "3.3.2"
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.2.tgz#afe5efe4332cd3515c065072bd4d6b0aa22152bd"
integrity sha512-4ZCADZHRkno244xlNnn4AOG6sRQ7iBZ5BbgZ4vW4y5IZw7cVUD1PPeblm1xx/nfmMxPdt/LHsXZW8z/j58+l9Q==
dependencies:
array-includes "^3.1.5"
object.assign "^4.1.2"
@ -3822,9 +3822,9 @@ linkify-it@^3.0.1:
uc.micro "^1.0.1"
liquidjs@^9.36.1:
version "9.37.0"
resolved "https://registry.yarnpkg.com/liquidjs/-/liquidjs-9.37.0.tgz#58e0dd2fa779635ead589e82cfa9baffe49f490e"
integrity sha512-qDj9iiNdB+QNZTR4iKjiQzoHQma7V8Itx5oZG/ZCP7xjebh1LI+s5IG2ZYUbs1ALO6hBzmW36Ptd8RR4eohuDA==
version "9.38.0"
resolved "https://registry.yarnpkg.com/liquidjs/-/liquidjs-9.38.0.tgz#0fb4d380007ecde00d8c43c5fd5535d6189b7778"
integrity sha512-LDGPbOfc8L9LG7ZSG0NueTcCe0AZzJAIa0BjYgMnCR+4VO0yS+AnOZjx0WOih8mtLRNSIYo9fik5exY6Cf4TOQ==
list-to-array@^1.1.0:
version "1.1.0"
@ -5038,9 +5038,9 @@ rimraf@^3.0.2:
glob "^7.1.3"
rollup@^2.62.0:
version "2.75.7"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.75.7.tgz#221ff11887ae271e37dcc649ba32ce1590aaa0b9"
integrity sha512-VSE1iy0eaAYNCxEXaleThdFXqZJ42qDBatAwrfnPlENEZ8erQ+0LYX4JXOLPceWfZpV1VtZwZ3dFCuOZiSyFtQ==
version "2.76.0"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.76.0.tgz#c69fe03db530ac53fcb9523b3caa0d3c0b9491a1"
integrity sha512-9jwRIEY1jOzKLj3nsY/yot41r19ITdQrhs+q3ggNWhr9TQgduHqANvPpS32RNpzGklJu3G1AJfvlZLi/6wFgWA==
optionalDependencies:
fsevents "~2.3.2"

Loading…
Cancel
Save