Browse Source

Merge branch 'main' into CLDC-1315-set-foreign-key-constraint-on-users

pull/726/head
Ted-U 3 years ago committed by GitHub
parent
commit
397472c4cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 37
      app/controllers/form_controller.rb
  2. 2
      app/controllers/locations_controller.rb
  3. 2
      app/frontend/controllers/accessible_autocomplete_controller.js
  4. 28
      app/frontend/modules/search.js
  5. 34
      app/models/case_log.rb
  6. 6
      app/models/derived_variables/case_log_variables.rb
  7. 34
      app/models/form/question.rb
  8. 2
      app/models/form/setup/questions/location_id.rb
  9. 6
      app/models/form/setup/questions/scheme_id.rb
  10. 20
      app/models/location.rb
  11. 12
      app/models/scheme.rb
  12. 10
      app/models/validations/submission_validations.rb
  13. 68
      app/services/imports/case_logs_import_service.rb
  14. 45
      app/services/imports/scheme_import_service.rb
  15. 13
      app/services/imports/scheme_location_import_service.rb
  16. 21
      app/services/postcode_service.rb
  17. 25
      app/views/form/_select_question.html.erb
  18. 15
      app/views/form/page.html.erb
  19. 22
      app/views/locations/edit.html.erb
  20. 22
      app/views/locations/new.html.erb
  21. 66
      config/forms/2021_2022.json
  22. 70
      config/forms/2022_2023.json
  23. 17
      config/locales/en.yml
  24. 7
      db/migrate/20220713095713_add_mobility_type_to_locations.rb
  25. 7
      db/migrate/20220714080044_add_location_startdate.rb
  26. 5
      db/migrate/20220715133937_remove_county_from_location.rb
  27. 5
      db/schema.rb
  28. 5
      spec/factories/location.rb
  29. 27
      spec/features/form/accessible_autocomplete_spec.rb
  30. 2
      spec/features/form/check_answers_page_spec.rb
  31. 76
      spec/features/form/form_navigation_spec.rb
  32. 1
      spec/features/form/page_routing_spec.rb
  33. 4
      spec/features/form/progressive_total_field_spec.rb
  34. 1
      spec/features/form/saving_data_spec.rb
  35. 47
      spec/features/schemes_spec.rb
  36. 524
      spec/fixtures/imports/case_logs/0b4a68df-30cc-474a-93c0-a56ce8fdad3b.xml
  37. 15
      spec/models/case_log_spec.rb
  38. 71
      spec/models/form/setup/questions/location_id_spec.rb
  39. 18
      spec/models/form/setup/questions/scheme_id_spec.rb
  40. 11
      spec/models/location_spec.rb
  41. 4
      spec/models/scheme_spec.rb
  42. 1
      spec/rails_helper.rb
  43. 2
      spec/requests/case_logs_controller_spec.rb
  44. 10
      spec/requests/locations_controller_spec.rb
  45. 68
      spec/services/imports/case_logs_import_service_spec.rb
  46. 13
      spec/services/imports/scheme_import_service_spec.rb
  47. 2
      spec/services/imports/scheme_location_import_service_spec.rb

37
app/controllers/form_controller.rb

@ -7,11 +7,16 @@ class FormController < ApplicationController
if @case_log
@page = @case_log.form.get_page(params[:case_log][:page])
responses_for_page = responses_for_page(@page)
if @case_log.update(responses_for_page)
session[:errors] = nil
mandatory_questions_with_no_response = mandatory_questions_with_no_response(responses_for_page)
if mandatory_questions_with_no_response.empty? && @case_log.update(responses_for_page)
session[:errors] = session[:fields] = nil
redirect_to(successful_redirect_path)
else
redirect_path = "case_log_#{@page.id}_path"
mandatory_questions_with_no_response.map do |question|
@case_log.errors.add question.id.to_sym, question.unanswered_error_message
end
session[:errors] = @case_log.errors.to_json
Rails.logger.info "User triggered validation(s) on: #{@case_log.errors.map(&:attribute).join(', ')}"
redirect_to(send(redirect_path, @case_log))
@ -48,6 +53,7 @@ class FormController < ApplicationController
messages.each { |message| @case_log.errors.add field.to_sym, message }
end
end
session["fields"].each { |field, value| @case_log[field] = value } if session["fields"]
@subsection = @case_log.form.subsection_for_page(page)
@page = @case_log.form.get_page(page.id)
if @page.routed_to?(@case_log, current_user)
@ -120,4 +126,31 @@ private
redirect_path = @case_log.form.next_page_redirect_path(@page, @case_log, current_user)
send(redirect_path, @case_log)
end
def mandatory_questions_with_no_response(responses_for_page)
session["fields"] = {}
calc_questions = @page.questions.map(&:result_field)
@page.questions.select do |question|
next if calc_questions.include?(question.id)
question_is_required?(question) && question_missing_response?(responses_for_page, question)
end
end
def question_is_required?(question)
CaseLog::OPTIONAL_FIELDS.exclude?(question.id) &&
@page.subsection.applicable_questions(@case_log).map(&:id).include?(question.id)
end
def question_missing_response?(responses_for_page, question)
if %w[checkbox validation_override].include?(question.type)
question.answer_options.keys.reject { |x| x.match(/divider/) }.all? do |option|
session["fields"][option] = @case_log[option] = params["case_log"][question.id].include?(option) ? 1 : 0
params["case_log"][question.id].exclude?(option)
end
else
session["fields"][question.id] = @case_log[question.id] = responses_for_page[question.id]
responses_for_page[question.id].nil? || responses_for_page[question.id].blank?
end
end
end

2
app/controllers/locations_controller.rb

@ -69,7 +69,7 @@ private
end
def location_params
required_params = params.require(:location).permit(:postcode, :name, :units, :type_of_unit, :wheelchair_adaptation, :add_another_location).merge(scheme_id: @scheme.id)
required_params = params.require(:location).permit(:postcode, :name, :units, :type_of_unit, :wheelchair_adaptation, :add_another_location, :startdate).merge(scheme_id: @scheme.id)
required_params[:postcode] = PostcodeService.clean(required_params[:postcode]) if required_params[:postcode]
required_params
end

2
app/frontend/controllers/accessible_autocomplete_controller.js

