diff --git a/.cfignore b/.cfignore index e69de29bb..9aae254ab 100644 --- a/.cfignore +++ b/.cfignore @@ -0,0 +1,2 @@ +docs/* +spec/* diff --git a/.gitignore b/.gitignore index 6cb937077..65bf729e6 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,12 @@ # Ignore master key for decrypting credentials and more. /config/master.key +# Ignore generated documentation files +_site +.sass-cache +.jekyll-cache +.jekyll-metadata + /public/packs /public/packs-test /node_modules @@ -40,7 +46,7 @@ yarn-debug.log* # Code coverage results /coverage -#IDE specific files +# IDE specific files /.idea /.idea/* .DS_Store diff --git a/Gemfile.lock b/Gemfile.lock index bc6d661d7..944e61046 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -13,67 +13,67 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (7.0.3) - actionpack (= 7.0.3) - activesupport (= 7.0.3) + actioncable (7.0.3.1) + actionpack (= 7.0.3.1) + activesupport (= 7.0.3.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.3) - actionpack (= 7.0.3) - activejob (= 7.0.3) - activerecord (= 7.0.3) - activestorage (= 7.0.3) - activesupport (= 7.0.3) + actionmailbox (7.0.3.1) + actionpack (= 7.0.3.1) + activejob (= 7.0.3.1) + activerecord (= 7.0.3.1) + activestorage (= 7.0.3.1) + activesupport (= 7.0.3.1) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.3) - actionpack (= 7.0.3) - actionview (= 7.0.3) - activejob (= 7.0.3) - activesupport (= 7.0.3) + actionmailer (7.0.3.1) + actionpack (= 7.0.3.1) + actionview (= 7.0.3.1) + activejob (= 7.0.3.1) + activesupport (= 7.0.3.1) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.0) - actionpack (7.0.3) - actionview (= 7.0.3) - activesupport (= 7.0.3) + actionpack (7.0.3.1) + actionview (= 7.0.3.1) + activesupport (= 7.0.3.1) rack (~> 2.0, >= 2.2.0) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.3) - actionpack (= 7.0.3) - activerecord (= 7.0.3) - activestorage (= 7.0.3) - activesupport (= 7.0.3) + actiontext (7.0.3.1) + actionpack (= 7.0.3.1) + activerecord (= 7.0.3.1) + activestorage (= 7.0.3.1) + activesupport (= 7.0.3.1) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.3) - activesupport (= 7.0.3) + actionview (7.0.3.1) + activesupport (= 7.0.3.1) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (7.0.3) - activesupport (= 7.0.3) + activejob (7.0.3.1) + activesupport (= 7.0.3.1) globalid (>= 0.3.6) - activemodel (7.0.3) - activesupport (= 7.0.3) - activerecord (7.0.3) - activemodel (= 7.0.3) - activesupport (= 7.0.3) - activestorage (7.0.3) - actionpack (= 7.0.3) - activejob (= 7.0.3) - activerecord (= 7.0.3) - activesupport (= 7.0.3) + activemodel (7.0.3.1) + activesupport (= 7.0.3.1) + activerecord (7.0.3.1) + activemodel (= 7.0.3.1) + activesupport (= 7.0.3.1) + activestorage (7.0.3.1) + actionpack (= 7.0.3.1) + activejob (= 7.0.3.1) + activerecord (= 7.0.3.1) + activesupport (= 7.0.3.1) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (7.0.3) + activesupport (7.0.3.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -82,7 +82,7 @@ GEM public_suffix (>= 2.0.2, < 5.0) ast (2.4.2) aws-eventstream (1.2.0) - aws-partitions (1.603.0) + aws-partitions (1.605.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.3) + govuk-components (3.1.4) actionpack (>= 6.1) activemodel (>= 6.1) html-attributes-utils (~> 0.9, >= 0.9.2) @@ -187,7 +187,7 @@ GEM html-attributes-utils (0.9.2) activesupport (>= 6.1.4.4) html_tokenizer (0.0.7) - i18n (1.10.0) + i18n (1.11.0) concurrent-ruby (~> 1.0) iniparse (1.5.0) jmespath (1.6.1) @@ -225,11 +225,11 @@ GEM net-protocol timeout nio4r (2.5.8) - nokogiri (1.13.6-arm64-darwin) + nokogiri (1.13.7-arm64-darwin) racc (~> 1.4) - nokogiri (1.13.6-x86_64-darwin) + nokogiri (1.13.7-x86_64-darwin) racc (~> 1.4) - nokogiri (1.13.6-x86_64-linux) + nokogiri (1.13.7-x86_64-linux) racc (~> 1.4) notifications-ruby-client (5.3.0) jwt (>= 1.5, < 3) @@ -275,28 +275,28 @@ GEM rack (>= 1.2.0) rack-test (2.0.2) rack (>= 1.3) - rails (7.0.3) - actioncable (= 7.0.3) - actionmailbox (= 7.0.3) - actionmailer (= 7.0.3) - actionpack (= 7.0.3) - actiontext (= 7.0.3) - actionview (= 7.0.3) - activejob (= 7.0.3) - activemodel (= 7.0.3) - activerecord (= 7.0.3) - activestorage (= 7.0.3) - activesupport (= 7.0.3) + rails (7.0.3.1) + actioncable (= 7.0.3.1) + actionmailbox (= 7.0.3.1) + actionmailer (= 7.0.3.1) + actionpack (= 7.0.3.1) + actiontext (= 7.0.3.1) + actionview (= 7.0.3.1) + activejob (= 7.0.3.1) + activemodel (= 7.0.3.1) + activerecord (= 7.0.3.1) + activestorage (= 7.0.3.1) + activesupport (= 7.0.3.1) bundler (>= 1.15.0) - railties (= 7.0.3) + railties (= 7.0.3.1) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) rails-html-sanitizer (1.4.3) loofah (~> 2.3) - railties (7.0.3) - actionpack (= 7.0.3) - activesupport (= 7.0.3) + railties (7.0.3.1) + actionpack (= 7.0.3.1) + activesupport (= 7.0.3.1) method_source rake (>= 12.2) thor (~> 1.0) diff --git a/README.md b/README.md index 3c568e8e1..cccb080f2 100644 --- a/README.md +++ b/README.md @@ -7,26 +7,11 @@ Ruby on Rails app that handles the submission of lettings and sales of social ho ## Domain documentation -- [Service overview](docs/service_overview.md) -- [Organisations](docs/organisations.md) -- [Users and roles](docs/users.md) -- [Supported housing schemes](docs/schemes.md) - -## Technical Documentation - -- [Developer setup](docs/developer_setup.md) -- [Frontend](docs/frontend.md) -- [Testing strategy](docs/testing.md) -- [Form Builder](docs/form_builder.md) -- [Form Runner](docs/form_runner.md) -- [Infrastructure](docs/infrastructure.md) -- [Monitoring](docs/monitoring.md) -- [Exporting to CDS](docs/exports.md) -- [Architecture decision records](docs/adr) - -## API documentation - -API documentation can be found here: . This is driven by [OpenAPI docs](docs/api/DLUHC-CORE-Data.v1.json) +* [Domain and technical documentation](https://communitiesuk.github.io/submit-social-housing-lettings-and-sales-data) + * [Local development setup](https://communitiesuk.github.io/submit-social-housing-lettings-and-sales-data/setup) + * [Architecture decision records](https://communitiesuk.github.io/submit-social-housing-lettings-and-sales-data/adr) +* [API browser](https://communitiesuk.github.io/submit-social-housing-lettings-and-sales-data/api) (using this [OpenAPI specification](docs/api/v1.json)) +* [Design history](https://core-design-history.herokuapp.com) ## System architecture diff --git a/app/controllers/locations_controller.rb b/app/controllers/locations_controller.rb index e900a12ff..b482b81f0 100644 --- a/app/controllers/locations_controller.rb +++ b/app/controllers/locations_controller.rb @@ -69,7 +69,7 @@ private 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 = params.require(:location).permit(:postcode, :name, :units, :type_of_unit, :wheelchair_adaptation, :add_another_location).merge(scheme_id: @scheme.id) required_params[:postcode] = PostcodeService.clean(required_params[:postcode]) if required_params[:postcode] required_params end diff --git a/app/frontend/controllers/accessible_autocomplete_controller.js b/app/frontend/controllers/accessible_autocomplete_controller.js index d3c900485..0b17e8aa8 100644 --- a/app/frontend/controllers/accessible_autocomplete_controller.js +++ b/app/frontend/controllers/accessible_autocomplete_controller.js @@ -1,12 +1,36 @@ import { Controller } from '@hotwired/stimulus' import accessibleAutocomplete from 'accessible-autocomplete' import 'accessible-autocomplete/dist/accessible-autocomplete.min.css' +import { enhanceOption, suggestion, sort } from '../modules/search' export default class extends Controller { connect () { + const selectEl = this.element + const selectOptions = Array.from(selectEl.options) + const options = selectOptions.map((o) => enhanceOption(o)) + + const matches = /^(\w+)\[(\w+)\]$/.exec(selectEl.name) + const rawFieldName = matches ? `${matches[1]}[${matches[2]}_raw]` : '' + accessibleAutocomplete.enhanceSelectElement({ defaultValue: '', - selectElement: this.element + selectElement: selectEl, + minLength: 2, + source: (query, populateResults) => { + if (/\S/.test(query)) { + populateResults(sort(query, options)) + } + }, + autoselect: true, + templates: { suggestion: (value) => suggestion(value, options) }, + name: rawFieldName, + onConfirm: (val) => { + const selectedOption = [].filter.call( + selectOptions, + (option) => (option.textContent || option.innerText) === val + )[0] + if (selectedOption) selectedOption.selected = true + } }) } } diff --git a/app/frontend/modules/search.js b/app/frontend/modules/search.js new file mode 100644 index 000000000..d9d233ab1 --- /dev/null +++ b/app/frontend/modules/search.js @@ -0,0 +1,106 @@ +const addWeightWithBoost = (option, query) => { + option.weight = calculateWeight(option.clean, query) * option.clean.boost + + return option +} + +const clean = (text) => + text + .trim() + .replace(/['’]/g, '') + .replace(/[.,"/#!$%^&*;:{}=\-_`~()]/g, ' ') + .toLowerCase() + +const cleanseOption = (option) => { + option.clean = { + name: clean(option.name), + nameWithoutStopWords: removeStopWords(option.name), + boost: option.boost || 1 + } + + return option +} + +const hasWeight = (option) => option.weight > 0 + +const byWeightThenAlphabetically = (a, b) => { + if (a.weight > b.weight) return -1 + if (a.weight < b.weight) return 1 + if (a.name < b.name) return -1 + if (a.name > b.name) return 1 + + return 0 +} + +const optionName = (option) => option.name +const exactMatch = (word, query) => word === query + +const startsWithRegExp = (query) => new RegExp('\\b' + query, 'i') +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 queryWithoutStopWords = removeStopWords(query) + + if (exactMatch(name, query)) return 100 + if (exactMatch(nameWithoutStopWords, queryWithoutStopWords)) return 95 + + if (startsWith(name, query)) return 60 + if (startsWith(nameWithoutStopWords, queryWithoutStopWords)) return 55 + const startsWithRegExps = queryWithoutStopWords + .split(/\s+/) + .map(startsWithRegExp) + + if (wordsStartsWithQuery(nameWithoutStopWords, startsWithRegExps)) return 25 + + return 0 +} + +const stopWords = ['the', 'of', 'in', 'and', 'at', '&'] + +const removeStopWords = (text) => { + const isAllStopWords = text + .trim() + .split(' ') + .every((word) => stopWords.includes(word)) + + if (isAllStopWords) { + return text + } + + const regex = new RegExp( + stopWords.map((word) => `(\\s+)?${word}(\\s+)?`).join('|'), + 'gi' + ) + return text.replace(regex, ' ').trim() +} + +export const sort = (query, options) => { + const cleanQuery = clean(query) + + return options + .map(cleanseOption) + .map((option) => addWeightWithBoost(option, cleanQuery)) + .filter(hasWeight) + .sort(byWeightThenAlphabetically) + .map(optionName) +} + +export const suggestion = (value, options) => { + const option = options.find((o) => o.name === value) + if (option) { + const html = `${value}` + return html + } else { + return 'No results found' + } +} + +export const enhanceOption = (option) => { + return { + name: option.label, + boost: parseFloat(option.getAttribute('data-boost')) || 1 + } +} diff --git a/app/models/case_log.rb b/app/models/case_log.rb index 305582db2..c2465105c 100644 --- a/app/models/case_log.rb +++ b/app/models/case_log.rb @@ -460,6 +460,10 @@ class CaseLog < ApplicationRecord public_send("age#{person_num}_known") == 1 end + def unittype_sh + location.type_of_unit_before_type_cast if location + end + private PIO = Postcodes::IO.new diff --git a/app/models/derived_variables/case_log_variables.rb b/app/models/derived_variables/case_log_variables.rb index 33fcef3d1..8a8d8429a 100644 --- a/app/models/derived_variables/case_log_variables.rb +++ b/app/models/derived_variables/case_log_variables.rb @@ -1,13 +1,5 @@ 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? @@ -83,8 +75,6 @@ module DerivedVariables::CaseLogVariables 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 diff --git a/app/models/location.rb b/app/models/location.rb index 6718c79a7..bb2453625 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -16,8 +16,8 @@ class Location < ApplicationRecord "Self-contained flat or bedsit with common facilities": 2, "Shared flat": 3, "Shared house or hostel": 4, - "Bungalow": 5, - "Self-contained house": 6, + "Bungalow": 6, + "Self-contained house": 7, }.freeze enum type_of_unit: TYPE_OF_UNIT diff --git a/app/models/scheme.rb b/app/models/scheme.rb index 13f090797..bafa7d395 100644 --- a/app/models/scheme.rb +++ b/app/models/scheme.rb @@ -17,10 +17,10 @@ class Scheme < ApplicationRecord enum sensitive: SENSITIVE, _suffix: true REGISTERED_UNDER_CARE_ACT = { - "No": 0, - "Yes – registered care home providing nursing care": 1, - "Yes – registered care home providing personal care": 2, - "Yes – part registered as a care home": 3, + "No": 1, + "Yes – registered care home providing nursing care": 4, + "Yes – registered care home providing personal care": 3, + "Yes – part registered as a care home": 2, }.freeze enum registered_under_care_act: REGISTERED_UNDER_CARE_ACT diff --git a/app/services/exports/case_log_export_service.rb b/app/services/exports/case_log_export_service.rb index 17f271762..d2e031e90 100644 --- a/app/services/exports/case_log_export_service.rb +++ b/app/services/exports/case_log_export_service.rb @@ -189,6 +189,8 @@ module Exports attribute_hash["age#{index}"] = -9 if attribute_hash["age#{index}_known"] == 1 end + attribute_hash["unittype_sh"] = case_log.unittype_sh + attribute_hash end diff --git a/app/services/imports/scheme_import_service.rb b/app/services/imports/scheme_import_service.rb new file mode 100644 index 000000000..b68e7e7da --- /dev/null +++ b/app/services/imports/scheme_import_service.rb @@ -0,0 +1,60 @@ +module Imports + class SchemeImportService < ImportService + def create_schemes(folder) + import_from(folder, :create_scheme) + end + + def create_scheme(xml_document) + old_id = string_or_nil(xml_document, "id") + status = string_or_nil(xml_document, "status") + + if 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"), + ) + else + @logger.warn("Scheme with legacy ID #{old_id} is not approved (#{status}), skipping") + end + end + + private + + def scheme_field_value(xml_document, field) + field_value(xml_document, "mgmtgroup", field) + end + + def string_or_nil(xml_doc, attribute) + str = scheme_field_value(xml_doc, attribute) + str.presence + end + + # Safe: A string that represents only an integer (or empty/nil) + def safe_string_as_integer(xml_doc, attribute) + str = scheme_field_value(xml_doc, attribute) + Integer(str, exception: false) + end + + def find_owning_organisation_id(xml_doc) + old_org_id = string_or_nil(xml_doc, "institution") + 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") + return unless old_visible_id + + organisation = Organisation.find_by(old_visible_id:) + raise "Organisation not found with legacy visible ID #{old_visible_id}" if organisation.nil? + + organisation.id + end + end +end diff --git a/app/services/imports/scheme_location_import_service.rb b/app/services/imports/scheme_location_import_service.rb new file mode 100644 index 000000000..f05381c3b --- /dev/null +++ b/app/services/imports/scheme_location_import_service.rb @@ -0,0 +1,193 @@ +module Imports + class SchemeLocationImportService < ImportService + def create_scheme_locations(folder) + import_from(folder, :create_scheme_location) + end + + def create_scheme_location(xml_document) + attributes = scheme_attributes(xml_document) + schemes = Scheme.where(old_id: attributes["scheme_old_id"]) + raise "Scheme not found with legacy ID #{attributes['scheme_old_id']}" if schemes.empty? + + if schemes.size == 1 && schemes.first.locations&.empty? + scheme = update_scheme(schemes.first, attributes) + else + scheme = find_scheme_to_merge(attributes) + scheme ||= duplicate_scheme(schemes, attributes) + end + add_location(scheme, attributes) + end + + private + + REGISTERED_UNDER_CARE_ACT = { + 2 => "(Part-registered care home)", + 3 => "(Registered personal care home)", + 4 => "(Registered nursing care home)", + }.freeze + + def create_scheme(source_scheme, attributes) + Scheme.create!( + scheme_type: attributes["scheme_type"], + registered_under_care_act: attributes["registered_under_care_act"], + support_type: attributes["support_type"], + intended_stay: attributes["intended_stay"], + primary_client_group: attributes["primary_client_group"], + secondary_client_group: attributes["secondary_client_group"], + sensitive: attributes["sensitive"], + end_date: attributes["end_date"], + # These values were set by the scheme import (management groups) + owning_organisation_id: source_scheme.owning_organisation_id, + managing_organisation_id: source_scheme.managing_organisation_id, + service_name: source_scheme.service_name, + arrangement_type: source_scheme.arrangement_type, + old_id: source_scheme.old_id, + old_visible_id: source_scheme.old_visible_id, + ) + end + + def update_scheme(scheme, attributes) + scheme.update!( + scheme_type: attributes["scheme_type"], + registered_under_care_act: attributes["registered_under_care_act"], + support_type: attributes["support_type"], + intended_stay: attributes["intended_stay"], + primary_client_group: attributes["primary_client_group"], + secondary_client_group: attributes["secondary_client_group"], + sensitive: attributes["sensitive"], + end_date: attributes["end_date"], + ) + scheme + end + + def scheme_attributes(xml_doc) + attributes = {} + attributes["scheme_type"] = safe_string_as_integer(xml_doc, "scheme-type") + registered_under_care_act = safe_string_as_integer(xml_doc, "reg-home-type") + 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["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["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") + attributes["type_of_unit"] = safe_string_as_integer(xml_doc, "unit-type") + attributes["location_old_id"] = string_or_nil(xml_doc, "id") + attributes["location_old_visible_id"] = safe_string_as_integer(xml_doc, "visible-id") + attributes["scheme_old_id"] = string_or_nil(xml_doc, "mgmtgroup") + attributes + end + + 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"], + units: attributes["units"], + type_of_unit: attributes["type_of_unit"], + old_visible_id: attributes["location_old_visible_id"], + old_id: attributes["location_old_id"], + scheme:, + ) + rescue ActiveRecord::RecordNotUnique + @logger.warn("Location is already present with legacy ID #{attributes['location_old_id']}, skipping") + end + else + @logger.warn("Location with legacy ID #{attributes['location_old_id']} is expired (#{attributes['end_date']}), skipping") + end + end + + def find_scheme_to_merge(attributes) + Scheme.find_by( + old_id: attributes["scheme_old_id"], + scheme_type: attributes["scheme_type"], + registered_under_care_act: attributes["registered_under_care_act"], + support_type: attributes["support_type"], + intended_stay: attributes["intended_stay"], + primary_client_group: attributes["primary_client_group"], + secondary_client_group: attributes["secondary_client_group"], + ) + end + + def duplicate_scheme(schemes, attributes) + # Since all schemes in the array are different, pick the first one + # In the future, consider a better selection method if needed + old_scheme = schemes.first + new_scheme = create_scheme(old_scheme, attributes) + + if old_scheme.scheme_type != new_scheme.scheme_type + rename_schemes(old_scheme, new_scheme, :scheme_type) + elsif old_scheme.registered_under_care_act != new_scheme.registered_under_care_act + rename_registered_care(old_scheme, new_scheme) + elsif old_scheme.support_type != new_scheme.support_type + rename_schemes(old_scheme, new_scheme, :support_type) + elsif old_scheme.intended_stay != new_scheme.intended_stay + rename_schemes(old_scheme, new_scheme, :intended_stay) + elsif old_scheme.primary_client_group != new_scheme.primary_client_group + rename_schemes(old_scheme, new_scheme, :primary_client_group) + elsif old_scheme.secondary_client_group != new_scheme.secondary_client_group + rename_schemes(old_scheme, new_scheme, :secondary_client_group) + end + + new_scheme + end + + def rename_registered_care(*schemes) + schemes.each do |scheme| + if REGISTERED_UNDER_CARE_ACT.key?(scheme.registered_under_care_act_before_type_cast) + suffix = REGISTERED_UNDER_CARE_ACT[scheme.registered_under_care_act_before_type_cast] + scheme.update!(service_name: "#{scheme.service_name} - #{suffix}") + end + end + end + + def rename_schemes(old_scheme, new_scheme, attribute) + old_scheme_attribute = old_scheme.send(attribute) + new_scheme_attribute = new_scheme.send(attribute) + + if old_scheme_attribute + old_scheme_name = "#{old_scheme.service_name} - #{old_scheme_attribute}" + old_scheme.update!(service_name: old_scheme_name) + end + if new_scheme_attribute + new_scheme_name = "#{new_scheme.service_name} - #{new_scheme_attribute}" + new_scheme.update!(service_name: new_scheme_name) + end + end + + def location_field_value(xml_doc, field) + field_value(xml_doc, "scheme", field) + end + + def string_or_nil(xml_doc, attribute) + str = location_field_value(xml_doc, attribute) + str.presence + end + + # Safe: A string that represents only an integer (or empty/nil) + def safe_string_as_integer(xml_doc, attribute) + str = location_field_value(xml_doc, attribute) + Integer(str, exception: false) + end + + def sensitive(xml_doc) + value = string_or_nil(xml_doc, "sensitive") + if value == "true" + 1 + else + 0 + 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 + end + end +end diff --git a/app/views/layouts/_footer.html.erb b/app/views/layouts/_footer.html.erb index 49a6e689c..91cff0282 100644 --- a/app/views/layouts/_footer.html.erb +++ b/app/views/layouts/_footer.html.erb @@ -34,6 +34,9 @@ +