@ -15,7 +15,7 @@ export default class extends Controller {
accessibleAutocomplete.enhanceSelectElement({
defaultValue: '',
selectElement: selectEl,
minLength: 2,
minLength: 1,
source: (query, populateResults) => {
if (/\S/.test(query)) {
populateResults(sort(query, options))

28
app/frontend/modules/search.js

@ -12,9 +12,13 @@ const clean = (text) =>
.toLowerCase()
const cleanseOption = (option) => {
const synonyms = (option.synonyms || []).map(clean)
option.clean = {
name: clean(option.name),
nameWithoutStopWords: removeStopWords(option.name),
synonyms,
synonymsWithoutStopWords: synonyms.map(removeStopWords),
boost: option.boost || 1
}
@ -41,19 +45,34 @@ const startsWith = (word, query) => word.search(startsWithRegExp(query)) === 0
const wordsStartsWithQuery = (word, regExps) =>
regExps.every((regExp) => word.search(regExp) >= 0)
const calculateWeight = ({ name, nameWithoutStopWords }, query) => {
const anyMatch = (words, query, evaluatorFunc) => words.some((word) => evaluatorFunc(word, query))
const synonymsExactMatch = (synonyms, query) => anyMatch(synonyms, query, exactMatch)
const synonymsStartsWith = (synonyms, query) => anyMatch(synonyms, query, startsWith)
const wordInSynonymStartsWithQuery = (synonyms, startsWithQueryWordsRegexes) =>
anyMatch(synonyms, startsWithQueryWordsRegexes, wordsStartsWithQuery)
const calculateWeight = ({ name, synonyms, nameWithoutStopWords, synonymsWithoutStopWords }, query) => {
const queryWithoutStopWords = removeStopWords(query)
if (exactMatch(name, query)) return 100
if (exactMatch(nameWithoutStopWords, queryWithoutStopWords)) return 95
if (synonymsExactMatch(synonyms, query)) return 75
if (synonymsExactMatch(synonymsWithoutStopWords, queryWithoutStopWords)) return 70
if (startsWith(name, query)) return 60
if (startsWith(nameWithoutStopWords, queryWithoutStopWords)) return 55
if (synonymsStartsWith(synonyms, query)) return 50
if (synonymsStartsWith(synonyms, queryWithoutStopWords)) return 40
const startsWithRegExps = queryWithoutStopWords
.split(/\s+/)
.map(startsWithRegExp)
if (wordsStartsWithQuery(nameWithoutStopWords, startsWithRegExps)) return 25
if (wordInSynonymStartsWithQuery(synonymsWithoutStopWords, startsWithRegExps)) return 10
return 0
}
@ -91,8 +110,8 @@ export const sort = (query, options) => {
export const suggestion = (value, options) => {
const option = options.find((o) => o.name === value)
if (option) {
const html = `<span>${value}</span>`
return html
const html = option.append ? `<span>${value}</span> <span class="autocomplete__option__append">${option.append}</span>` : `<span>${value}</span>`
return option.hint ? `${html}<div class="autocomplete__option__hint">${option.hint}</div>` : html
} else {
return '<span>No results found</span>'
}
@ -101,6 +120,9 @@ export const suggestion = (value, options) => {
export const enhanceOption = (option) => {
return {
name: option.label,
synonyms: (option.getAttribute('data-synonyms') ? option.getAttribute('data-synonyms').split('|') : []),
append: option.getAttribute('data-append'),
hint: option.getAttribute('data-hint'),
boost: parseFloat(option.getAttribute('data-boost')) || 1
}
}

34
app/models/case_log.rb

@ -8,7 +8,6 @@ class CaseLogValidator < ActiveModel::Validator
include Validations::TenancyValidations
include Validations::DateValidations
include Validations::LocalAuthorityValidations
include Validations::SubmissionValidations
def validate(record)
validation_methods = public_methods.select { |method| method.starts_with?("validate_") }
@ -103,6 +102,22 @@ class CaseLog < ApplicationRecord
attribute_names - AUTOGENERATED_FIELDS
end
def la
if location
location.location_code
else
super
end
end
def postcode_full
if location
location.postcode
else
super
end
end
def completed?
status == "completed"
end
@ -466,7 +481,7 @@ class CaseLog < ApplicationRecord
private
PIO = Postcodes::IO.new
PIO = PostcodeService.new
def update_status!
self.status = if all_fields_completed? && errors.empty?
@ -588,20 +603,7 @@ private
end
def get_inferred_la(postcode)
# Avoid network calls when postcode is invalid
return unless postcode.match(POSTCODE_REGEXP)
postcode_lookup = nil
begin
# URI encoding only supports ASCII characters
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")
end
if postcode_lookup && postcode_lookup.info.present?
postcode_lookup.codes["admin_district"]
end
PIO.infer_la(postcode)
end
def get_has_benefits

6
app/models/derived_variables/case_log_variables.rb

@ -73,10 +73,8 @@ module DerivedVariables::CaseLogVariables
self.location = scheme.locations.first
end
if location
self.la = location.county
self.postcode_full = location.postcode
wheelchair_adaptation_map = { 1 => 1, 0 => 2 }
self.wchair = wheelchair_adaptation_map[location.wheelchair_adaptation.to_i]
# TODO: Remove and replace with mobility type
self.wchair = location.wheelchair_adaptation_before_type_cast
end
if is_renewal?
self.voiddate = startdate

34
app/models/form/question.rb

@ -136,7 +136,11 @@ class Form::Question
labels = answer_options[value.to_s]
labels["value"] if labels
when "select"
answer_options[value.to_s]
if answer_options[value.to_s].respond_to?(:service_name)
answer_options[value.to_s].service_name
else
answer_options[value.to_s]
end
else
value.to_s
end
@ -173,6 +177,16 @@ class Form::Question
type == "radio" && RADIO_REFUSED_VALUE[id.to_sym]&.include?(value)
end
def display_label
check_answer_label || header || id.humanize
end
def unanswered_error_message
return I18n.t("validations.declaration.missing") if id == "declaration"
I18n.t("validations.not_answered", question: display_label.downcase)
end
def suffix_label(case_log)
return "" unless suffix
return suffix if suffix.is_a?(String)
@ -191,6 +205,24 @@ class Form::Question
label
end
def answer_option_synonyms(resource)
return unless resource.respond_to?(:synonyms)
resource.synonyms
end
def answer_option_append(resource)
return unless resource.respond_to?(:appended_text)
resource.appended_text
end
def answer_option_hint(resource)
return unless resource.respond_to?(:hint)
resource.hint
end
private
def selected_answer_option_is_derived?(case_log)

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

@ -13,7 +13,7 @@ class Form::Setup::Questions::LocationId < ::Form::Question
answer_opts = {}
return answer_opts unless ActiveRecord::Base.connected?
Location.select(:id, :postcode, :name).each_with_object(answer_opts) do |location, hsh|
Location.select(:id, :postcode, :name).where("startdate <= ? or startdate IS NULL", Time.zone.today).each_with_object(answer_opts) do |location, hsh|
hsh[location.id.to_s] = { "value" => location.postcode, "hint" => location.name }
hsh
end

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

@ -13,8 +13,8 @@ class Form::Setup::Questions::SchemeId < ::Form::Question
answer_opts = {}
return answer_opts unless ActiveRecord::Base.connected?
Scheme.select(:id, :service_name).each_with_object(answer_opts) do |scheme, hsh|
hsh[scheme.id.to_s] = scheme.service_name
Scheme.select(:id, :service_name, :primary_client_group, :secondary_client_group).each_with_object(answer_opts) do |scheme, hsh|
hsh[scheme.id.to_s] = scheme
hsh
end
end
@ -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(owning_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).joins(:locations).merge(Location.where("startdate <= ? or startdate IS NULL", Time.zone.today)).map(&:id)
answer_options.select do |k, _v|
user_org_scheme_ids.include?(k.to_i)
end

20
app/models/location.rb

@ -2,15 +2,27 @@ class Location < ApplicationRecord
validate :validate_postcode
belongs_to :scheme
before_save :infer_la!, if: :postcode_changed?
attr_accessor :add_another_location
WHEELCHAIR_ADAPTATIONS = {
Yes: 1,
No: 0,
No: 2,
}.freeze
enum wheelchair_adaptation: WHEELCHAIR_ADAPTATIONS
MOBILITY_TYPE = {
"Property fitted with equipment and adaptations (if not designed to above standards)": "A",
"Property designed to accessible general standard": "M",
"None": "N",
"Property designed to wheelchair user standard": "W",
"Missing": "X",
}.freeze
enum mobility_type: MOBILITY_TYPE
TYPE_OF_UNIT = {
"Self-contained flat or bedsit": 1,
"Self-contained flat or bedsit with common facilities": 2,
@ -34,10 +46,16 @@ class Location < ApplicationRecord
private
PIO = PostcodeService.new
def validate_postcode
if postcode.nil? || !postcode&.match(POSTCODE_REGEXP)
error_message = I18n.t("validations.postcode")
errors.add :postcode, error_message
end
end
def infer_la!
self.location_code = PIO.infer_la(postcode)
end
end

12
app/models/scheme.rb

@ -142,4 +142,16 @@ class Scheme < ApplicationRecord
{ name: "Intended length of stay", value: intended_stay },
]
end
def synonyms
locations.map(&:postcode).join(",")
end
def appended_text
"(#{locations.count { |location| location.startdate.blank? || location.startdate <= Time.zone.today }} locations)"
end
def hint
[primary_client_group, secondary_client_group].filter(&:present?).join(", ")
end
end

10
app/models/validations/submission_validations.rb

@ -1,10 +0,0 @@
module Validations::SubmissionValidations
# Validations methods need to be called 'validate_<page_name>' to run on model save
# or 'validate_' to run on submit as well
def validate_declaration(record)
if record.declaration&.zero?
record.errors.add :declaration, I18n.t("validations.declaration.missing")
end
end
end

68
app/services/imports/case_logs_import_service.rb

@ -15,6 +15,12 @@ module Imports
private
FORM_NAME_INDEX = {
start_year: 0,
rent_type: 2,
needs_type: 3,
}.freeze
GN_SH = {
general_needs: 1,
supported_housing: 2,
@ -175,15 +181,20 @@ module Imports
attributes["first_time_property_let_as_social_housing"] = first_time_let(attributes["rsnvac"])
attributes["declaration"] = declaration(xml_doc)
# Set charges to 0 if others are partially populated
unless attributes["brent"].nil? &&
attributes["scharge"].nil? &&
attributes["pscharge"].nil? &&
attributes["supcharg"].nil?
attributes["brent"] ||= BigDecimal("0.0")
attributes["scharge"] ||= BigDecimal("0.0")
attributes["pscharge"] ||= BigDecimal("0.0")
attributes["supcharg"] ||= BigDecimal("0.0")
set_partial_charges_to_zero(attributes)
# Supported Housing fields
if attributes["needstype"] == GN_SH[:supported_housing]
old_visible_id = safe_string_as_integer(xml_doc, "_1cschemecode")
location = Location.find_by(old_visible_id:)
scheme = location.scheme
# Set the scheme via location, because the scheme old visible ID is not unique
attributes["location_id"] = location.id
attributes["scheme_id"] = scheme.id
attributes["sheltered"] = unsafe_string_as_integer(xml_doc, "Q1e")
attributes["chcharge"] = safe_string_as_decimal(xml_doc, "Q18b")
attributes["household_charge"] = household_charge(xml_doc)
attributes["is_carehome"] = is_carehome(scheme)
end
# Handles confidential schemes
@ -306,7 +317,7 @@ module Imports
end
def needs_type(xml_doc)
gn_sh = get_form_name_component(xml_doc, -1)
gn_sh = get_form_name_component(xml_doc, FORM_NAME_INDEX[:needs_type])
case gn_sh
when "GN"
GN_SH[:general_needs]
@ -319,7 +330,7 @@ module Imports
# This does not match renttype (CDS) which is derived by case log logic
def rent_type(xml_doc, lar, irproduct)
sr_ar_ir = get_form_name_component(xml_doc, -2)
sr_ar_ir = get_form_name_component(xml_doc, FORM_NAME_INDEX[:rent_type])
case sr_ar_ir
when "SR"
@ -590,5 +601,40 @@ module Imports
end
end
end
def household_charge(xml_doc)
value = string_or_nil(xml_doc, "Q18c")
start_year = Integer(get_form_name_component(xml_doc, FORM_NAME_INDEX[:start_year]))
if start_year <= 2021
# Yes means that there are no charges (2021 or earlier)
value && value.include?("Yes") ? 1 : 0
else
# Yes means that there are charges (2022 onwards)
value && value.include?("Yes") ? 0 : 1
end
end
def set_partial_charges_to_zero(attributes)
unless attributes["brent"].nil? &&
attributes["scharge"].nil? &&
attributes["pscharge"].nil? &&
attributes["supcharg"].nil?
attributes["brent"] ||= BigDecimal("0.0")
attributes["scharge"] ||= BigDecimal("0.0")
attributes["pscharge"] ||= BigDecimal("0.0")
attributes["supcharg"] ||= BigDecimal("0.0")
end
end
def is_carehome(scheme)
return nil unless scheme
if [2, 3, 4].include?(scheme.registered_under_care_act_before_type_cast)
1
else
0
end
end
end
end

45
app/services/imports/scheme_import_service.rb

@ -5,20 +5,18 @@ module Imports
end
def create_scheme(xml_document)
old_id = string_or_nil(xml_document, "id")
status = string_or_nil(xml_document, "status")
if status == "Approved"
attributes = scheme_attributes(xml_document)
if attributes["status"] == "Approved"
Scheme.create!(
owning_organisation_id: find_owning_organisation_id(xml_document),
managing_organisation_id: find_managing_organisation_id(xml_document),
service_name: string_or_nil(xml_document, "name"),
arrangement_type: string_or_nil(xml_document, "arrangement_type"),
old_id:,
old_visible_id: safe_string_as_integer(xml_document, "visible-id"),
owning_organisation_id: attributes["owning_organisation_id"],
managing_organisation_id: attributes["managing_organisation_id"],
service_name: attributes["service_name"],
arrangement_type: attributes["arrangement_type"],
old_id: attributes["old_id"],
old_visible_id: attributes["old_visible_id"],
)
else
@logger.warn("Scheme with legacy ID #{old_id} is not approved (#{status}), skipping")
@logger.warn("Scheme with legacy ID #{attributes['old_id']} is not approved (#{attributes['status']}), skipping")
end
end
@ -39,16 +37,33 @@ module Imports
Integer(str, exception: false)
end
def find_owning_organisation_id(xml_doc)
old_org_id = string_or_nil(xml_doc, "institution")
def scheme_attributes(xml_doc)
attributes = {}
attributes["old_id"] = string_or_nil(xml_doc, "id")
attributes["old_visible_id"] = string_or_nil(xml_doc, "visible-id")
attributes["status"] = string_or_nil(xml_doc, "status")
attributes["service_name"] = string_or_nil(xml_doc, "name")
attributes["arrangement_type"] = string_or_nil(xml_doc, "arrangement_type")
attributes["owning_org_old_id"] = string_or_nil(xml_doc, "institution")
attributes["owning_organisation_id"] = find_owning_organisation_id(attributes["owning_org_old_id"])
attributes["management_org_old_visible_id"] = safe_string_as_integer(xml_doc, "agent")
attributes["managing_organisation_id"] = find_managing_organisation_id(attributes["management_org_old_visible_id"])
if attributes["arrangement_type"] == "D" && attributes["managing_organisation_id"].nil?
attributes["managing_organisation_id"] = attributes["owning_organisation_id"]
end
attributes
end
def find_owning_organisation_id(old_org_id)
organisation = Organisation.find_by(old_org_id:)
raise "Organisation not found with legacy ID #{old_org_id}" if organisation.nil?
organisation.id
end
def find_managing_organisation_id(xml_doc)
old_visible_id = safe_string_as_integer(xml_doc, "agent")
def find_managing_organisation_id(old_visible_id)
return unless old_visible_id
organisation = Organisation.find_by(old_visible_id:)

13
app/services/imports/scheme_location_import_service.rb

@ -67,11 +67,13 @@ module Imports
attributes["registered_under_care_act"] = registered_under_care_act.zero? ? nil : registered_under_care_act
attributes["support_type"] = safe_string_as_integer(xml_doc, "support-type")
attributes["intended_stay"] = string_or_nil(xml_doc, "intended-stay")
attributes["mobility_type"] = string_or_nil(xml_doc, "mobility-type")
attributes["primary_client_group"] = string_or_nil(xml_doc, "client-group-1")
attributes["secondary_client_group"] = string_or_nil(xml_doc, "client-group-2")
attributes["secondary_client_group"] = nil if attributes["primary_client_group"] == attributes["secondary_client_group"]
attributes["sensitive"] = sensitive(xml_doc)
attributes["end_date"] = parse_end_date(xml_doc)
attributes["start_date"] = parse_date(xml_doc, "start-date")
attributes["end_date"] = parse_date(xml_doc, "end-date")
attributes["location_name"] = string_or_nil(xml_doc, "name")
attributes["postcode"] = string_or_nil(xml_doc, "postcode")
attributes["units"] = safe_string_as_integer(xml_doc, "total-units")
@ -84,15 +86,16 @@ module Imports
def add_location(scheme, attributes)
if attributes["end_date"].nil? || attributes["end_date"] >= Time.zone.now
# wheelchair_adaptation: string_or_nil(xml_doc, "mobility-type"),
begin
Location.create!(
name: attributes["location_name"],
postcode: attributes["postcode"],
mobility_type: attributes["mobility_type"],
units: attributes["units"],
type_of_unit: attributes["type_of_unit"],
old_visible_id: attributes["location_old_visible_id"],
old_id: attributes["location_old_id"],
startdate: attributes["start_date"],
scheme:,
)
rescue ActiveRecord::RecordNotUnique
@ -185,9 +188,9 @@ module Imports
end
end
def parse_end_date(xml_doc)
end_date = string_or_nil(xml_doc, "end-date")
Time.zone.parse(end_date) if end_date
def parse_date(xml_doc, attribute)
date = string_or_nil(xml_doc, attribute)
Time.zone.parse(date) if date
end
end
end

21
app/services/postcode_service.rb

@ -1,4 +1,25 @@
class PostcodeService
def initialize
@pio = Postcodes::IO.new
end
def infer_la(postcode)
# Avoid network calls when postcode is invalid
return unless postcode.match(POSTCODE_REGEXP)
postcode_lookup = nil
begin
# URI encoding only supports ASCII characters
ascii_postcode = self.class.clean(postcode)
Timeout.timeout(5) { postcode_lookup = @pio.lookup(ascii_postcode) }
rescue Timeout::Error
Rails.logger.warn("Postcodes.io lookup timed out")
end
if postcode_lookup && postcode_lookup.info.present?
postcode_lookup.codes["admin_district"]
end
end
def self.clean(postcode)
postcode.encode("ASCII", "UTF-8", invalid: :replace, undef: :replace, replace: "").delete(" ").upcase
end

25
app/views/form/_select_question.html.erb

@ -1,13 +1,18 @@
<%= render partial: "form/guidance/#{question.guidance_partial}" if question.guidance_partial %>
<% selected = @case_log.public_send(question.id) || "" %>
<% answers = question.displayed_answer_options(@case_log).map { |key, value| OpenStruct.new(id: key, name: value) } %>
<%= f.govuk_collection_select question.id.to_sym,
answers,
:id,
:name,
caption: caption(caption_text, page_header, conditional),
label: legend(question, page_header, conditional),
hint: { text: question.hint_text&.html_safe },
options: { disabled: [""], selected: },
"data-controller": "accessible-autocomplete" %>
<% answers = question.displayed_answer_options(@case_log).map { |key, value| OpenStruct.new(id: key, name: value.respond_to?(:service_name) ? value.service_name : nil, resource: value) } %>
<%= f.govuk_select(question.id.to_sym,
label: legend(question, page_header, conditional),
"data-controller": "accessible-autocomplete",
caption: caption(caption_text, page_header, conditional),
hint: { text: question.hint_text&.html_safe }) do %>
<% answers.each do |answer| %>
<option value="<%= answer.id %>"
data-synonyms="<%= question.answer_option_synonyms(answer.resource) %>"
data-append="<%= question.answer_option_append(answer.resource) %>"
data-hint="<%= question.answer_option_hint(answer.resource) %>"
<%= @case_log[question.id] == answer.name || @case_log[question.id] == answer.resource || @case_log[question.id].to_s == answer.id ? "selected" : "" %>
<%= answer.id == "" ? "disabled" : "" %>><%= answer.name || answer.resource %></option>
<% end %>
<% end %>

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

@ -5,7 +5,6 @@
<% end %>
<div data-controller="govukfrontend"></div>
<%= form_with model: @case_log, url: form_case_log_path(@case_log), method: "post", local: true do |f| %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-<%= @page.questions[0].type == "interruption_screen" ? "full-from-desktop" : "two-thirds-from-desktop" %>">
@ -39,9 +38,17 @@
<% end %>
<%= f.hidden_field :page, value: @page.id %>
<% if !@page.id.include?("value_check") %>
<%= f.govuk_submit "Save and continue" %>
<% end %>
<div class="govuk-button-group">
<% if !@page.id.include?("value_check") && if request.query_parameters["referrer"] != "check_answers" %>
<%= f.govuk_submit "Save and continue" %>
<%= govuk_link_to "Skip for now", send(@case_log.form.next_page_redirect_path(@page, @case_log, current_user), @case_log) %>
<% else %>
<%= f.govuk_submit "Save changes" %>
<%= govuk_link_to "Cancel", "/logs/#{@case_log.id}/setup/check-answers" %>
<% end %>
<% end %>
</div>
</div>
</div>
<% end %>

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

@ -16,17 +16,17 @@
<%= f.govuk_text_field :postcode,
label: { size: "m" },
hint: { text: "For example, SW1P 4DF." },
hint: { text: I18n.t("hints.location.postcode") },
width: 5 %>
<%= f.govuk_text_field :name,
label: { text: "Location name (optional)", size: "m" },
hint: { text: "This is how you refer to this location within your organisation" } %>
label: { text: I18n.t("questions.location.name"), size: "m" },
hint: { text: I18n.t("hints.location.name") } %>
<%= f.govuk_number_field :units,
label: { text: "Total number of units at this location", size: "m" },
label: { text: I18n.t("questions.location.units"), size: "m" },
width: 2,
hint: { text: "A unit can be a bedroom in a shared house or flat, or a house with 4 bedrooms. Do not include bedrooms used for wardens, managers, volunteers or sleep-in staff.s" },
hint: { text: I18n.t("hints.location.units") },
autofocus: true %>
<% type_of_units_selection = Location.type_of_units.keys.map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %>
@ -34,15 +34,19 @@
type_of_units_selection,
:id,
:name,
legend: { text: "What is this type of scheme?", size: "m" } %>
legend: { text: I18n.t("questions.location.type_of_unit"), 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,
:name,
hint: { text: "This includes stairlifts, ramps, level-access showers or grab rails" },
legend: { text: "Are the majority of units in this location built or adapted to wheelchair-user standards?", size: "m" } %>
hint: { text: I18n.t("hints.location.wheelchair_adaptation") },
legend: { text: I18n.t("questions.location.wheelchair_adaptation"), size: "m" } %>
<%= f.govuk_date_field :startdate,
legend: { text: I18n.t("questions.location.startdate"), size: "m" },
width: 20 %>
<%= govuk_section_break(visible: true, size: "m") %>
@ -52,7 +56,7 @@
:id,
:name,
inline: true,
legend: { text: "Do you want to add another location?", size: "m" } %>
legend: { text: I18n.t("questions.location.add_another_location"), size: "m" } %>
<%= f.hidden_field :page, value: "edit" %>

22
app/views/locations/new.html.erb

@ -16,17 +16,17 @@
<%= f.govuk_text_field :postcode,
label: { size: "m" },
hint: { text: "For example, SW1P 4DF." },
hint: { text: I18n.t("hints.location.postcode") },
width: 5 %>
<%= f.govuk_text_field :name,
label: { text: "Location name (optional)", size: "m" },
hint: { text: "This is how you refer to this location within your organisation" } %>
label: { text: I18n.t("questions.location.name"), size: "m" },
hint: { text: I18n.t("hints.location.name") } %>
<%= f.govuk_number_field :units,
label: { text: "Total number of units at this location", size: "m" },
label: { text: I18n.t("questions.location.units"), size: "m" },
width: 2,
hint: { text: "A unit can be a bedroom in a shared house or flat, or a house with 4 bedrooms. Do not include bedrooms used for wardens, managers, volunteers or sleep-in staff.s" },
hint: { text: I18n.t("hints.location.units") },
autofocus: true %>
<% type_of_units_selection = Location.type_of_units.keys.map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %>
@ -35,7 +35,7 @@
type_of_units_selection,
:id,
:name,
legend: { text: "What is this type of scheme?", size: "m" } %>
legend: { text: I18n.t("questions.location.type_of_unit"), size: "m" } %>
<% wheelchair_user_selection = Location.wheelchair_adaptations.keys.map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %>
@ -43,8 +43,12 @@
wheelchair_user_selection,
:id,
:name,
hint: { text: "This includes stairlifts, ramps, level-access showers or grab rails" },
legend: { text: "Are the majority of units in this location built or adapted to wheelchair-user standards?", size: "m" } %>
hint: { text: I18n.t("hints.location.wheelchair_adaptation") },
legend: { text: I18n.t("questions.location.wheelchair_adaptation"), size: "m" } %>
<%= f.govuk_date_field :startdate,
legend: { text: I18n.t("questions.location.startdate"), size: "m" },
width: 20 %>
<%= govuk_section_break(visible: true, size: "m") %>
@ -55,7 +59,7 @@
:id,
:name,
inline: true,
legend: { text: "Do you want to add another location?", size: "m" } %>
legend: { text: I18n.t("questions.location.add_another_location"), size: "m" } %>
<%= f.govuk_submit "Save and continue" %>
</div>

66
config/forms/2021_2022.json

@ -1078,7 +1078,8 @@
"step": 1,
"width": 2
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"no_females_pregnant_household_lead_hhmemb_value_check": {
"depends_on": [{ "no_females_in_a_pregnant_household?": true }],
@ -1120,11 +1121,7 @@
}
},
"females_in_soft_age_range_in_pregnant_household_lead_hhmemb_value_check": {
"depends_on": [
{
"female_in_pregnant_household_in_soft_validation_range?": true
}
],
"depends_on": [{ "female_in_pregnant_household_in_soft_validation_range?": true }],
"title_text": {
"translation": "soft_validations.pregnancy.title",
"arguments": [
@ -1207,7 +1204,8 @@
"value": "Not known"
}
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"no_females_pregnant_household_lead_age_value_check": {
"depends_on": [{ "no_females_in_a_pregnant_household?": true }],
@ -1249,11 +1247,7 @@
}
},
"females_in_soft_age_range_in_pregnant_household_lead_age_value_check": {
"depends_on": [
{
"female_in_pregnant_household_in_soft_validation_range?": true
}
],
"depends_on": [{ "female_in_pregnant_household_in_soft_validation_range?": true }],
"title_text": {
"translation": "soft_validations.pregnancy.title",
"arguments": [
@ -1318,7 +1312,8 @@
}
}
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"no_females_pregnant_household_lead_value_check": {
"depends_on": [{ "no_females_in_a_pregnant_household?": true }],
@ -1360,11 +1355,7 @@
}
},
"females_in_soft_age_range_in_pregnant_household_lead_value_check": {
"depends_on": [
{
"female_in_pregnant_household_in_soft_validation_range?": true
}
],
"depends_on": [{ "female_in_pregnant_household_in_soft_validation_range?": true }],
"title_text": {
"translation": "soft_validations.pregnancy.title",
"arguments": [
@ -1435,7 +1426,8 @@
}
}
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"lead_tenant_ethnic_background_arab": {
"header": "",
@ -1456,11 +1448,7 @@
}
}
},
"depends_on": [
{
"ethnic_group": 4
}
]
"depends_on": [{ "ethnic_group": 4 }]
},
"lead_tenant_ethnic_background_asian": {
"header": "",
@ -1490,11 +1478,7 @@
}
}
},
"depends_on": [
{
"ethnic_group": 2
}
]
"depends_on": [{ "ethnic_group": 2 }]
},
"lead_tenant_ethnic_background_black": {
"header": "",
@ -1518,11 +1502,7 @@
}
}
},
"depends_on": [
{
"ethnic_group": 3
}
]
"depends_on": [{ "ethnic_group": 3 }]
},
"lead_tenant_ethnic_background_mixed": {
"header": "",
@ -1549,11 +1529,7 @@
}
}
},
"depends_on": [
{
"ethnic_group": 1
}
]
"depends_on": [{ "ethnic_group": 1 }]
},
"lead_tenant_ethnic_background_white": {
"header": "",
@ -1580,11 +1556,7 @@
}
}
},
"depends_on": [
{
"ethnic_group": 0
}
]
"depends_on": [{ "ethnic_group": 0 }]
},
"lead_tenant_nationality": {
"header": "",
@ -1650,7 +1622,8 @@
}
}
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"lead_tenant_working_situation": {
"header": "",
@ -1697,7 +1670,8 @@
}
}
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"lead_tenant_under_retirement_value_check": {
"depends_on": [{ "person_1_retired_under_soft_min_age?": true }],

70
config/forms/2022_2023.json

@ -1113,7 +1113,8 @@
"step": 1,
"width": 2
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"no_females_pregnant_household_lead_hhmemb_value_check": {
"depends_on": [{ "no_females_in_a_pregnant_household?": true }],
@ -1155,11 +1156,7 @@
}
},
"females_in_soft_age_range_in_pregnant_household_lead_hhmemb_value_check": {
"depends_on": [
{
"female_in_pregnant_household_in_soft_validation_range?": true
}
],
"depends_on": [{ "female_in_pregnant_household_in_soft_validation_range?": true }],
"title_text": {
"translation": "soft_validations.pregnancy.title",
"arguments": [
@ -1242,7 +1239,8 @@
"value": "Not known"
}
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"no_females_pregnant_household_lead_age_value_check": {
"depends_on": [{ "no_females_in_a_pregnant_household?": true }],
@ -1284,11 +1282,7 @@
}
},
"females_in_soft_age_range_in_pregnant_household_lead_age_value_check": {
"depends_on": [
{
"female_in_pregnant_household_in_soft_validation_range?": true
}
],
"depends_on": [{ "female_in_pregnant_household_in_soft_validation_range?": true }],
"title_text": {
"translation": "soft_validations.pregnancy.title",
"arguments": [
@ -1353,7 +1347,8 @@
}
}
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"no_females_pregnant_household_lead_value_check": {
"depends_on": [{ "no_females_in_a_pregnant_household?": true }],
@ -1395,11 +1390,7 @@
}
},
"females_in_soft_age_range_in_pregnant_household_lead_value_check": {
"depends_on": [
{
"female_in_pregnant_household_in_soft_validation_range?": true
}
],
"depends_on": [{ "female_in_pregnant_household_in_soft_validation_range?": true }],
"title_text": {
"translation": "soft_validations.pregnancy.title",
"arguments": [
@ -1470,7 +1461,8 @@
}
}
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"lead_tenant_ethnic_background_arab": {
"header": "",
@ -1491,11 +1483,7 @@
}
}
},
"depends_on": [
{
"ethnic_group": 4
}
]
"depends_on": [{ "ethnic_group": 4 }]
},
"lead_tenant_ethnic_background_asian": {
"header": "",
@ -1525,11 +1513,7 @@
}
}
},
"depends_on": [
{
"ethnic_group": 2
}
]
"depends_on": [{ "ethnic_group": 2 }]
},
"lead_tenant_ethnic_background_black": {
"header": "",
@ -1553,11 +1537,7 @@
}
}
},
"depends_on": [
{
"ethnic_group": 3
}
]
"depends_on": [{ "ethnic_group": 3 }]
},
"lead_tenant_ethnic_background_mixed": {
"header": "",
@ -1584,11 +1564,7 @@
}
}
},
"depends_on": [
{
"ethnic_group": 1
}
]
"depends_on": [{ "ethnic_group": 1 }]
},
"lead_tenant_ethnic_background_white": {
"header": "",
@ -1615,11 +1591,7 @@
}
}
},
"depends_on": [
{
"ethnic_group": 0
}
]
"depends_on": [{ "ethnic_group": 0 }]
},
"lead_tenant_nationality": {
"header": "",
@ -1649,7 +1621,8 @@
}
}
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"lead_tenant_working_situation": {
"header": "",
@ -1696,7 +1669,8 @@
}
}
}
}
},
"depends_on": [{ "declaration": 1 }]
},
"lead_tenant_under_retirement_value_check": {
"depends_on": [{ "person_1_retired_under_soft_min_age?": true }],
@ -1743,9 +1717,7 @@
}
},
"lead_tenant_over_retirement_value_check": {
"depends_on": [
{ "person_1_not_retired_over_soft_max_age?": true }
],
"depends_on": [{ "person_1_not_retired_over_soft_max_age?": true }],
"title_text": {
"translation": "soft_validations.retirement.max.title",
"arguments": [

17
config/locales/en.yml

@ -51,6 +51,7 @@ en:
organisation:
name_missing: "Enter the organisation’s name"
provider_type_missing: "Select the organisation’s type"
not_answered: "You must answer %{question}"
other_field_missing: "If %{main_field_label} is other then %{other_field_label} must be provided"
other_field_not_required: "%{other_field_label} must not be provided if %{main_field_label} was not other"
@ -287,6 +288,22 @@ en:
code_required: "Security code is required"
code_incorrect: "Security code is incorrect"
questions:
location:
name: "Location name (optional)"
units: "Total number of units at this location"
type_of_unit: "What is this type of scheme?"
wheelchair_adaptation: "Are the majority of units in this location built or adapted to wheelchair-user standards?"
startdate: "When did the first property in this location become available under this scheme?"
add_another_location: "Do you want to add another location?"
hints:
location:
postcode: "For example, SW1P 4DF."
name: "This is how you refer to this location within your organisation"
units: "A unit can be a bedroom in a shared house or flat, or a house with 4 bedrooms. Do not include bedrooms used for wardens, managers, volunteers or sleep-in staff."
wheelchair_adaptation: "This includes stairlifts, ramps, level-access showers or grab rails"
test:
one_argument: "This is based on the tenant’s work situation: %{ecstat1}"
title_text:

7
db/migrate/20220713095713_add_mobility_type_to_locations.rb

@ -0,0 +1,7 @@
class AddMobilityTypeToLocations < ActiveRecord::Migration[7.0]
def change
change_table :locations, bulk: true do |t|
t.column :mobility_type, :string
end
end
end

7
db/migrate/20220714080044_add_location_startdate.rb

@ -0,0 +1,7 @@
class AddLocationStartdate < ActiveRecord::Migration[7.0]
def change
change_table :locations, bulk: true do |t|
t.column :startdate, :datetime
end
end
end

5
db/migrate/20220715133937_remove_county_from_location.rb

@ -0,0 +1,5 @@
class RemoveCountyFromLocation < ActiveRecord::Migration[7.0]
def change
remove_column :locations, :county, :string
end
end

5
db/schema.rb

@ -11,7 +11,7 @@
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2022_07_12_143943) do
ActiveRecord::Schema[7.0].define(version: 2022_07_15_133937) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -245,13 +245,14 @@ ActiveRecord::Schema[7.0].define(version: 2022_07_12_143943) do
t.integer "wheelchair_adaptation"
t.bigint "scheme_id", null: false
t.string "name"
t.string "county"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "units"
t.integer "type_of_unit"
t.string "old_id"
t.integer "old_visible_id"
t.string "mobility_type"
t.datetime "startdate", precision: nil
t.index ["old_id"], name: "index_locations_on_old_id", unique: true
t.index ["scheme_id"], name: "index_locations_on_scheme_id"
end

5
spec/factories/location.rb

@ -1,12 +1,11 @@
FactoryBot.define do
factory :location do
location_code { Faker::Name.initials(number: 10) }
postcode { Faker::Address.postcode.delete(" ") }
name { Faker::Address.street_name }
type_of_unit { [1, 2, 3, 4, 6, 7].sample }
type_of_building { "Purpose built" }
wheelchair_adaptation { 0 }
county { Faker::Address.state }
mobility_type { %w[A M N W X].sample }
wheelchair_adaptation { 2 }
scheme
end
end

27
spec/features/form/accessible_autocomplete_spec.rb

@ -14,6 +14,7 @@ RSpec.describe "Accessible Automcomplete" do
is_la_inferred: false,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
created_by: user,
)
end
@ -58,6 +59,32 @@ RSpec.describe "Accessible Automcomplete" do
end
end
context "when searching schemes" do
let(:scheme) { FactoryBot.create(:scheme, owning_organisation_id: case_log.created_by.organisation_id, primary_client_group: "Q", secondary_client_group: "P") }
before do
FactoryBot.create(:location, scheme:, postcode: "W6 0ST")
FactoryBot.create(:location, scheme:, postcode: "SE6 1LB")
case_log.update!(needstype: 2)
visit("/logs/#{case_log.id}/scheme")
end
it "can match on synonyms", js: true do
find("#case-log-scheme-id-field").click.native.send_keys("w", "6", :down, :enter)
expect(find("#case-log-scheme-id-field").value).to eq(scheme.service_name)
end
it "displays appended text next to the options", js: true do
find("#case-log-scheme-id-field").click.native.send_keys("w", "6", :down, :enter)
expect(find(".autocomplete__option__append", visible: :hidden, text: /(2 locations)/)).to be_present
end
it "displays hint text under the options", js: true do
find("#case-log-scheme-id-field").click.native.send_keys("w", "6", :down, :enter)
expect(find(".autocomplete__option__hint", visible: :hidden, text: /Young people at risk, Young people leaving care/)).to be_present
end
end
it "has the correct option selected if one has been saved" do
case_log.update!(postcode_known: 0, previous_la_known: 1, prevloc: "E07000178")
visit("/logs/#{case_log.id}/accessible-select")

2
spec/features/form/check_answers_page_spec.rb

@ -136,7 +136,7 @@ RSpec.describe "Form Check Answers Page" do
first("a", text: /Change/).click
uncheck("case-log-accessibility-requirements-housingneeds-c-field")
check("case-log-accessibility-requirements-housingneeds-b-field")
click_button("Save and continue")
click_button("Save changes")
expect(page).to have_current_path("/logs/#{empty_case_log.id}/household-needs/check-answers")
end
end

76
spec/features/form/form_navigation_spec.rb

@ -13,6 +13,15 @@ RSpec.describe "Form Navigation" do
created_by: user,
)
end
let(:empty_case_log) do
FactoryBot.create(
:case_log,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
created_by: user,
)
end
let(:id) { case_log.id }
let(:question_answers) do
{
@ -47,11 +56,23 @@ RSpec.describe "Form Navigation" do
pages = question_answers.map { |_key, val| val[:path] }
pages[0..-2].each_with_index do |val, index|
visit("/logs/#{id}/#{val}")
click_button("Save and continue")
click_link("Skip for now")
expect(page).to have_current_path("/logs/#{id}/#{pages[index + 1]}")
end
end
it "a question page has a Skip for now link that lets you move on to the next question without inputting anything" do
visit("logs/#{empty_case_log.id}/tenant-code-test")
click_link(text: "Skip for now")
expect(page).to have_current_path("/logs/#{empty_case_log.id}/person-1-age")
end
it "routes to check answers when skipping on the last page in the form" do
visit("logs/#{empty_case_log.id}/propcode")
click_link(text: "Skip for now")
expect(page).to have_current_path("/logs/#{empty_case_log.id}/household-characteristics/check-answers")
end
describe "Back link directs correctly", js: true do
it "go back to tasklist page from tenant code" do
visit("/logs/#{id}")
@ -89,4 +110,57 @@ RSpec.describe "Form Navigation" do
end
end
end
describe "Editing a log" do
it "a question page has a link allowing you to cancel your input and return to the check answers page" do
visit("logs/#{id}/tenant-code-test?referrer=check_answers")
click_link(text: "Cancel")
expect(page).to have_current_path("/logs/#{id}/setup/check-answers")
end
context "when clicking save and continue on a mandatory question with no input" do
let(:id) { empty_case_log.id }
it "shows a validation error on radio questions" do
visit("/logs/#{id}/renewal")
click_button("Save and continue")
expect(page).to have_selector("#error-summary-title")
expect(page).to have_selector("#case-log-renewal-error")
expect(page).to have_title("Error")
end
it "shows a validation error on date questions" do
visit("/logs/#{id}/tenancy-start-date")
click_button("Save and continue")
expect(page).to have_selector("#error-summary-title")
expect(page).to have_selector("#case-log-startdate-error")
expect(page).to have_title("Error")
end
context "when the page has a main and conditional question" do
context "when the conditional question is required but not answered" do
it "shows a validation error for the conditional question" do
visit("/logs/#{id}/armed-forces")
choose("case-log-armedforces-1-field", allow_label_click: true)
click_button("Save and continue")
expect(page).to have_selector("#error-summary-title")
expect(page).to have_selector("#case-log-leftreg-error")
expect(page).to have_title("Error")
end
end
end
end
context "when clicking save and continue on an optional question with no input" do
let(:id) { empty_case_log.id }
it "does not show a validation error" do
visit("/logs/#{id}/tenant-code")
click_button("Save and continue")
expect(page).not_to have_selector("#error-summary-title")
expect(page).not_to have_title("Error")
expect(page).to have_current_path("/logs/#{id}/property-reference")
end
end
end
end

1
spec/features/form/page_routing_spec.rb

@ -43,6 +43,7 @@ RSpec.describe "Form Page Routing" do
choose("case-log-preg-occ-2-field", allow_label_click: true)
click_button("Save and continue")
expect(page).to have_current_path("/logs/#{id}/conditional-question-no-page")
choose("case-log-cbl-0-field", allow_label_click: true)
click_button("Save and continue")
expect(page).to have_current_path("/logs/#{id}/conditional-question/check-answers")
end

4
spec/features/form/progressive_total_field_spec.rb

@ -33,6 +33,7 @@ RSpec.describe "Accessible Automcomplete" do
it "total displays despite error message", js: true do
visit("/logs/#{case_log.id}/rent")
choose("case-log-period-1-field", allow_label_click: true)
fill_in("case-log-brent-field", with: 500)
fill_in("case-log-scharge-field", with: 50)
fill_in("case-log-pscharge-field", with: 50)
@ -40,6 +41,9 @@ RSpec.describe "Accessible Automcomplete" do
expect(find("#case-log-tcharge-field").value).to eq("5600.00")
click_button("Save and continue")
expect(page).to have_selector(".govuk-error-summary")
fill_in("case-log-scharge-field", with: nil)
fill_in("case-log-pscharge-field", with: nil)
fill_in("case-log-supcharg-field-error", with: nil)
fill_in("case-log-brent-field", with: 500)
expect(find("#case-log-tcharge-field").value).to eq("500.00")
fill_in("case-log-supcharg-field-error", with: 50)

1
spec/features/form/saving_data_spec.rb

@ -26,7 +26,6 @@ RSpec.describe "Form Saving Data" do
tenancycode: { type: "text", answer: "BZ737", path: "tenant-code-test" },
age1: { type: "numeric", answer: 25, path: "person_1_age" },
sex1: { type: "radio", answer: { "F" => "Female" }, path: "person_1_gender" },
hhmemb: { type: "numeric", answer: 3, path: "household_number_of_members" },
}
end

47
spec/features/schemes_spec.rb

@ -772,4 +772,51 @@ RSpec.describe "Schemes scheme Features" do
end
end
end
context "when selecting a scheme" do
let!(:user) { FactoryBot.create(:user, :data_coordinator, last_sign_in_at: Time.zone.now) }
let!(:schemes) { FactoryBot.create_list(:scheme, 5, owning_organisation: user.organisation) }
let(:location) { FactoryBot.create(:location, scheme: schemes[2]) }
let!(:case_log) { FactoryBot.create(:case_log, created_by: user, needstype: 2) }
before do
Timecop.freeze(Time.utc(2022, 6, 3))
location.update!(startdate: nil)
FactoryBot.create(:location, scheme: schemes[0], startdate: nil)
FactoryBot.create(:location, scheme: schemes[1], startdate: nil)
FactoryBot.create(:location, scheme: schemes[1], startdate: nil)
FactoryBot.create(:location, scheme: schemes[1], startdate: Time.utc(2023, 6, 3))
visit("/logs")
fill_in("user[email]", with: user.email)
fill_in("user[password]", with: user.password)
click_button("Sign in")
end
after do
Timecop.unfreeze
end
it "does not display the schemes without a location" do
visit("/logs/#{case_log.id}/scheme")
expect(find("#case-log-scheme-id-field").all("option").count).to eq(3)
end
it "does not display the schemes with a location with a startdate in the future" do
location.update!(startdate: Time.utc(2022, 7, 4))
visit("/logs/#{case_log.id}/scheme")
expect(find("#case-log-scheme-id-field").all("option").count).to eq(2)
end
it "does display the schemes with a location with a startdate in the past" do
location.update!(startdate: Time.utc(2022, 5, 2))
visit("/logs/#{case_log.id}/scheme")
expect(find("#case-log-scheme-id-field").all("option").count).to eq(3)
end
it "does display the schemes with a location with a startdate being today" do
location.update!(startdate: Time.utc(2022, 6, 3))
visit("/logs/#{case_log.id}/scheme")
expect(find("#case-log-scheme-id-field").all("option").count).to eq(3)
end
end
end

524
spec/fixtures/imports/case_logs/0b4a68df-30cc-474a-93c0-a56ce8fdad3b.xml vendored

@ -0,0 +1,524 @@
<Group xmlns="http://data.gov.uk/core/logs/2021-CORE-SR-SH" xmlns:app="http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:meta="http://data.gov.uk/core/metadata" xmlns:svc="http://www.w3.org/2007/app" xmlns:xf="http://www.w3.org/2002/xforms" xmlns:xfimpl="http://www.w3.org/2002/xforms/implementation" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xxf="http://orbeon.org/oxf/xml/xforms" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<meta:metadata xmlns:es="http://www.ecmascript.org/" xmlns:xqx="http://www.w3.org/2005/XQueryX" xmlns:XSLT="http://www.w3.org/1999/XSL/Transform/compile">
<meta:form-name>2021-CORE-SR-SH</meta:form-name>
<meta:document-id>0b4a68df-30cc-474a-93c0-a56ce8fdad3b</meta:document-id>
<meta:owner-user-id>c3061a2e6ea0b702e6f6210d5c52d2a92612d2aa</meta:owner-user-id>
<meta:owner-institution-id>7c5bd5fb549c09z2c55d9cb90d7ba84927e64618</meta:owner-institution-id>
<meta:managing-institution-id>7c5bd5fb549c09z2c55d9cb90d7ba84927e64618</meta:managing-institution-id>
<meta:created-date>2022-01-05T12:50:20.39153Z</meta:created-date>
<meta:modified-date>2022-01-05T12:50:20.39153Z</meta:modified-date>
<meta:status>submitted-valid</meta:status>
<meta:reporting-year>2021</meta:reporting-year>
<meta:upload-method>Manual Entry</meta:upload-method>
<meta:schema assert-valid="true"/>
<meta:rules assert-valid="true"/>
</meta:metadata>
<Group>
<Qdp>Yes</Qdp>
<KeyDate>2021-11-05</KeyDate>
<FORM>123456</FORM>
<Landlord source-value="2">2 Local Authority</Landlord>
<Group>
<_1cmangroupcode>123</_1cmangroupcode>
<_1cschemecode>10</_1cschemecode>
<schPC/>
<Q1e>3 No</Q1e>
</Group>
</Group>
<Group>
<_2a>2 No</_2a>
<Q2b>1 Secure (inc flexible)</Q2b>
<Q2ba/>
<_2bTenCode>14044912001</_2bTenCode>
<_2cYears>2</_2cYears>
</Group>
<Group>
<P1Age override-field="">72</P1Age>
<P1AR/>
<P1Sex override-field="">Female</P1Sex>
<P1Eco>5) Retired</P1Eco>
<P1Eth>1 White: English/Scottish/Welsh/Northern Irish/British</P1Eth>
<P1Nat>1 UK national resident in UK</P1Nat>
<P2Age override-field="">74</P2Age>
<P2AR/>
<P2Sex override-field="">Male</P2Sex>
<P2Rel>Partner</P2Rel>
<P2Eco>5) Retired</P2Eco>
<P3Age override-field=""/>
<P3AR/>
<P3Sex override-field=""/>
<P3Rel/>
<P3Eco/>
<P4Age override-field=""/>
<P4AR/>
<P4Sex override-field=""/>
<P4Rel/>
<P4Eco/>
<P5Age override-field=""/>
<P5AR/>
<P5Sex override-field=""/>
<P5Rel/>
<P5Eco/>
<P6Age override-field=""/>
<P6AR/>
<P6Sex override-field=""/>
<P6Rel/>
<P6Eco/>
<P7Age override-field=""/>
<P7AR/>
<P7Sex override-field=""/>
<P7Rel/>
<P7Eco/>
<P8Age override-field=""/>
<P8AR/>
<P8Sex override-field=""/>
<P8Rel/>
<P8Eco/>
<Group>
<ArmedF>2 No</ArmedF>
<LeftAF/>
<Inj/>
<Preg override-field="">2 No</Preg>
</Group>
<Group>
<Q6Ben>9 Not in receipt of either UC or HB</Q6Ben>
</Group>
<Group>
<Q7Ben>2 Some</Q7Ben>
<Q8Refused>Refused</Q8Refused>
<Q8Money override-field=""/>
<Q8a/>
</Group>
<Group>
<Q9a>13 Property unsuitable because of ill health / disability</Q9a>
<Q9aa/>
</Group>
<Group>
<_9b override-field="">2 No</_9b>
<Q10-a/>
<Q10-b/>
<Q10-c/>
<Q10-f/>
<Q10-g>Yes</Q10-g>
<Q10-h/>
<Q10ia>1 Yes</Q10ia>
<Q10ib-1/>
<Q10ib-2/>
<Q10ib-3>Yes</Q10ib-3>
<Q10ib-4/>
<Q10ib-5/>
<Q10ib-6/>
<Q10ib-7/>
<Q10ib-8/>
<Q10ib-9/>
<Q10ib-10/>
<Q11 override-field="">26 Owner occupation (private)</Q11>
<Q12a>DLUHC</Q12a>
<Q12aONS>E08000035</Q12aONS>
<Q12b override-field="">S80 4DJ</Q12b>
<Q12bnot/>
<Q12c>5 5 years or more</Q12c>
<Q12d>9 3 years but under 4 years</Q12d>
</Group>
<Group>
<Q13>1 Not homeless</Q13>
<Q14a>2 No</Q14a>
<Q14b1/>
<Q14b2/>
<Q14b3/>
<Q14b4/>
<Q14b5/>
</Group>
<Group>
<Q15CBL>1 Yes</Q15CBL>
<Q15CHR>1 Yes</Q15CHR>
<Q15CAP>1 Yes</Q15CAP>
</Group>
<Group>
<Q16>2 Tenant applied direct (no referral or nomination)</Q16>
</Group>
</Group>
<Group>
<Q17>7 Weekly for 48 weeks</Q17>
<Q18ai override-field="true">125.00</Q18ai>
<Q18aii override-field=""/>
<Q18aiii override-field=""/>
<Q18aiv override-field="">7.00</Q18aiv>
<Q18av override-field="">132.00</Q18av>
<Q18b override-field=""/>
<Q18c/>
<Q18d/>
<Q18dyes override-field=""/>
<Q19void>2021-08-24</Q19void>
<Q19repair/>
<Q19supsch/>
<Q20 override-field="">0</Q20>
<Q21a>14044912</Q21a>
</Group>
<Group>
<Q25>1 Yes</Q25>
<Q27>15 First let of newbuild property</Q27>
</Group>
<Group>
<F1Age>0</F1Age>
<F2Age>0</F2Age>
<F3Age>0</F3Age>
<F4Age>0</F4Age>
<F5Age>0</F5Age>
<F6Age>0</F6Age>
<F7Age>0</F7Age>
<F8Age>0</F8Age>
<FAge>0</FAge>
<F1>1</F1>
<F2>0</F2>
<F3>0</F3>
<F4>0</F4>
<F5>0</F5>
<F6>0</F6>
<F7>0</F7>
<F8>0</F8>
<F>1</F>
<P1100>0</P1100>
<P2100>0</P2100>
<P3100>0</P3100>
<P4100>0</P4100>
<P5100>0</P5100>
<P6100>0</P6100>
<P7100>0</P7100>
<P8100>0</P8100>
<_100>0</_100>
<P170>1</P170>
<P270>1</P270>
<P370>0</P370>
<P470>0</P470>
<P570>0</P570>
<P670>0</P670>
<P770>0</P770>
<P870>0</P870>
<_70>1</_70>
<P1PT>0</P1PT>
<P2PT>0</P2PT>
<P3PT>0</P3PT>
<P4PT>0</P4PT>
<P5PT>0</P5PT>
<P6PT>0</P6PT>
<P7PT>0</P7PT>
<P8PT>0</P8PT>
<PT>0</PT>
<P1FT>0</P1FT>
<P2FT>0</P2FT>
<P3FT>0</P3FT>
<P4FT>0</P4FT>
<P5FT>0</P5FT>
<P6FT>0</P6FT>
<P7FT>0</P7FT>
<P8FT>0</P8FT>
<FT>0</FT>
<P1Stud>0</P1Stud>
<P2Stud>0</P2Stud>
<P3Stud>0</P3Stud>
<P4Stud>0</P4Stud>
<P5Stud>0</P5Stud>
<P6Stud>0</P6Stud>
<P7Stud>0</P7Stud>
<P8Stud>0</P8Stud>
<Stud>0</Stud>
<P2Child>0</P2Child>
<P3Child>0</P3Child>
<P4Child>0</P4Child>
<P5Child>0</P5Child>
<P6Child>0</P6Child>
<P7Child>0</P7Child>
<P8Child>0</P8Child>
<Child>0</Child>
<P2Partner>1</P2Partner>
<P3Partner>0</P3Partner>
<P4Partner>0</P4Partner>
<P5Partner>0</P5Partner>
<P6Partner>0</P6Partner>
<P7Partner>0</P7Partner>
<P8Partner>0</P8Partner>
<Partner>1</Partner>
<Q1cV1>1</Q1cV1>
<Q1cV2>1</Q1cV2>
<Q1cVT>2</Q1cVT>
<P1Adult>1</P1Adult>
<P2Adult>1</P2Adult>
<P3Adult>0</P3Adult>
<P4Adult>0</P4Adult>
<P5Adult>0</P5Adult>
<P6Adult>0</P6Adult>
<P7Adult>0</P7Adult>
<P8Adult>0</P8Adult>
<PAdultT>2</PAdultT>
<P2PAge>74</P2PAge>
<P3PAge>0</P3PAge>
<P4PAge>0</P4PAge>
<P5PAge>0</P5PAge>
<P6PAge>0</P6PAge>
<P7PAge>0</P7PAge>
<P8PAge>0</P8PAge>
<PAGE>74</PAGE>
<P2ChildAge>0</P2ChildAge>
<P3ChildAge>0</P3ChildAge>
<P4ChildAge>0</P4ChildAge>
<P5ChildAge>0</P5ChildAge>
<P6ChildAge>0</P6ChildAge>
<P7ChildAge>0</P7ChildAge>
<P8ChildAge>0</P8ChildAge>
<ChildAgeMin>0</ChildAgeMin>
<AgeDiff1>72</AgeDiff1>
<AgeDiff2>0</AgeDiff2>
<AgeDiff3>74</AgeDiff3>
<TODAY>2022-01-05Z</TODAY>
<FutureLimit>2022-01-20Z</FutureLimit>
<minmax1/>
<minmax2/>
<minmax3/>
<minmax4/>
<minmax5/>
<minmax6/>
<minmax7/>
<minmax8/>
<minmax9/>
<minmax0/>
<minmax10/>
<minmaxT/>
<Q10av>0</Q10av>
<Q10bv>0</Q10bv>
<Q10cv>0</Q10cv>
<Q10fv>0</Q10fv>
<Q10gv>20</Q10gv>
<Q10hv>0</Q10hv>
<Q10Validate>20</Q10Validate>
<Q2bv>A</Q2bv>
<P2Agev>1</P2Agev>
<P2Sexv>1</P2Sexv>
<P2Relv>1</P2Relv>
<P2Ecov>1</P2Ecov>
<P2valid>4</P2valid>
<P3Agev>0</P3Agev>
<P3Sexv>0</P3Sexv>
<P3Relv>0</P3Relv>
<P3Ecov>0</P3Ecov>
<P3valid>0</P3valid>
<P4Agev>0</P4Agev>
<P4Sexv>0</P4Sexv>
<P4Relv>0</P4Relv>
<P4Ecov>0</P4Ecov>
<P4valid>0</P4valid>
<P5Agev>0</P5Agev>
<P5Sexv>0</P5Sexv>
<P5Relv>0</P5Relv>
<P5Ecov>0</P5Ecov>
<P5valid>0</P5valid>
<P6Agev>0</P6Agev>
<P6Sexv>0</P6Sexv>
<P6Relv>0</P6Relv>
<P6Ecov>0</P6Ecov>
<P6valid>0</P6valid>
<P7Agev>0</P7Agev>
<P7Sexv>0</P7Sexv>
<P7Relv>0</P7Relv>
<P7Ecov>0</P7Ecov>
<P7valid>0</P7valid>
<P8Agev>0</P8Agev>
<P8Sexv>0</P8Sexv>
<P8Relv>0</P8Relv>
<P8Ecov>0</P8Ecov>
<P8valid>0</P8valid>
<Q14b1v>0</Q14b1v>
<Q14b2v>0</Q14b2v>
<Q14b3v>0</Q14b3v>
<Q14b4v>0</Q14b4v>
<Q14b5v>0</Q14b5v>
<Q14bv>0</Q14bv>
<P2Other>0</P2Other>
<P3Other>0</P3Other>
<P4Other>0</P4Other>
<P5Other>0</P5Other>
<P6Other>0</P6Other>
<P7Other>0</P7Other>
<P8Other>0</P8Other>
<Other>0</Other>
<P2ARefused>0</P2ARefused>
<P3ARefused>0</P3ARefused>
<P4ARefused>0</P4ARefused>
<P5ARefused>0</P5ARefused>
<P6ARefused>0</P6ARefused>
<P7ARefused>0</P7ARefused>
<P8ARefused>0</P8ARefused>
<TAREUSED>0</TAREUSED>
<P2RRefused>0</P2RRefused>
<P3RRefused>0</P3RRefused>
<P4RRefused>0</P4RRefused>
<P5RRefused>0</P5RRefused>
<P6RRefused>0</P6RRefused>
<P7RRefused>0</P7RRefused>
<P8RRefused>0</P8RRefused>
<TotRRefused>0</TotRRefused>
<TOTREFUSED>0</TOTREFUSED>
</Group>
<Group>
<ChildBen>0.00</ChildBen>
<TOTADULT>2</TOTADULT>
<NEW_OLD>1 New Tenant</NEW_OLD>
<WCHCHRG/>
<VACDAYS>73</VACDAYS>
<HHMEMB>2</HHMEMB>
<HHTYPEP1A>0</HHTYPEP1A>
<HHTYPEP2A>0</HHTYPEP2A>
<HHTYPEP3A>0</HHTYPEP3A>
<HHTYPEP4A>0</HHTYPEP4A>
<HHTYPEP5A>0</HHTYPEP5A>
<HHTYPEP6A>0</HHTYPEP6A>
<HHTYPEP7A>0</HHTYPEP7A>
<HHTYPEP8A>0</HHTYPEP8A>
<TADULT>0</TADULT>
<HHTYPEP1E>1</HHTYPEP1E>
<HHTYPEP2E>1</HHTYPEP2E>
<HHTYPEP3E>0</HHTYPEP3E>
<HHTYPEP4E>0</HHTYPEP4E>
<HHTYPEP5E>0</HHTYPEP5E>
<HHTYPEP6E>0</HHTYPEP6E>
<HHTYPEP7E>0</HHTYPEP7E>
<HHTYPEP8E>0</HHTYPEP8E>
<TELDER>2</TELDER>
<HHTYPEP1C>0</HHTYPEP1C>
<HHTYPEP2C>0</HHTYPEP2C>
<HHTYPEP3C>0</HHTYPEP3C>
<HHTYPEP4C>0</HHTYPEP4C>
<HHTYPEP5C>0</HHTYPEP5C>
<HHTYPEP6C>0</HHTYPEP6C>
<HHTYPEP7C>0</HHTYPEP7C>
<HHTYPEP8C>0</HHTYPEP8C>
<TCHILD>0</TCHILD>
<Q18aValid>1</Q18aValid>
<Q18bValid>0</Q18bValid>
<Q18cValid>0</Q18cValid>
<Q18Valid>1</Q18Valid>
<HHTYPE>2 = 2 Adults at least one is an Elder</HHTYPE>
<WEEKLYINC/>
<INCOME/>
<TYPEHB>15.00</TYPEHB>
<AFFRATE/>
<Weekinc/>
<LETTYPE>2 Local Authority</LETTYPE>
<PLOACODE/>
<OACODE/>
<GOVREG>E12000004</GOVREG>
<OWNINGORGID>1</OWNINGORGID>
<OWNINGORGNAME>DLUHC</OWNINGORGNAME>
<MANINGORGNAME>DLUHC</MANINGORGNAME>
<HCNUM>N/A</HCNUM>
<MANHCNUM>N/A</MANHCNUM>
<LAHA/>
<MANINGORGID>1</MANINGORGID>
<Q28same1>false</Q28same1>
<HBTYPE1/>
<HBTYPE2/>
<HBTYPE3/>
<HBTYPE4/>
<HBTYPE5/>
<HBTYPE6/>
<HBTYPE7/>
<HBTYPE8/>
<HBTYPE9/>
<HBTYPE10/>
<HBTYPE11/>
<HBTYPE12/>
<HBTYPE13/>
<HBTYPE14/>
<HBTYPE15>15</HBTYPE15>
<HBTYPE>15</HBTYPE>
<SCHEME>000001005048</SCHEME>
<MANTYPE>D</MANTYPE>
<UNITS>15</UNITS>
<UNITTYPE>6</UNITTYPE>
<SCHTYPE>7</SCHTYPE>
<REGHOME>1</REGHOME>
<SUPPORT>2</SUPPORT>
<MOBSTAND>A</MOBSTAND>
<INTSTAY>P</INTSTAY>
<CLIGRP1>M</CLIGRP1>
<CLIGRP2/>
<Q28Auth>DLUHC</Q28Auth>
<Q28ONS>E08000035</Q28ONS>
<Q28pc override-field="">S80 4QE</Q28pc>
<Q28same/>
<P1R>0</P1R>
<P2R>0</P2R>
<P3R>0</P3R>
<P4R>0</P4R>
<P5R>0</P5R>
<P6R>0</P6R>
<P7R>0</P7R>
<P8R>0</P8R>
<REFUSEDTOT>0</REFUSEDTOT>
<REFUSED/>
<WTSHORTFALL/>
<WTSHORTFALLHB/>
<WTSHORTFALLHE/>
<WRENT>115.38</WRENT>
<WTCHARGE>121.85</WTCHARGE>
<WSCHARGE/>
<WPSCHRGE/>
<WSUPCHRG>6.46</WSUPCHRG>
<WTSHORTFALL1/>
<WRENT1>115.38</WRENT1>
<WTCHARGE1>121.85</WTCHARGE1>
<WSCHARGE1/>
<WPSCHRGE1/>
<WSUPCHRG1>6.46</WSUPCHRG1>
</Group>
<Group>
<BSa>1</BSa>
<BSb>0</BSb>
<BSc>0</BSc>
<BScm>0</BScm>
<BScf>0</BScf>
<BSd>0</BSd>
<BSdm>0</BSdm>
<BSdf>0</BSdf>
<BSe>0</BSe>
<BSem>0</BSem>
<BSef>0</BSef>
<BSf>0</BSf>
<BSfm>0</BSfm>
<BSff>0</BSff>
<BSfmx>0</BSfmx>
<BSffx>0</BSffx>
<BEDROOMSTAND>1</BEDROOMSTAND>
<BEDMINUSBEDS/>
<WRENTreduced>115.38</WRENTreduced>
<NonDepDeduct>0</NonDepDeduct>
<RENTHB/>
<ChildAllowan>0</ChildAllowan>
<PrsnlAllowan>117.4</PrsnlAllowan>
<HousBenDisAl>10</HousBenDisAl>
<PAIDHB/>
<HCNETAF/>
<ChldAlloCat1>0</ChldAlloCat1>
<ChldAlloCat2>0</ChldAlloCat2>
<P2NnDepDedct>0</P2NnDepDedct>
<P3NnDepDedct>0</P3NnDepDedct>
<P4NnDepDedct>0</P4NnDepDedct>
<P5NnDepDedct>0</P5NnDepDedct>
<P6NnDepDedct>0</P6NnDepDedct>
<P7NnDepDedct>0</P7NnDepDedct>
<P8NnDepDedct>0</P8NnDepDedct>
<DAY>5</DAY>
<MONTH>11</MONTH>
<YEAR>2021</YEAR>
<VDAY>24</VDAY>
<VMONTH>8</VMONTH>
<VYEAR>2021</VYEAR>
<MRCDAY/>
<MRCMONTH/>
<MRCYEAR/>
<PPOSTC1>LS16</PPOSTC1>
<PPOSTC2>6FT</PPOSTC2>
<POSTCODE>LS16</POSTCODE>
<POSTCOD2>6FT</POSTCOD2>
</Group>
</Group>

15
spec/models/case_log_spec.rb

@ -1702,9 +1702,9 @@ RSpec.describe CaseLog do
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(:location) { FactoryBot.create(:location, scheme:, postcode: "M11AE", type_of_unit: 1, type_of_building: "Purpose built") }
let!(:supported_housing_case_log) do
let(:supported_housing_case_log) do
described_class.create!({
managing_organisation: owning_organisation,
owning_organisation:,
@ -1716,14 +1716,21 @@ RSpec.describe CaseLog do
})
end
before do
stub_request(:get, /api.postcodes.io/)
.to_return(status: 200, body: "{\"status\":200,\"result\":{\"admin_district\":\"Manchester\",\"codes\":{\"admin_district\": \"E08000003\"}}}", headers: {})
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)
expect(record_from_db["la"]).to be_nil
expect(supported_housing_case_log.la).to eq("E08000003")
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)
expect(record_from_db["postcode_full"]).to be_nil
expect(supported_housing_case_log.postcode_full).to eq("M11AE")
end
it "unittype_sh method returns the type_of_unit of the location" do

71
spec/models/form/setup/questions/location_id_spec.rb

@ -31,7 +31,74 @@ RSpec.describe Form::Setup::Questions::LocationId, type: :model do
expect(question).not_to be_derived
end
it "has the correct answer_options" do
expect(question.answer_options).to eq({})
context "when there are no locations" do
it "the answer_options is an empty hash" do
expect(question.answer_options).to eq({})
end
end
context "when getting available locations" do
let(:scheme) { FactoryBot.create(:scheme) }
let(:case_log) { FactoryBot.create(:case_log, scheme:, needstype: 2) }
context "when there are no locations" do
it "the displayed_answer_options is an empty hash" do
expect(question.displayed_answer_options(case_log)).to eq({})
end
end
context "when selected scheme has locations" do
before do
Timecop.freeze(Time.utc(2022, 5, 12))
end
after do
Timecop.unfreeze
end
context "and all the locations have a future startdate" do
before do
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 5, 13))
FactoryBot.create(:location, scheme:, startdate: Time.utc(2023, 1, 1))
end
it "the displayed_answer_options is an empty hash" do
expect(question.displayed_answer_options(case_log)).to eq({})
end
end
context "and the locations have a no startdate" do
before do
FactoryBot.create(:location, scheme:, startdate: nil)
FactoryBot.create(:location, scheme:, startdate: nil)
end
it "the displayed_answer_options shows the locations" do
expect(question.displayed_answer_options(case_log).count).to eq(2)
end
end
context "and the locations have a past startdate" do
before do
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 4, 10))
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 5, 12))
end
it "the displayed_answer_options shows the locations" do
expect(question.displayed_answer_options(case_log).count).to eq(2)
end
end
context "and some locations have a past startdate" do
before do
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 10, 10))
FactoryBot.create(:location, scheme:, startdate: Time.utc(2022, 5, 12))
end
it "the displayed_answer_options shows the active location" do
expect(question.displayed_answer_options(case_log).count).to eq(1)
end
end
end
end
end

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

@ -50,9 +50,21 @@ RSpec.describe Form::Setup::Questions::SchemeId, type: :model do
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
expected_answer = { scheme.id.to_s => scheme.service_name }
expect(question.displayed_answer_options(case_log)).to eq(expected_answer)
context "when a scheme with at least 1 location exists" do
before do
FactoryBot.create(:location, scheme:)
end
it "has the correct answer_options based on the schemes the user's organisation owns or manages" do
expected_answer = { scheme.id.to_s => scheme }
expect(question.displayed_answer_options(case_log)).to eq(expected_answer)
end
end
context "when there are no schemes with locations" do
it "returns an empty hash" do
expect(question.displayed_answer_options(case_log)).to eq({})
end
end
end
end

11
spec/models/location_spec.rb

@ -4,9 +4,20 @@ RSpec.describe Location, type: :model do
describe "#new" do
let(:location) { FactoryBot.build(:location) }
before do
stub_request(:get, /api.postcodes.io/)
.to_return(status: 200, body: "{\"status\":200,\"result\":{\"admin_district\":\"Manchester\",\"codes\":{\"admin_district\": \"E08000003\"}}}", headers: {})
end
it "belongs to an organisation" do
expect(location.scheme).to be_a(Scheme)
end
it "infers the local authority" do
location.postcode = "M1 1AE"
location.save!
expect(location.location_code).to eq("E08000003")
end
end
describe "#validate_postcode" do

4
spec/models/scheme_spec.rb

@ -46,6 +46,10 @@ RSpec.describe Scheme, type: :model do
end
context "when searching by all searchable fields" do
before do
location_2.update!(postcode: location_2.postcode.gsub(scheme_1.id.to_s, "0"))
end
it "returns case insensitive matching records" do
expect(described_class.search_by(scheme_1.id.to_s).count).to eq(1)
expect(described_class.search_by(scheme_1.id.to_s).first.id).to eq(scheme_1.id)

1
spec/rails_helper.rb

@ -80,6 +80,7 @@ RSpec.configure do |config|
Capybara.server = :puma, { Silent: true }
config.include Devise::Test::ControllerHelpers, type: :controller
config.include Devise::Test::ControllerHelpers, type: :view
config.include Devise::Test::IntegrationHelpers, type: :request
config.include ViewComponent::TestHelpers, type: :component
config.include Capybara::RSpecMatchers, type: :component

2
spec/requests/case_logs_controller_spec.rb

@ -778,7 +778,7 @@ RSpec.describe CaseLogsController, type: :request do
it "dowloads searched logs" do
get "/logs?search=#{case_log.id}", headers:, params: {}
csv = CSV.parse(response.body)
expect(csv.count).to eq(2)
expect(csv.count).to eq(CaseLog.search_by(case_log.id.to_s).count + 1)
end
context "when both filter and search applied" do

10
spec/requests/locations_controller_spec.rb

@ -90,7 +90,8 @@ 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, owning_organisation: user.organisation) }
let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ" } } }
let(:startdate) { Time.utc(2022, 2, 2) }
let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ", startdate: } } }
before do
sign_in user
@ -111,6 +112,7 @@ RSpec.describe LocationsController, type: :request do
expect(Location.last.units).to eq(5)
expect(Location.last.type_of_unit).to eq("Bungalow")
expect(Location.last.wheelchair_adaptation).to eq("No")
expect(Location.last.startdate).to eq(startdate)
end
context "when postcode is submitted with lower case" do
@ -389,8 +391,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, owning_organisation: user.organisation) }
let!(:location) { FactoryBot.create(:location, scheme:) }
let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ", page: "edit" } } }
let!(:location) { FactoryBot.create(:location, scheme:) }
let(:startdate) { Time.utc(2021, 1, 2) }
let(:params) { { location: { name: "Test", units: "5", type_of_unit: "Bungalow", wheelchair_adaptation: "No", add_another_location: "No", postcode: "ZZ1 1ZZ", startdate:, page: "edit" } } }
before do
sign_in user
@ -410,6 +413,7 @@ RSpec.describe LocationsController, type: :request do
expect(Location.last.units).to eq(5)
expect(Location.last.type_of_unit).to eq("Bungalow")
expect(Location.last.wheelchair_adaptation).to eq("No")
expect(Location.last.startdate).to eq(startdate)
end
context "when updating from edit-name page" do

68
spec/services/imports/case_logs_import_service_spec.rb

@ -16,6 +16,9 @@ RSpec.describe Imports::CaseLogsImportService do
end
before do
WebMock.stub_request(:get, /api.postcodes.io\/postcodes\/LS166FT/)
.to_return(status: 200, body: '{"status":200,"result":{"codes":{"admin_district":"E08000035"}}}', headers: {})
allow(Organisation).to receive(:find_by).and_return(nil)
allow(Organisation).to receive(:find_by).with(old_visible_id: organisation.old_visible_id.to_i).and_return(organisation)
@ -23,12 +26,17 @@ RSpec.describe Imports::CaseLogsImportService do
FactoryBot.create(:user, old_user_id: "c3061a2e6ea0b702e6f6210d5c52d2a92612d2aa", organisation:)
FactoryBot.create(:user, old_user_id: "e29c492473446dca4d50224f2bb7cf965a261d6f", organisation:)
# Scheme and Location
scheme = FactoryBot.create(:scheme, old_visible_id: 123)
FactoryBot.create(:location,
old_visible_id: 10,
scheme_id: scheme.id,
wheelchair_adaptation: 1,
postcode: "LS166FT")
# Stub the form handler to use the real form
allow(FormHandler.instance).to receive(:get_form).with("2021_2022").and_return(real_2021_2022_form)
allow(FormHandler.instance).to receive(:get_form).with("2022_2023").and_return(real_2022_2023_form)
WebMock.stub_request(:get, /api.postcodes.io\/postcodes\/LS166FT/)
.to_return(status: 200, body: '{"status":200,"result":{"codes":{"admin_district":"E08000035"}}}', headers: {})
end
context "when importing case logs" do
@ -36,11 +44,12 @@ RSpec.describe Imports::CaseLogsImportService do
let(:case_log_id) { "0ead17cb-1668-442d-898c-0d52879ff592" }
let(:case_log_id2) { "166fc004-392e-47a8-acb8-1c018734882b" }
let(:case_log_id3) { "00d2343e-d5fa-4c89-8400-ec3854b0f2b4" }
let(:case_log_id4) { "0b4a68df-30cc-474a-93c0-a56ce8fdad3b" }
before do
# Stub the S3 file listing and download
allow(storage_service).to receive(:list_files)
.and_return(%W[#{remote_folder}/#{case_log_id}.xml #{remote_folder}/#{case_log_id2}.xml #{remote_folder}/#{case_log_id3}.xml])
.and_return(%W[#{remote_folder}/#{case_log_id}.xml #{remote_folder}/#{case_log_id2}.xml #{remote_folder}/#{case_log_id3}.xml #{remote_folder}/#{case_log_id4}.xml])
allow(storage_service).to receive(:get_file_io)
.with("#{remote_folder}/#{case_log_id}.xml")
.and_return(open_file(fixture_directory, case_log_id), open_file(fixture_directory, case_log_id))
@ -50,6 +59,9 @@ RSpec.describe Imports::CaseLogsImportService do
allow(storage_service).to receive(:get_file_io)
.with("#{remote_folder}/#{case_log_id3}.xml")
.and_return(open_file(fixture_directory, case_log_id3), open_file(fixture_directory, case_log_id3))
allow(storage_service).to receive(:get_file_io)
.with("#{remote_folder}/#{case_log_id4}.xml")
.and_return(open_file(fixture_directory, case_log_id4), open_file(fixture_directory, case_log_id4))
end
it "successfully create all case logs" do
@ -57,45 +69,45 @@ RSpec.describe Imports::CaseLogsImportService do
expect(logger).not_to receive(:warn)
expect(logger).not_to receive(:info)
expect { case_log_service.create_logs(remote_folder) }
.to change(CaseLog, :count).by(3)
.to change(CaseLog, :count).by(4)
end
it "only updates existing case logs" do
expect(logger).not_to receive(:error)
expect(logger).not_to receive(:warn)
expect(logger).to receive(:info).with(/Updating case log/).exactly(3).times
expect(logger).to receive(:info).with(/Updating case log/).exactly(4).times
expect { 2.times { case_log_service.create_logs(remote_folder) } }
.to change(CaseLog, :count).by(3)
.to change(CaseLog, :count).by(4)
end
context "when there are status discrepancies" do
let(:case_log_id4) { "893ufj2s-lq77-42m4-rty6-ej09gh585uy1" }
let(:case_log_id5) { "5ybz29dj-l33t-k1l0-hj86-n4k4ma77xkcd" }
let(:case_log_file) { open_file(fixture_directory, case_log_id4) }
let(:case_log_id5) { "893ufj2s-lq77-42m4-rty6-ej09gh585uy1" }
let(:case_log_id6) { "5ybz29dj-l33t-k1l0-hj86-n4k4ma77xkcd" }
let(:case_log_file) { open_file(fixture_directory, case_log_id5) }
let(:case_log_xml) { Nokogiri::XML(case_log_file) }
before do
allow(storage_service).to receive(:get_file_io)
.with("#{remote_folder}/#{case_log_id4}.xml")
.and_return(open_file(fixture_directory, case_log_id4), open_file(fixture_directory, case_log_id4))
allow(storage_service).to receive(:get_file_io)
.with("#{remote_folder}/#{case_log_id5}.xml")
.and_return(open_file(fixture_directory, case_log_id5), open_file(fixture_directory, case_log_id5))
allow(storage_service).to receive(:get_file_io)
.with("#{remote_folder}/#{case_log_id6}.xml")
.and_return(open_file(fixture_directory, case_log_id6), open_file(fixture_directory, case_log_id6))
end
it "the logger logs a warning with the case log's old id/filename" do
expect(logger).to receive(:warn).with(/is not completed/).once
expect(logger).to receive(:warn).with(/Case log with old id:#{case_log_id4} is incomplete but status should be complete/).once
expect(logger).to receive(:warn).with(/Case log with old id:#{case_log_id5} is incomplete but status should be complete/).once
case_log_service.send(:create_log, case_log_xml)
end
it "on completion the ids of all logs with status discrepancies are logged in a warning" do
allow(storage_service).to receive(:list_files)
.and_return(%W[#{remote_folder}/#{case_log_id4}.xml #{remote_folder}/#{case_log_id5}.xml])
allow(logger).to receive(:warn).with(/is not completed/)
allow(logger).to receive(:warn).with(/is incomplete but status should be complete/)
expect(logger).to receive(:warn).with(/The following case logs had status discrepancies: \[893ufj2s-lq77-42m4-rty6-ej09gh585uy1, 5ybz29dj-l33t-k1l0-hj86-n4k4ma77xkcd\]/).once
.and_return(%W[#{remote_folder}/#{case_log_id5}.xml #{remote_folder}/#{case_log_id6}.xml])
expect(logger).to receive(:warn).with(/is not completed/).twice
expect(logger).to receive(:warn).with(/is incomplete but status should be complete/).twice
expect(logger).to receive(:warn).with(/The following case logs had status discrepancies: \[893ufj2s-lq77-42m4-rty6-ej09gh585uy1, 5ybz29dj-l33t-k1l0-hj86-n4k4ma77xkcd\]/)
case_log_service.create_logs(remote_folder)
end
@ -111,8 +123,8 @@ RSpec.describe Imports::CaseLogsImportService do
before { case_log_xml.at_xpath("//xmlns:VYEAR").content = 2023 }
it "does not import the voiddate" do
allow(logger).to receive(:warn).with(/is not completed/)
allow(logger).to receive(:warn).with(/Case log with old id:#{case_log_id} is incomplete but status should be complete/)
expect(logger).to receive(:warn).with(/is not completed/)
expect(logger).to receive(:warn).with(/Case log with old id:#{case_log_id} is incomplete but status should be complete/)
case_log_service.send(:create_log, case_log_xml)
@ -138,7 +150,7 @@ RSpec.describe Imports::CaseLogsImportService do
it "sets the economic status to child under 16" do
# The update is done when calculating derived variables
allow(logger).to receive(:warn).with(/Differences found when saving log/)
expect(logger).to receive(:warn).with(/Differences found when saving log/)
case_log_service.send(:create_log, case_log_xml)
case_log = CaseLog.where(old_id: case_log_id).first
@ -203,5 +215,19 @@ RSpec.describe Imports::CaseLogsImportService do
end
end
end
context "and this is a supported housing log" do
let(:case_log_id) { "0b4a68df-30cc-474a-93c0-a56ce8fdad3b" }
it "sets the scheme and location values" do
expect(logger).not_to receive(:warn)
case_log_service.send(:create_log, case_log_xml)
case_log = CaseLog.find_by(old_id: case_log_id)
expect(case_log.scheme_id).not_to be_nil
expect(case_log.location_id).not_to be_nil
expect(case_log.status).to eq("completed")
end
end
end
end

13
spec/services/imports/scheme_import_service_spec.rb

@ -60,5 +60,18 @@ RSpec.describe Imports::SchemeImportService do
.not_to change(Scheme, :count)
end
end
context "and the scheme arrange type is direct" do
before do
scheme_xml.at_xpath("//mgmtgroup:arrangement_type").content = "D"
scheme_xml.at_xpath("//mgmtgroup:agent").content = ""
end
it "assigns both owning and managing organisation to the same one" do
scheme = scheme_service.create_scheme(scheme_xml)
expect(scheme.owning_organisation).to eq(owning_org)
expect(scheme.managing_organisation).to eq(owning_org)
end
end
end
end

2
spec/services/imports/scheme_location_import_service_spec.rb

@ -134,9 +134,11 @@ RSpec.describe Imports::SchemeLocationImportService do
expect(location.name).to eq("Location 1")
expect(location.postcode).to eq("S44 6EJ")
expect(location.units).to eq(5)
expect(location.mobility_type).to eq("Property fitted with equipment and adaptations (if not designed to above standards)")
expect(location.type_of_unit).to eq("Bungalow")
expect(location.old_id).to eq(first_location_id)
expect(location.old_visible_id).to eq(10)
expect(location.startdate).to eq("1900-01-01")
expect(location.scheme).to eq(scheme)
end

Loading…
Cancel
Save