Browse Source

Merge remote-tracking branch 'origin/main' into cldc-1304-export-fixes

pull/650/head
Stéphane Meny 3 years ago
parent
commit
449aec1559
No known key found for this signature in database
GPG Key ID: 9D0AFEA988527923
  1. 1
      .github/workflows/staging_pipeline.yml
  2. 2
      app/components/primary_navigation_component.rb
  3. 18
      app/controllers/schemes_controller.rb
  4. 29
      app/frontend/application.js
  5. 6
      app/frontend/controllers/accessible_autocomplete_controller.js
  6. 6
      app/frontend/controllers/application.js
  7. 10
      app/frontend/controllers/conditional_filter_controller.js
  8. 40
      app/frontend/controllers/conditional_question_controller.js
  9. 6
      app/frontend/controllers/filter_layout_controller.js
  10. 8
      app/frontend/controllers/govukfrontend_controller.js
  11. 26
      app/frontend/controllers/index.js
  12. 39
      app/frontend/controllers/numeric_question_controller.js
  13. 26
      app/frontend/modules/filter_toggle.js
  14. 21
      app/helpers/navigation_items_helper.rb
  15. 17
      app/models/form.rb
  16. 8
      app/models/form_handler.rb
  17. 20
      app/models/location.rb
  18. 5
      app/models/scheme.rb
  19. 4
      app/services/exports/case_log_export_service.rb
  20. 42
      app/services/imports/case_logs_field_import_service.rb
  21. 27
      app/services/imports/case_logs_import_service.rb
  22. 6
      app/views/organisations/schemes.html.erb
  23. 4
      app/views/schemes/_scheme_list.html.erb
  24. 6
      app/views/schemes/index.html.erb
  25. 41
      app/views/schemes/locations.html.erb
  26. 12
      app/views/schemes/show.html.erb
  27. 12
      babel.config.js
  28. 138
      config/forms/setup/log_setup.json
  29. 7
      config/initializers/feature_toggle.rb
  30. 8
      config/routes.rb
  31. 17
      db/migrate/20220614124115_create_locations.rb
  32. 18
      db/schema.rb
  33. 40
      db/seeds.rb
  34. 2
      lib/tasks/data_import_field.rake
  35. 7
      lib/tasks/lint.rake
  36. 8
      package.json
  37. 8
      spec/components/primary_navigation_component_spec.rb
  38. 3
      spec/factories/case_log.rb
  39. 13
      spec/factories/location.rb
  40. 2
      spec/factories/scheme.rb
  41. 18
      spec/features/form/tasklist_page_spec.rb
  42. 61
      spec/features/schemes_spec.rb
  43. 2
      spec/fixtures/exports/case_logs.xml
  44. 76
      spec/fixtures/forms/2022_2023.json
  45. 71
      spec/fixtures/forms/setup/log_setup.json
  46. 4
      spec/fixtures/softwire_imports/case_logs/166fc004-392e-47a8-acb8-1c018734882b.xml
  47. 2
      spec/fixtures/softwire_imports/case_logs/5ybz29dj-l33t-k1l0-hj86-n4k4ma77xkcd.xml
  48. 2
      spec/fixtures/softwire_imports/case_logs/893ufj2s-lq77-42m4-rty6-ej09gh585uy1.xml
  49. 85
      spec/helpers/navigation_items_helper_spec.rb
  50. 4
      spec/helpers/tab_nav_helper_spec.rb
  51. 4
      spec/helpers/tasklist_helper_spec.rb
  52. 14
      spec/lib/tasks/date_import_field_spec.rb
  53. 33
      spec/models/case_log_spec.rb
  54. 4
      spec/models/form_handler_spec.rb
  55. 2
      spec/models/organisation_spec.rb
  56. 3
      spec/models/rent_period_spec.rb
  57. 7
      spec/requests/case_logs_controller_spec.rb
  58. 26
      spec/requests/organisations_controller_spec.rb
  59. 235
      spec/requests/schemes_controller_spec.rb
  60. 122
      spec/services/imports/case_logs_field_import_service_spec.rb
  61. 5
      spec/services/imports/case_logs_import_service_spec.rb
  62. 34
      webpack.config.js
  63. 951
      yarn.lock

1
.github/workflows/staging_pipeline.yml

@ -8,6 +8,7 @@ on:
types:
- opened
- ready_for_review
- synchronize
workflow_dispatch:
defaults:

2
app/components/primary_navigation_component.rb

@ -3,7 +3,7 @@ class PrimaryNavigationComponent < ViewComponent::Base
def initialize(items:)
@items = items
Rails.env.production? ? @items = @items.reject { |nav_item| nav_item.text.include?("Supported housing") } : @items
FeatureToggle.supported_housing_schemes_enabled? ? @items : @items.reject! { |nav_item| nav_item.text.include?("Schemes") }
super
end

18
app/controllers/schemes_controller.rb

@ -3,10 +3,11 @@ class SchemesController < ApplicationController
include Modules::SearchFilter
before_action :authenticate_user!
before_action :find_resource, except: %i[index]
before_action :authenticate_scope!
def index
redirect_to supported_housing_organisation_path(current_user.organisation) unless current_user.support?
redirect_to schemes_organisation_path(current_user.organisation) unless current_user.support?
all_schemes = Scheme.all
@pagy, @schemes = pagy(filtered_collection(all_schemes, search_term))
@ -16,7 +17,12 @@ class SchemesController < ApplicationController
def show
@scheme = Scheme.find_by(id: params[:id])
render_not_found and return unless (current_user.organisation == @scheme.organisation) || current_user.support?
end
def locations
@scheme = Scheme.find_by(id: params[:id])
@pagy, @locations = pagy(@scheme.locations)
@total_count = @scheme.locations.size
end
private
@ -25,7 +31,15 @@ private
params["search"]
end
def find_resource
@scheme = Scheme.find_by(id: params[:id])
end
def authenticate_scope!
head :unauthorized and return unless current_user.data_coordinator? || current_user.support?
if %w[show locations].include?(action_name) && !((current_user.organisation == @scheme.organisation) || current_user.support?)
render_not_found and return
end
end
end

29
app/frontend/application.js

@ -1,22 +1,23 @@
// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.
// present in this directory. You're encouraged to place your actual application
// logic in a relevant structure within app/javascript and only use these pack
// files to reference that code so it'll be compiled.
// Polyfills for IE
import "@webcomponents/webcomponentsjs"
import "core-js/stable"
import "regenerator-runtime/runtime"
import "@stimulus/polyfills"
import "custom-event-polyfill"
import "intersection-observer"
import '@webcomponents/webcomponentsjs'
import 'core-js/stable'
import 'regenerator-runtime/runtime'
import '@stimulus/polyfills'
import 'custom-event-polyfill'
import 'intersection-observer'
//
import GOVUKFrontend from 'govuk-frontend'
import GOVUKPrototypeComponents from 'govuk-prototype-components'
import './styles/application.scss'
import './controllers'
require.context("govuk-frontend/govuk/assets")
import GOVUKFrontend from "govuk-frontend"
import GOVUKPrototypeComponents from "govuk-prototype-components"
import "./styles/application.scss"
import "./controllers"
require.context('govuk-frontend/govuk/assets')
GOVUKFrontend.initAll()
GOVUKPrototypeComponents.initAll()

6
app/frontend/controllers/accessible_autocomplete_controller.js

@ -1,9 +1,9 @@
import { Controller } from "@hotwired/stimulus"
import accessibleAutocomplete from "accessible-autocomplete"
import { Controller } from '@hotwired/stimulus'
import accessibleAutocomplete from 'accessible-autocomplete'
import 'accessible-autocomplete/dist/accessible-autocomplete.min.css'
export default class extends Controller {
connect() {
connect () {
accessibleAutocomplete.enhanceSelectElement({
defaultValue: '',
selectElement: this.element

6
app/frontend/controllers/application.js

@ -1,10 +1,10 @@
import { Application } from "@hotwired/stimulus"
import { Application } from '@hotwired/stimulus'
const application = Application.start()
// Configure Stimulus development experience
application.warnings = true
application.debug = false
window.Stimulus = application
application.debug = false
window.Stimulus = application
export { application }

10
app/frontend/controllers/conditional_filter_controller.js

@ -1,13 +1,13 @@
import { Controller } from "@hotwired/stimulus"
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
initialize() {
initialize () {
this.clearIfHidden()
}
clearIfHidden() {
if(this.element.style["display"] == "none") {
this.element.value = ""
clearIfHidden () {
if (this.element.style.display === 'none') {
this.element.value = ''
}
}
}

40
app/frontend/controllers/conditional_question_controller.js

@ -1,36 +1,36 @@
import { Controller } from "@hotwired/stimulus"
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
initialize() {
initialize () {
this.displayConditional()
}
displayConditional() {
if(this.element.checked) {
let selectedValue = this.element.value
let conditional_for = JSON.parse(this.element.dataset.info)
Object.entries(conditional_for).map(([targetQuestion, conditions]) => {
if(conditions.map(String).includes(String(selectedValue))) {
displayConditional () {
if (this.element.checked) {
const selectedValue = this.element.value
const conditionalFor = JSON.parse(this.element.dataset.info)
Object.entries(conditionalFor).forEach(([targetQuestion, conditions]) => {
if (!conditions.map(String).includes(String(selectedValue))) {
const textNumericInput = document.getElementById(`case-log-${targetQuestion.replaceAll('_', '-')}-field`)
if (textNumericInput == null) {
const dateInputs = [1, 2, 3].map((idx) => {
return document.getElementById(`case_log_${targetQuestion}_${idx}i`)
})
this.clearDateInputs(dateInputs)
} else {
const textNumericInput = document.getElementById(`case-log-${targetQuestion.replaceAll("_","-")}-field`)
if (textNumericInput == null) {
const dateInputs = [1,2,3].map((idx) => {
return document.getElementById(`case_log_${targetQuestion}_${idx}i`)
})
this.clearDateInputs(dateInputs)
} else {
this.clearTextNumericInput(textNumericInput)
this.clearTextNumericInput(textNumericInput)
}
}
})
}
}
clearTextNumericInput(input) {
input.value = ""
clearTextNumericInput (input) {
input.value = ''
}
clearDateInputs(inputs) {
inputs.forEach((input) => { input.value = "" })
clearDateInputs (inputs) {
inputs.forEach((input) => { input.value = '' })
}
}

6
app/frontend/controllers/filter_layout_controller.js

@ -1,8 +1,8 @@
import { Controller } from "@hotwired/stimulus";
import { FilterToggle } from "../modules/filter_toggle.js"
import { Controller } from '@hotwired/stimulus'
import { FilterToggle } from '../modules/filter_toggle.js'
export default class extends Controller {
connect() {
connect () {
const filterToggle = new FilterToggle({
bigModeMediaQuery: '(min-width: 48.0625em)',
closeButton: {

8
app/frontend/controllers/govukfrontend_controller.js

@ -1,9 +1,9 @@
import GOVUKFrontend from "govuk-frontend";
import GOVUKPrototypeComponents from "govuk-prototype-components"
import { Controller } from "@hotwired/stimulus";
import GOVUKFrontend from 'govuk-frontend'
import GOVUKPrototypeComponents from 'govuk-prototype-components'
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
connect() {
connect () {
GOVUKFrontend.initAll()
GOVUKPrototypeComponents.initAll()
}

26
app/frontend/controllers/index.js

@ -1,22 +1,22 @@
// Load all the controllers within this directory and all subdirectories.
// Controller files must be named *_controller.js.
import { application } from "./application"
import { application } from './application'
import AccessibleAutocompleteController from "./accessible_autocomplete_controller.js"
application.register("accessible-autocomplete", AccessibleAutocompleteController)
import AccessibleAutocompleteController from './accessible_autocomplete_controller.js'
import ConditionalFilterController from "./conditional_filter_controller.js"
application.register("conditional-filter", ConditionalFilterController)
import ConditionalFilterController from './conditional_filter_controller.js'
import ConditionalQuestionController from "./conditional_question_controller.js"
application.register("conditional-question", ConditionalQuestionController)
import ConditionalQuestionController from './conditional_question_controller.js'
import GovukfrontendController from "./govukfrontend_controller.js"
application.register("govukfrontend", GovukfrontendController)
import GovukfrontendController from './govukfrontend_controller.js'
import NumericQuestionController from "./numeric_question_controller.js"
application.register("numeric-question", NumericQuestionController)
import NumericQuestionController from './numeric_question_controller.js'
import FilterLayoutController from "./filter_layout_controller.js"
application.register("filter-layout", FilterLayoutController)
import FilterLayoutController from './filter_layout_controller.js'
application.register('accessible-autocomplete', AccessibleAutocompleteController)
application.register('conditional-filter', ConditionalFilterController)
application.register('conditional-question', ConditionalQuestionController)
application.register('govukfrontend', GovukfrontendController)
application.register('numeric-question', NumericQuestionController)
application.register('filter-layout', FilterLayoutController)

39
app/frontend/controllers/numeric_question_controller.js

@ -1,27 +1,26 @@
import { Controller } from "@hotwired/stimulus"
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
connect() {
const affectedField = this.element.dataset.target;
const targetQuestion = affectedField.split("case-log-")[1].split("-field")[0]
const div = document.getElementById(targetQuestion + "_div");
div.style.display = "block";
connect () {
const affectedField = this.element.dataset.target
const targetQuestion = affectedField.split('case-log-')[1].split('-field')[0]
const div = document.getElementById(targetQuestion + '_div')
div.style.display = 'block'
}
calculateFields() {
const affectedField = this.element.dataset.target;
const fieldsToAdd = JSON.parse(this.element.dataset.calculated).map(x => `case-log-${x.replaceAll("_","-")}-field`);
const valuesToAdd = fieldsToAdd.map(x => getFieldValue(x)).filter(x => x);
const newValue = valuesToAdd.map(x => parseFloat(x)).reduce((a, b) => a + b, 0).toFixed(2);
const elementToUpdate = document.getElementById(affectedField);
elementToUpdate.value = newValue;
calculateFields () {
const affectedField = this.element.dataset.target
const fieldsToAdd = JSON.parse(this.element.dataset.calculated).map(x => `case-log-${x.replaceAll('_', '-')}-field`)
const valuesToAdd = fieldsToAdd.map(x => getFieldValue(x)).filter(x => x)
const newValue = valuesToAdd.map(x => parseFloat(x)).reduce((a, b) => a + b, 0).toFixed(2)
const elementToUpdate = document.getElementById(affectedField)
elementToUpdate.value = newValue
}
}
const getFieldValue = (field) => {
const elementFieldToAdd = document.getElementById(field)
if (elementFieldToAdd) {
return elementFieldToAdd.value
}
let getFieldValue = (field) => {
const elementFieldToAdd= document.getElementById(field)
if (elementFieldToAdd) {
return elementFieldToAdd.value
}
return document.getElementById(`${field}-error`).value
return document.getElementById(`${field}-error`).value
}

26
app/frontend/modules/filter_toggle.js

@ -25,7 +25,7 @@ export class FilterToggle {
}
enableSmallMode () {
this.options.filter.container.setAttribute("tabindex", "-1")
this.options.filter.container.setAttribute('tabindex', '-1')
this.hideMenu()
this.addMenuButton()
this.addCloseButton()
@ -33,8 +33,8 @@ export class FilterToggle {
addCloseButton () {
if (this.options.closeButton) {
this.closeButton = document.createElement("button")
this.closeButton.classList.add("app-filter__close")
this.closeButton = document.createElement('button')
this.closeButton.classList.add('app-filter__close')
this.closeButton.innerText = this.options.closeButton.text
this.closeButton.type = 'button'
this.closeButton.addEventListener('click', this.onCloseClick.bind(this))
@ -56,13 +56,13 @@ export class FilterToggle {
}
addMenuButton () {
this.menuButton = document.createElement("button")
this.menuButton.setAttribute("aria-expanded", "false")
this.menuButton.setAttribute("aria-has-popup", "true")
this.menuButton.classList.add("govuk-button", this.options.toggleButton.classes, "app-filter-toggle__button")
this.menuButton = document.createElement('button')
this.menuButton.setAttribute('aria-expanded', 'false')
this.menuButton.setAttribute('aria-has-popup', 'true')
this.menuButton.classList.add('govuk-button', this.options.toggleButton.classes, 'app-filter-toggle__button')
this.menuButton.innerText = this.options.toggleButton.showText
this.menuButton.type = "button"
this.menuButton.addEventListener("click", this.onMenuButtonClick.bind(this))
this.menuButton.type = 'button'
this.menuButton.addEventListener('click', this.onMenuButtonClick.bind(this))
this.options.toggleButton.container.prepend(this.menuButton)
}
@ -76,18 +76,18 @@ export class FilterToggle {
hideMenu () {
if (this.menuButton) {
this.menuButton.setAttribute("aria-expanded", "false")
this.menuButton.setAttribute('aria-expanded', 'false')
this.menuButton.innerText = this.options.toggleButton.showText
}
this.options.filter.container.setAttribute("hidden", true)
this.options.filter.container.setAttribute('hidden', true)
}
showMenu () {
if (this.menuButton) {
this.menuButton.setAttribute("aria-expanded", "true")
this.menuButton.setAttribute('aria-expanded', 'true')
this.menuButton.innerText = this.options.toggleButton.hideText
}
this.options.filter.container.removeAttribute("hidden")
this.options.filter.container.removeAttribute('hidden')
}
onMenuButtonClick () {

21
app/helpers/navigation_items_helper.rb

@ -7,12 +7,12 @@ module NavigationItemsHelper
NavigationItem.new("Organisations", organisations_path, organisations_current?(path)),
NavigationItem.new("Users", "/users", users_current?(path)),
NavigationItem.new("Logs", case_logs_path, logs_current?(path)),
NavigationItem.new("Supported housing", "/supported-housing", supported_housing_current?(path)),
NavigationItem.new("Schemes", "/schemes", supported_housing_schemes_current?(path)),
]
elsif current_user.data_coordinator?
[
NavigationItem.new("Logs", case_logs_path, logs_current?(path)),
NavigationItem.new("Supported housing", "/supported-housing", subnav_supported_housing_path?(path)),
NavigationItem.new("Schemes", "/schemes", subnav_supported_housing_schemes_path?(path)),
NavigationItem.new("Users", users_organisation_path(current_user.organisation), subnav_users_path?(path)),
NavigationItem.new("About your organisation", "/organisations/#{current_user.organisation.id}", subnav_details_path?(path)),
]
@ -28,12 +28,19 @@ module NavigationItemsHelper
def secondary_items(path, current_organisation_id)
[
NavigationItem.new("Logs", "/organisations/#{current_organisation_id}/logs", subnav_logs_path?(path)),
NavigationItem.new("Supported housing", "/organisations/#{current_organisation_id}/supported-housing", subnav_supported_housing_path?(path)),
NavigationItem.new("Schemes", "/organisations/#{current_organisation_id}/schemes", subnav_supported_housing_schemes_path?(path)),
NavigationItem.new("Users", "/organisations/#{current_organisation_id}/users", subnav_users_path?(path)),
NavigationItem.new("About this organisation", "/organisations/#{current_organisation_id}", subnav_details_path?(path)),
]
end
def scheme_items(path, current_scheme_id, title)
[
NavigationItem.new("Scheme", "/schemes/#{current_scheme_id}", !path.include?("locations")),
NavigationItem.new(title, "/schemes/#{current_scheme_id}/locations", path.include?("locations")),
]
end
private
def logs_current?(path)
@ -44,16 +51,16 @@ private
path == "/users" || path.include?("/users/")
end
def supported_housing_current?(path)
path == "/supported-housing" || path.include?("/supported-housing/")
def supported_housing_schemes_current?(path)
path == "/schemes" || path.include?("/schemes/")
end
def organisations_current?(path)
path == "/organisations" || path.include?("/organisations/")
end
def subnav_supported_housing_path?(path)
path.include?("/organisations") && path.include?("/supported-housing") || path.include?("/supported-housing/")
def subnav_supported_housing_schemes_path?(path)
path.include?("/organisations") && path.include?("/schemes") || path.include?("/schemes/")
end
def subnav_users_path?(path)

17
app/models/form.rb

@ -1,19 +1,24 @@
class Form
attr_reader :form_definition, :sections, :subsections, :pages, :questions,
:start_date, :end_date, :type, :name
:start_date, :end_date, :type, :name, :setup_definition,
:setup_sections, :form_sections
def initialize(form_path, name)
def initialize(form_path, name, setup_path)
raise "No setup definition file exists for given path".freeze unless File.exist?(setup_path)
raise "No form definition file exists for given year".freeze unless File.exist?(form_path)
@form_definition = JSON.parse(File.open(form_path).read)
@name = name
@start_date = Time.iso8601(form_definition["start_date"])
@end_date = Time.iso8601(form_definition["end_date"])
@setup_definition = JSON.parse(File.open(setup_path).read)
@setup_sections = setup_definition["sections"].map { |id, s| Form::Section.new(id, s, self) } || []
@form_definition = JSON.parse(File.open(form_path).read)
@form_sections = form_definition["sections"].map { |id, s| Form::Section.new(id, s, self) }
@type = form_definition["form_type"]
@sections = form_definition["sections"].map { |id, s| Form::Section.new(id, s, self) }
@sections = setup_sections + form_sections
@subsections = sections.flat_map(&:subsections)
@pages = subsections.flat_map(&:pages)
@questions = pages.flat_map(&:questions)
@start_date = Time.iso8601(form_definition["start_date"])
@end_date = Time.iso8601(form_definition["end_date"])
end
def get_subsection(id)

8
app/models/form_handler.rb

@ -21,12 +21,18 @@ private
directories.each do |directory|
Dir.glob("#{directory}/*.json").each do |form_path|
form_name = File.basename(form_path, ".json")
forms[form_name] = Form.new(form_path, form_name)
forms[form_name] = Form.new(form_path, form_name, setup_path)
end
end
forms
end
def setup_path
return "spec/fixtures/forms/setup/log_setup.json" if Rails.env.test?
"config/forms/setup/log_setup.json"
end
def directories
Rails.env.test? ? ["spec/fixtures/forms"] : ["config/forms"]
end

20
app/models/location.rb

@ -0,0 +1,20 @@
class Location < ApplicationRecord
belongs_to :scheme
WHEELCHAIR_ADAPTATIONS = {
no: 0,
yes: 1,
}.freeze
enum wheelchair_adaptation: WHEELCHAIR_ADAPTATIONS
def display_attributes
[
{ name: "Location code ", value: location_code, suffix: false },
{ name: "Postcode", value: postcode, suffix: county },
{ name: "Type of unit", value: type_of_unit, suffix: false },
{ name: "Type of building", value: type_of_building, suffix: false },
{ name: "Wheelchair adaptation", value: wheelchair_adaptation, suffix: false },
]
end
end

5
app/models/scheme.rb

@ -1,5 +1,6 @@
class Scheme < ApplicationRecord
belongs_to :organisation
has_many :locations
scope :search_by_code, ->(code) { where("code ILIKE ?", "%#{code}%") }
scope :search_by_service_name, ->(name) { where("service_name ILIKE ?", "%#{name}%") }
@ -66,8 +67,8 @@ class Scheme < ApplicationRecord
{ name: "Service code", value: code },
{ name: "Name", value: service_name },
{ name: "Confidential information", value: sensitive_display },
{ name: "Managing agent", value: organisation.name },
{ name: "Type of service", value: scheme_type_display },
{ name: "Managed by", value: organisation.name },
{ name: "Type of scheme", value: scheme_type_display },
{ name: "Registered under Care Standards Act 2000", value: registered_under_care_act_display },
{ name: "Total number of units", value: total_units },
{ name: "Primary client group", value: primary_client_group_display },

4
app/services/exports/case_log_export_service.rb

@ -180,6 +180,10 @@ module Exports
attribute_hash["createddate"] = attribute_hash["created_at"]
attribute_hash["uploaddate"] = attribute_hash["updated_at"]
attribute_hash["cbl"] = 2 if attribute_hash["cbl"]&.zero?
attribute_hash["cap"] = 2 if attribute_hash["cap"]&.zero?
attribute_hash["chr"] = 2 if attribute_hash["chr"]&.zero?
# Age refused
(1..8).each do |index|
attribute_hash["age#{index}"] = -9 if attribute_hash["age#{index}_known"] == 1

42
app/services/imports/case_logs_field_import_service.rb

@ -6,6 +6,8 @@ module Imports
import_from(folder, :update_tenant_code)
when "major_repairs"
import_from(folder, :update_major_repairs)
when "lettings_allocation"
import_from(folder, :update_lettings_allocation)
else
raise "Updating #{field} is not supported by the field import service"
end
@ -13,6 +15,36 @@ module Imports
private
def update_lettings_allocation(xml_doc)
old_id = field_value(xml_doc, "meta", "document-id")
previous_status = field_value(xml_doc, "meta", "status")
record = CaseLog.find_by(old_id:)
if record.present? && previous_status.include?("submitted")
cbl = unsafe_string_as_integer(xml_doc, "Q15CBL")
chr = unsafe_string_as_integer(xml_doc, "Q15CHR")
cap = unsafe_string_as_integer(xml_doc, "Q15CAP")
if cbl == 2 && record.cbl == 1
record.update!(cbl: 0)
@logger.info("Case Log #{record.id}'s cbl value has been updated'")
end
if chr == 2 && record.chr == 1
record.update!(chr: 0)
@logger.info("Case Log #{record.id}'s chr value has been updated'")
end
if cap == 2 && record.cap == 1
record.update!(cap: 0)
@logger.info("Case Log #{record.id}'s cap value has been updated'")
end
if cbl == 2 && chr == 2 && cap == 2 && record.letting_allocation_unknown.nil?
record.update!(letting_allocation_unknown: 1)
@logger.info("Case Log #{record.id}'s letting_allocation_unknown value has been updated'")
end
else
@logger.warn("Could not find record matching legacy ID #{old_id}")
end
end
def update_major_repairs(xml_doc)
old_id = field_value(xml_doc, "meta", "document-id")
record = CaseLog.find_by(old_id:)
@ -67,5 +99,15 @@ module Imports
str = field_value(xml_doc, "xmlns", attribute)
str.presence
end
# Unsafe: A string that has more than just the integer value
def unsafe_string_as_integer(xml_doc, attribute)
str = string_or_nil(xml_doc, attribute)
if str.nil?
nil
else
str.to_i
end
end
end
end

27
app/services/imports/case_logs_import_service.rb

@ -123,9 +123,10 @@ module Imports
attributes["rp_hardship"] = unsafe_string_as_integer(xml_doc, "Q14b4").present? ? 1 : nil
attributes["rp_dontknow"] = unsafe_string_as_integer(xml_doc, "Q14b5").present? ? 1 : nil
attributes["cbl"] = unsafe_string_as_integer(xml_doc, "Q15CBL").present? ? 1 : nil
attributes["chr"] = unsafe_string_as_integer(xml_doc, "Q15CHR").present? ? 1 : nil
attributes["cap"] = unsafe_string_as_integer(xml_doc, "Q15CAP").present? ? 1 : nil
attributes["cbl"] = allocation_system(unsafe_string_as_integer(xml_doc, "Q15CBL"))
attributes["chr"] = allocation_system(unsafe_string_as_integer(xml_doc, "Q15CHR"))
attributes["cap"] = allocation_system(unsafe_string_as_integer(xml_doc, "Q15CAP"))
attributes["letting_allocation_unknown"] = allocation_system_unknown(attributes["cbl"], attributes["chr"], attributes["cap"])
attributes["referral"] = unsafe_string_as_integer(xml_doc, "Q16")
attributes["period"] = unsafe_string_as_integer(xml_doc, "Q17")
@ -553,6 +554,26 @@ module Imports
end
end
def allocation_system(value)
case value
when 1
1
when 2
0
end
end
def allocation_system_unknown(cbl, chr, cap)
allocation_values = [cbl, chr, cap]
if allocation_values.all?(&:nil?)
nil
elsif allocation_values.all? { |att| att&.zero? }
1
else
0
end
end
def apply_date_consistency!(attributes)
return if attributes["voiddate"].nil? || attributes["startdate"].nil?

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

@ -1,9 +1,9 @@
<% item_label = format_label(@pagy.count, "scheme") %>
<% title = format_title(@searched, "Supported housing services", current_user, item_label, @pagy.count, @organisation.name) %>
<% title = format_title(@searched, "Supported housing schemes", current_user, item_label, @pagy.count, @organisation.name) %>
<% content_for :title, title %>
<%= render partial: "organisations/headings", locals: current_user.support? ? { main: @organisation.name, sub: nil } : { main: "Supported housing services", sub: current_user.organisation.name } %>
<%= render partial: "organisations/headings", locals: current_user.support? ? { main: @organisation.name, sub: nil } : { main: "Schemes", sub: current_user.organisation.name } %>
<% if current_user.support? %>
<%= render SubNavigationComponent.new(
@ -11,7 +11,7 @@
) %>
<% end %>
<h2 class="govuk-visually-hidden">Supported housing services</h2>
<h2 class="govuk-visually-hidden">Supported housing schemes</h2>
<%= render SearchComponent.new(current_user:, search_label: "Search by service name or code", value: @searched) %>

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

@ -14,10 +14,10 @@
<% row.cell(header: true, text: "Code", html_attributes: {
scope: "col",
}) %>
<% row.cell(header: true, text: "Service", html_attributes: {
<% row.cell(header: true, text: "Scheme", html_attributes: {
scope: "col",
}) %>
<% row.cell(header: true, text: "Managing agent", html_attributes: {
<% row.cell(header: true, text: "Managed by", html_attributes: {
scope: "col",
}) %>
<% row.cell(header: true, text: "Created", html_attributes: {

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

@ -1,11 +1,11 @@
<% item_label = format_label(@pagy.count, "scheme") %>
<% title = format_title(@searched, "Supported housing services", current_user, item_label, @pagy.count, nil) %>
<% title = format_title(@searched, "Supported housing schemes", current_user, item_label, @pagy.count, nil) %>
<% content_for :title, title %>
<%= render partial: "organisations/headings", locals: current_user.support? ? { main: "Supported housing services", sub: nil } : { main: "Supported housing services", sub: current_user.organisation.name } %>
<%= render partial: "organisations/headings", locals: current_user.support? ? { main: "Supported housing schemes", sub: nil } : { main: "Supported housing schemes", sub: current_user.organisation.name } %>
<h2 class="govuk-visually-hidden">Supported housing services</h2>
<h2 class="govuk-visually-hidden">Supported housing schemes</h2>
<%= render SearchComponent.new(current_user:, search_label: "Search by service name or code", value: @searched) %>

41
app/views/schemes/locations.html.erb

@ -0,0 +1,41 @@
<% title = @scheme.service_name %>
<% content_for :title, title %>
<%= govuk_back_link(href: request.referer.to_s) %>
<%= render partial: "organisations/headings", locals: { main: @scheme.service_name, sub: nil } %>
<%= render SubNavigationComponent.new(items: scheme_items(request.path, @scheme.id, @scheme.locations.count.eql?(1) ? "1 location" : "#{@scheme.locations.count} locations")) %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-three-quarters">
<% @locations.each do |location| %>
<section class="x-govuk-summary-card govuk-!-margin-bottom-6">
<header class="x-govuk-summary-card__header">
<h2 class="x-govuk-summary-card__title">
<%= "#{location.address_line1}, #{location.address_line2}" %>
</h2>
</header>
<div class="x-govuk-summary-card__body">
<dl class="govuk-summary-list">
<% location.display_attributes.each do |attribute| %>
<div class="govuk-summary-list__row">
<dt class="govuk-summary-list__key">
<%= attribute[:name] %>
</dt>
<dd class="govuk-summary-list__value app-!-font-tabular">
<span class="govuk-!-margin-right-4"><%= attribute[:value] %></span>
<% if attribute[:suffix] %>
<span class="govuk-!-font-weight-regular app-!-colour-muted"><%= attribute[:suffix] %></span>
<% end %>
</dd>
</div>
<% end %>
</dl>
</div>
</section>
<% end %>
</div>
</div>
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "locations" } %>

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

@ -1,17 +1,21 @@
<% title = @scheme.service_name %>
<% content_for :title, title %>
<%= govuk_back_link(href: request.referer.to_s) %>
<%= render partial: "organisations/headings", locals: { main: @scheme.service_name, sub: nil } %>
<%= render SubNavigationComponent.new(items: scheme_items(request.path, @scheme.id, @scheme.locations.count.eql?(1) ? "1 location" : "#{@scheme.locations.count} locations")) %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<%= govuk_summary_list do |summary_list| %>
<% @scheme.display_attributes.each do |attr| %>
<%= summary_list.row do |row| %>
<% row.key { attr[:name].to_s.humanize } %>
<% row.value { details_html(attr) } %>
<% end %>
<%= summary_list.row do |row| %>
<% row.key { attr[:name].eql?("Registered under Care Standards Act 2000") ? "Registered under Care Standards Act 2000" : attr[:name].to_s.humanize } %>
<% row.value { details_html(attr) } %>
<% end %>
<% end %>
<% end %>
</div>
</div>

12
babel.config.js

@ -1,9 +1,9 @@
module.exports = function(api) {
var validEnv = ['development', 'test', 'production']
var currentEnv = api.env()
var isDevelopmentEnv = api.env('development')
var isProductionEnv = api.env('production')
var isTestEnv = api.env('test')
module.exports = function (api) {
const validEnv = ['development', 'test', 'production']
const currentEnv = api.env()
const isDevelopmentEnv = api.env('development')
const isProductionEnv = api.env('production')
const isTestEnv = api.env('test')
if (!validEnv.includes(currentEnv)) {
throw new Error(

138
config/forms/setup/log_setup.json

@ -0,0 +1,138 @@
{
"form_type": "setup",
"sections": {
"setup": {
"label": "Before you start",
"subsections": {
"setup": {
"label": "Set up this lettings log",
"pages": {
"needs_type": {
"header": "",
"description": "",
"questions": {
"needstype": {
"check_answer_label": "Needs type",
"header": "What is the needs type?",
"hint_text": "",
"type": "radio",
"answer_options": {
"1": {
"value": "General needs"
},
"2": {
"value": "Supported housing"
}
}
}
},
"derived": true,
"depends_on": [
{
"supported_housing_schemes_enabled?" : true
}
]
},
"renewal": {
"header": "",
"description": "",
"questions": {
"renewal": {
"check_answer_label": "Property renewal",
"header": "Is this letting a renewal?",
"hint_text": "A renewal is a letting to the same tenant in the same property.",
"type": "radio",
"answer_options": {
"1": {
"value": "Yes"
},
"0": {
"value": "No"
}
}
}
}
},
"tenancy_start_date": {
"header": "",
"description": "",
"questions": {
"startdate": {
"check_answer_label": "Tenancy start date",
"header": "What is the tenancy start date?",
"type": "date"
}
}
},
"rent_type": {
"header": "",
"description": "",
"questions": {
"rent_type": {
"check_answer_label": "Rent type",
"header": "What is the rent type?",
"hint_text": "",
"type": "radio",
"answer_options": {
"1": {
"value": "Affordable Rent"
},
"2": {
"value": "London Affordable Rent"
},
"4": {
"value": "London Living Rent"
},
"3": {
"value": "Rent to Buy"
},
"0": {
"value": "Social Rent"
},
"5": {
"value": "Other intermediate rent product"
}
},
"conditional_for": {
"irproduct_other": [5]
}
},
"irproduct_other": {
"check_answer_label": "Product name",
"header": "Name of rent product",
"type": "text"
}
}
},
"tenant_code": {
"header": "",
"description": "",
"questions": {
"tenant_code": {
"check_answer_label": "Tenant code",
"header": "What is the tenant code?",
"hint_text": "This is how you usually refer to this tenancy on your own systems.",
"type": "text",
"width": 10
}
}
},
"property_reference": {
"header": "",
"description": "",
"questions": {
"propcode": {
"check_answer_label": "Property reference",
"header": "What is the property reference?",
"hint_text": "This is how you usually refer to this property on your own systems.",
"type": "text",
"width": 10
}
}
}
}
}
}
}
}
}

7
config/initializers/feature_toggle.rb

@ -0,0 +1,7 @@
class FeatureToggle
def self.supported_housing_schemes_enabled?
return true unless Rails.env.production?
false
end
end

8
config/routes.rb

@ -35,7 +35,11 @@ Rails.application.routes.draw do
get "edit/password", to: "users#edit_password"
end
resources :schemes, path: "/supported-housing", only: %i[index show]
resources :schemes, only: %i[index show] do
member do
get "locations", to: "schemes#locations"
end
end
resources :users do
member do
@ -50,7 +54,7 @@ Rails.application.routes.draw do
get "users", to: "organisations#users"
get "users/invite", to: "users/account#new"
get "logs", to: "organisations#logs"
get "supported-housing", to: "organisations#schemes"
get "schemes", to: "organisations#schemes"
end
end

17
db/migrate/20220614124115_create_locations.rb

@ -0,0 +1,17 @@
class CreateLocations < ActiveRecord::Migration[7.0]
def change
create_table :locations do |t|
t.string :location_code
t.string :postcode
t.string :type_of_unit
t.string :type_of_building
t.integer :wheelchair_adaptation
t.references :scheme, null: false, foreign_key: true
t.string :address_line1
t.string :address_line2
t.string :county
t.timestamps
end
end
end

18
db/schema.rb

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2022_06_13_123730) do
ActiveRecord::Schema[7.0].define(version: 2022_06_14_124115) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -232,6 +232,21 @@ ActiveRecord::Schema[7.0].define(version: 2022_06_13_123730) do
t.index ["start_year", "lettype", "beds", "la"], name: "index_la_rent_ranges_on_start_year_and_lettype_and_beds_and_la", unique: true
end
create_table "locations", force: :cascade do |t|
t.string "location_code"
t.string "postcode"
t.string "type_of_unit"
t.string "type_of_building"
t.integer "wheelchair_adaptation"
t.bigint "scheme_id", null: false
t.string "address_line1"
t.string "address_line2"
t.string "county"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["scheme_id"], name: "index_locations_on_scheme_id"
end
create_table "logs_exports", force: :cascade do |t|
t.datetime "created_at", default: -> { "CURRENT_TIMESTAMP" }
t.datetime "started_at", null: false
@ -349,5 +364,6 @@ ActiveRecord::Schema[7.0].define(version: 2022_06_13_123730) do
t.index ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id"
end
add_foreign_key "locations", "schemes"
add_foreign_key "schemes", "organisations"
end

40
db/seeds.rb

@ -70,7 +70,7 @@ unless Rails.env.test?
end
if Rails.env.development? && Scheme.count.zero?
Scheme.create!(
scheme1 = Scheme.create!(
code: "S878",
service_name: "Beulahside Care",
sensitive: 0,
@ -85,7 +85,7 @@ unless Rails.env.test?
created_at: Time.zone.now,
)
Scheme.create!(
scheme2 = Scheme.create!(
code: "S312",
service_name: "Abdullahview Point",
sensitive: 0,
@ -114,6 +114,42 @@ unless Rails.env.test?
organisation: dummy_org,
created_at: Time.zone.now,
)
Location.create!(
scheme: scheme1,
location_code: "S254-CU193AA",
postcode: "CU19 3AA",
address_line1: "Rectory Road",
address_line2: "North Chaim",
type_of_unit: "Self-contained flat or bedsit",
type_of_building: "Purpose-built",
county: "Mid Sussex",
wheelchair_adaptation: 0,
)
Location.create!(
scheme: scheme1,
location_code: "S254-DM250DC",
postcode: "DM25 0DC",
address_line1: "Smithy Lane",
address_line2: "North Kellieworth",
type_of_unit: "Self-contained flat or bedsit with common facilities",
type_of_building: "Converted from previous residential or non-residential property",
county: "Fife",
wheelchair_adaptation: 1,
)
Location.create!(
scheme: scheme2,
location_code: "S254-YX130WP",
postcode: "YX13 0WP",
address_line1: "Smithy Lane",
address_line2: "East Darwin",
type_of_unit: "Shared house or hostel",
type_of_building: "Converted from previous residential or non-residential property",
county: "Rochford",
wheelchair_adaptation: 1,
)
end
pp "Seeded 3 dummy schemes"

2
lib/tasks/data_import_field.rake

@ -9,7 +9,7 @@ namespace :core do
# We only allow a reduced list of known fields to be updatable
case field
when "tenant_code", "major_repairs"
when "tenant_code", "major_repairs", "lettings_allocation"
Imports::CaseLogsFieldImportService.new(storage_service).update_field(field, path)
else
raise "Field #{field} cannot be updated by data_import_field"

7
lib/tasks/lint.rake

@ -8,10 +8,15 @@ task erblint: :environment do
sh "bundle exec erblint --lint-all"
end
desc "Run Standard"
task standard: :environment do
sh "yarn standard"
end
desc "Run Stylelint"
task stylelint: :environment do
sh "yarn stylelint app/frontend/styles"
end
desc "Run all the linters"
task lint: %i[rubocop erblint stylelint]
task lint: %i[rubocop erblint standard stylelint]

8
package.json

@ -20,7 +20,7 @@
"css-loader": "^6.7.1",
"custom-event-polyfill": "^1.0.7",
"file-loader": "^6.2.0",
"govuk-frontend": "^4.0.1",
"govuk-frontend": "4.0.1",
"govuk-prototype-components": "^0.1.5",
"html5shiv": "^3.7.3",
"intersection-observer": "^0.12.0",
@ -36,6 +36,7 @@
"version": "0.1.0",
"devDependencies": {
"are-you-es5": "^2.1.2",
"standard": "^17.0.0",
"stylelint": "^14.7.1",
"stylelint-config-gds": "^0.2.0"
},
@ -51,6 +52,11 @@
"last 1 safari version"
]
},
"standard": {
"ignore": [
"app/frontend/vendor/*.js"
]
},
"stylelint": {
"extends": "stylelint-config-gds/scss"
},

8
spec/components/primary_navigation_component_spec.rb

@ -6,7 +6,7 @@ RSpec.describe PrimaryNavigationComponent, type: :component do
NavigationItemsHelper::NavigationItem.new("Organisations", "/organisations", true),
NavigationItemsHelper::NavigationItem.new("Users", "/users", false),
NavigationItemsHelper::NavigationItem.new("Logs ", "/logs", false),
NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false),
NavigationItemsHelper::NavigationItem.new("Schemes", "/schemes", false),
]
end
@ -36,7 +36,7 @@ RSpec.describe PrimaryNavigationComponent, type: :component do
expect(result.text).to include("Organisations")
expect(result.text).to include("Users")
expect(result.text).to include("Logs")
expect(result.text).to include("Supported housing")
expect(result.text).to include("Schemes")
end
end
@ -45,9 +45,9 @@ RSpec.describe PrimaryNavigationComponent, type: :component do
allow(Rails.env).to receive(:production?).and_return(true)
end
it "doesn't render supported housing" do
it "doesn't render schemes" do
result = render_inline(described_class.new(items:))
expect(result.text).not_to include("Supported housing")
expect(result.text).not_to include("Schemes")
end
end
end

3
spec/factories/case_log.rb

@ -16,6 +16,9 @@ FactoryBot.define do
ppostcode_full { Faker::Address.postcode }
age1 { 17 }
age2 { 19 }
renewal { 1 }
rent_type { 1 }
startdate { Time.zone.local(2021, 5, 1) }
end
trait :soft_validations_triggered do
status { 1 }

13
spec/factories/location.rb

@ -0,0 +1,13 @@
FactoryBot.define do
factory :location do
location_code { Faker::Name.initials(number: 10) }
postcode { Faker::Address.postcode }
address_line1 { Faker::Address.street_name }
address_line2 { Faker::Address.city }
type_of_unit { Faker::Lorem.word }
type_of_building { Faker::Lorem.word }
wheelchair_adaptation { 0 }
county { Faker::Address.state }
scheme
end
end

2
spec/factories/scheme.rb

@ -1,7 +1,7 @@
FactoryBot.define do
factory :scheme do
code { Faker::Name.initials(number: 4) }
service_name { Faker::Name.name_with_middle }
service_name { Faker::Name.name }
sensitive { Faker::Number.within(range: 0..1) }
registered_under_care_act { Faker::Number.within(range: 0..1) }
support_type { Faker::Number.within(range: 0..6) }

18
spec/features/form/tasklist_page_spec.rb

@ -27,6 +27,15 @@ RSpec.describe "Task List" do
managing_organisation: user.organisation,
)
end
let(:setup_completed_log) do
FactoryBot.create(
:case_log,
:about_completed,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
startdate: Time.zone.local(2021, 5, 1),
)
end
let(:id) { case_log.id }
let(:status) { case_log.status }
@ -40,14 +49,13 @@ RSpec.describe "Task List" do
end
it "shows number of completed sections if one section is completed" do
answer_all_questions_in_income_subsection(empty_case_log)
visit("/logs/#{empty_case_log.id}")
expect(page).to have_content("1 of 8 sections completed.")
visit("/logs/#{setup_completed_log.id}")
expect(page).to have_content("1 of 9 sections completed.")
end
it "show skip link for next incomplete section" do
answer_all_questions_in_income_subsection(empty_case_log)
visit("/logs/#{empty_case_log.id}")
answer_all_questions_in_income_subsection(setup_completed_log)
visit("/logs/#{setup_completed_log.id}")
expect(page).to have_link("Skip to next incomplete section", href: /#household-characteristics/)
end

61
spec/features/schemes_spec.rb

@ -1,6 +1,6 @@
require "rails_helper"
RSpec.describe "Supported housing scheme Features" do
RSpec.describe "Schemes scheme Features" do
context "when viewing list of schemes" do
context "when I am signed as a support user and there are schemes in the database" do
let(:user) { FactoryBot.create(:user, :support, last_sign_in_at: Time.zone.now) }
@ -25,13 +25,13 @@ RSpec.describe "Supported housing scheme Features" do
click_button("Submit")
end
it "displays the link to the supported housing" do
expect(page).to have_link("Supported housing", href: "/supported-housing")
it "displays the link to the schemes" do
expect(page).to have_link("Schemes", href: "/schemes")
end
context "when I click Supported housing" do
context "when I click schemes" do
before do
click_link "Supported housing", href: "/supported-housing"
click_link "Schemes", href: "/schemes"
end
it "shows list of schemes" do
@ -99,9 +99,9 @@ RSpec.describe "Supported housing scheme Features" do
click_button("Submit")
end
context "when I visit supported housing page" do
context "when I visit schemes page" do
before do
visit("supported-housing")
visit("schemes")
end
it "shows list of links to schemes" do
@ -112,8 +112,10 @@ RSpec.describe "Supported housing scheme Features" do
end
context "when I click to see individual scheme" do
let(:scheme) { schemes.first }
before do
click_link(schemes.first.service_name)
click_link(scheme.service_name)
end
it "shows me details about the selected scheme" do
@ -128,6 +130,49 @@ RSpec.describe "Supported housing scheme Features" do
expect(page).to have_content(schemes.first.support_type_display)
expect(page).to have_content(schemes.first.intended_stay_display)
end
context "when I click to go back" do
before do
visit("schemes")
click_link(scheme.service_name)
end
it "shows list of links to schemes" do
click_on("Back")
schemes.each do |scheme|
expect(page).to have_link(scheme.service_name)
expect(page).to have_content(scheme.primary_client_group_display)
end
end
end
context "when there are locations that belong to the selected scheme" do
let!(:schemes) { FactoryBot.create_list(:scheme, 5) }
let(:scheme) { schemes.first }
let!(:locations) { FactoryBot.create_list(:location, 3, scheme:) }
before do
visit("schemes")
click_link(scheme.service_name)
end
it "shows service and locations tab" do
expect(page).to have_link("Scheme")
expect(page).to have_link("#{scheme.locations.count} locations")
end
context "when I click locations link" do
before do
click_link("#{scheme.locations.count} locations")
end
it "shows details of those locations" do
locations.each do |location|
expect(page).to have_content(location.location_code)
end
end
end
end
end
end
end

2
spec/fixtures/exports/case_logs.xml vendored

@ -56,7 +56,7 @@
<reasonpref>1</reasonpref>
<cbl>1</cbl>
<chr>1</chr>
<cap>0</cap>
<cap>2</cap>
<reasonother/>
<housingneeds_a>1</housingneeds_a>
<housingneeds_b>0</housingneeds_b>

76
spec/fixtures/forms/2022_2023.json vendored

@ -3,82 +3,6 @@
"start_date": "2022-04-01T00:00:00.000+01:00",
"end_date": "2023-07-01T00:00:00.000+01:00",
"sections": {
"setup": {
"label": "Before you start",
"subsections": {
"setup": {
"label": "Set up this lettings log",
"pages": {
"renewal": {
"header": "",
"description": "",
"questions": {
"renewal": {
"check_answer_label": "Property renewal",
"header": "Is this a renewal to the same tenant in the same property?",
"hint_text": "",
"type": "radio",
"answer_options": {
"1": "Yes",
"0": "No"
}
}
}
},
"startdate": {
"header": "",
"description": "",
"questions": {
"startdate": {
"check_answer_label": "Tenancy start date",
"header": "What is the tenancy start date?",
"hint_text": "For example, 27 3 2007",
"type": "date"
}
}
},
"about_this_letting": {
"header": "Tell us about this letting",
"description": "",
"questions": {
"rent_type": {
"check_answer_label": "Rent type",
"header": "What is the rent type?",
"hint_text": "",
"type": "radio",
"answer_options": {
"0": "Social rent",
"1": "Affordable rent",
"2": "London Affordable rent",
"3": "Rent to buy",
"4": "London living rent",
"5": "Other intermediate rent product"
},
"conditional_for": {
"intermediate_rent_product_name": [5]
}
},
"intermediate_rent_product_name": {
"check_answer_label": "Product name",
"header": "What is intermediate rent product name?",
"type": "text"
},
"needstype": {
"check_answer_label": "Needs type",
"header": "What is the needs type?",
"hint_text": "",
"type": "radio",
"answer_options": {
"0": "Supported housing",
"1": "General needs"
}
}
}
}
}
}
}
},
"household": {
"label": "About the household",
"subsections": {

71
spec/fixtures/forms/setup/log_setup.json vendored

@ -0,0 +1,71 @@
{
"form_type": "setup",
"sections": {
"setup": {
"label": "Before you start",
"subsections": {
"setup": {
"label": "Set up this lettings log",
"pages": {
"renewal": {
"header": "",
"description": "",
"questions": {
"renewal": {
"check_answer_label": "Property renewal",
"header": "Is this a renewal to the same tenant in the same property?",
"hint_text": "",
"type": "radio",
"answer_options": {
"1": "Yes",
"0": "No"
}
}
}
},
"startdate": {
"header": "",
"description": "",
"questions": {
"startdate": {
"check_answer_label": "Tenancy start date",
"header": "What is the tenancy start date?",
"hint_text": "For example, 27 3 2007",
"type": "date"
}
}
},
"about_this_letting": {
"header": "Tell us about this letting",
"description": "",
"questions": {
"rent_type": {
"check_answer_label": "Rent type",
"header": "What is the rent type?",
"hint_text": "",
"type": "radio",
"answer_options": {
"0": "Social rent",
"1": "Affordable rent",
"2": "London Affordable rent",
"3": "Rent to buy",
"4": "London living rent",
"5": "Other intermediate rent product"
},
"conditional_for": {
"intermediate_rent_product_name": [5]
}
},
"intermediate_rent_product_name": {
"check_answer_label": "Product name",
"header": "What is intermediate rent product name?",
"type": "text"
}
}
}
}
}
}
}
}
}

4
spec/fixtures/softwire_imports/case_logs/166fc004-392e-47a8-acb8-1c018734882b.xml vendored

@ -2,7 +2,7 @@
<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-IR-GN</meta:form-name>
<meta:document-id>166fc004-392e-47a8-acb8-1c018734882b</meta:document-id>
<meta:owner-user-id>e29c492473446dca4d50224f2bb7cf965a261d6f</meta:owner-user-id>
<meta:owner-user-id>c3061a2e6ea0b702e6f6210d5c52d2a92612d2aa</meta:owner-user-id>
<meta:owner-institution-id>7c5bd5fb549c09a2c55d7cb90d7ba84927e64618</meta:owner-institution-id>
<meta:managing-institution-id>7c5bd5fb549c09a2c55d7cb90d7ba84927e64618</meta:managing-institution-id>
<meta:created-date>2022-04-12T14:10:59.953121Z</meta:created-date>
@ -133,7 +133,7 @@
<Group>
<Q15CBL>2 No</Q15CBL>
<Q15CHR>2 No</Q15CHR>
<Q15CAP>2 No</Q15CAP>
<Q15CAP>1 Yes</Q15CAP>
</Group>
<Group>
<Q16>10 Other social landlord</Q16>

2
spec/fixtures/softwire_imports/case_logs/5ybz29dj-l33t-k1l0-hj86-n4k4ma77xkcd.xml vendored

@ -131,7 +131,7 @@
<Q14b5/>
</Group>
<Group>
<Q15CBL>2 No</Q15CBL>
<Q15CBL>1 Yes</Q15CBL>
<Q15CHR>2 No</Q15CHR>
<Q15CAP>2 No</Q15CAP>
</Group>

2
spec/fixtures/softwire_imports/case_logs/893ufj2s-lq77-42m4-rty6-ej09gh585uy1.xml vendored

@ -2,7 +2,7 @@
<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-IR-GN</meta:form-name>
<meta:document-id>893ufj2s-lq77-42m4-rty6-ej09gh585uy1</meta:document-id>
<meta:owner-user-id>e29c492473446dca4d50224f2bb7cf965a261d6f</meta:owner-user-id>
<meta:owner-user-id>c3061a2e6ea0b702e6f6210d5c52d2a92612d2aa</meta:owner-user-id>
<meta:owner-institution-id>7c5bd5fb549c09a2c55d7cb90d7ba84927e64618</meta:owner-institution-id>
<meta:managing-institution-id>7c5bd5fb549c09a2c55d7cb90d7ba84927e64618</meta:managing-institution-id>
<meta:created-date>2022-04-11T13:46:23.953121Z</meta:created-date>

85
spec/helpers/navigation_items_helper_spec.rb

@ -12,7 +12,7 @@ RSpec.describe NavigationItemsHelper do
let(:expected_navigation_items) do
[
NavigationItemsHelper::NavigationItem.new("Logs", "/logs", true),
NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false),
NavigationItemsHelper::NavigationItem.new("Schemes", "/schemes", false),
NavigationItemsHelper::NavigationItem.new("Users", users_path, false),
NavigationItemsHelper::NavigationItem.new("About your organisation", organisation_path, false),
]
@ -27,7 +27,7 @@ RSpec.describe NavigationItemsHelper do
let(:expected_navigation_items) do
[
NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false),
NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false),
NavigationItemsHelper::NavigationItem.new("Schemes", "/schemes", false),
NavigationItemsHelper::NavigationItem.new("Users", users_path, true),
NavigationItemsHelper::NavigationItem.new("About your organisation", organisation_path, false),
]
@ -42,7 +42,7 @@ RSpec.describe NavigationItemsHelper do
let(:expected_navigation_items) do
[
NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false),
NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false),
NavigationItemsHelper::NavigationItem.new("Schemes", "/schemes", false),
NavigationItemsHelper::NavigationItem.new("Users", users_path, false),
NavigationItemsHelper::NavigationItem.new("About your organisation", organisation_path, true),
]
@ -57,7 +57,7 @@ RSpec.describe NavigationItemsHelper do
let(:expected_navigation_items) do
[
NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false),
NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false),
NavigationItemsHelper::NavigationItem.new("Schemes", "/schemes", false),
NavigationItemsHelper::NavigationItem.new("Users", "/organisations/#{current_user.organisation.id}/users", false),
NavigationItemsHelper::NavigationItem.new("About your organisation", organisation_path, false),
]
@ -72,7 +72,7 @@ RSpec.describe NavigationItemsHelper do
let(:expected_navigation_items) do
[
NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false),
NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false),
NavigationItemsHelper::NavigationItem.new("Schemes", "/schemes", false),
NavigationItemsHelper::NavigationItem.new("Users", "/organisations/#{current_user.organisation.id}/users", true),
NavigationItemsHelper::NavigationItem.new("About your organisation", organisation_path, false),
]
@ -87,14 +87,14 @@ RSpec.describe NavigationItemsHelper do
let(:expected_navigation_items) do
[
NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false),
NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", true),
NavigationItemsHelper::NavigationItem.new("Schemes", "/schemes", true),
NavigationItemsHelper::NavigationItem.new("Users", "/organisations/#{current_user.organisation.id}/users", false),
NavigationItemsHelper::NavigationItem.new("About your organisation", organisation_path, false),
]
end
it "returns navigation items with supported housing item set as current" do
expect(primary_items("/supported-housing/1", current_user)).to eq(expected_navigation_items)
it "returns navigation items with Schemes item set as current" do
expect(primary_items("/schemes/1", current_user)).to eq(expected_navigation_items)
end
end
end
@ -108,7 +108,7 @@ RSpec.describe NavigationItemsHelper do
NavigationItemsHelper::NavigationItem.new("Organisations", "/organisations", false),
NavigationItemsHelper::NavigationItem.new("Users", "/users", false),
NavigationItemsHelper::NavigationItem.new("Logs", "/logs", true),
NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false),
NavigationItemsHelper::NavigationItem.new("Schemes", "/schemes", false),
]
end
@ -123,7 +123,7 @@ RSpec.describe NavigationItemsHelper do
NavigationItemsHelper::NavigationItem.new("Organisations", "/organisations", false),
NavigationItemsHelper::NavigationItem.new("Users", "/users", true),
NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false),
NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false),
NavigationItemsHelper::NavigationItem.new("Schemes", "/schemes", false),
]
end
@ -138,7 +138,7 @@ RSpec.describe NavigationItemsHelper do
NavigationItemsHelper::NavigationItem.new("Organisations", "/organisations", false),
NavigationItemsHelper::NavigationItem.new("Users", "/users", false),
NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false),
NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false),
NavigationItemsHelper::NavigationItem.new("Schemes", "/schemes", false),
]
end
@ -147,18 +147,18 @@ RSpec.describe NavigationItemsHelper do
end
end
context "when the user is on the supported housing page" do
context "when the user is on the Schemes page" do
let(:expected_navigation_items) do
[
NavigationItemsHelper::NavigationItem.new("Organisations", "/organisations", false),
NavigationItemsHelper::NavigationItem.new("Users", "/users", false),
NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false),
NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", true),
NavigationItemsHelper::NavigationItem.new("Schemes", "/schemes", true),
]
end
it "returns navigation items with the users item set as current" do
expect(primary_items("/supported-housing", current_user)).to eq(expected_navigation_items)
expect(primary_items("/schemes", current_user)).to eq(expected_navigation_items)
end
end
@ -168,7 +168,7 @@ RSpec.describe NavigationItemsHelper do
NavigationItemsHelper::NavigationItem.new("Organisations", "/organisations", false),
NavigationItemsHelper::NavigationItem.new("Users", "/users", true),
NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false),
NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false),
NavigationItemsHelper::NavigationItem.new("Schemes", "/schemes", false),
]
end
@ -183,12 +183,43 @@ RSpec.describe NavigationItemsHelper do
NavigationItemsHelper::NavigationItem.new("Organisations", "/organisations", false),
NavigationItemsHelper::NavigationItem.new("Users", "/users", false),
NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false),
NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", true),
NavigationItemsHelper::NavigationItem.new("Schemes", "/schemes", true),
]
end
it "returns navigation items with supported housing item set as current" do
expect(primary_items("/supported-housing/1", current_user)).to eq(expected_navigation_items)
let(:expected_scheme_items) do
[
NavigationItemsHelper::NavigationItem.new("Scheme", "/schemes/1", true),
NavigationItemsHelper::NavigationItem.new("1 location", "/schemes/1/locations", false),
]
end
it "returns navigation items with Schemes item set as current" do
expect(primary_items("/schemes/1", current_user)).to eq(expected_navigation_items)
expect(scheme_items("/schemes/1", 1, "1 location")).to eq(expected_scheme_items)
end
end
context "when the user is on the scheme locations page" do
let(:expected_navigation_items) do
[
NavigationItemsHelper::NavigationItem.new("Organisations", "/organisations", false),
NavigationItemsHelper::NavigationItem.new("Users", "/users", false),
NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false),
NavigationItemsHelper::NavigationItem.new("Schemes", "/schemes", true),
]
end
let(:expected_scheme_items) do
[
NavigationItemsHelper::NavigationItem.new("Scheme", "/schemes/1", false),
NavigationItemsHelper::NavigationItem.new("1 location", "/schemes/1/locations", true),
]
end
it "returns navigation items with Schemes item set as current" do
expect(primary_items("/schemes/1/locations", current_user)).to eq(expected_navigation_items)
expect(scheme_items("/schemes/1/locations", 1, "1 location")).to eq(expected_scheme_items)
end
end
@ -200,14 +231,14 @@ RSpec.describe NavigationItemsHelper do
NavigationItemsHelper::NavigationItem.new("Organisations", "/organisations", true),
NavigationItemsHelper::NavigationItem.new("Users", "/users", false),
NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false),
NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false),
NavigationItemsHelper::NavigationItem.new("Schemes", "/schemes", false),
]
end
let(:expected_secondary_navigation_items) do
[
NavigationItemsHelper::NavigationItem.new("Logs", "/organisations/#{current_user.organisation.id}/logs", true),
NavigationItemsHelper::NavigationItem.new("Supported housing", "/organisations/#{current_user.organisation.id}/supported-housing", false),
NavigationItemsHelper::NavigationItem.new("Schemes", "/organisations/#{current_user.organisation.id}/schemes", false),
NavigationItemsHelper::NavigationItem.new("Users", "/organisations/#{current_user.organisation.id}/users", false),
NavigationItemsHelper::NavigationItem.new("About this organisation", "/organisations/#{current_user.organisation.id}", false),
]
@ -226,14 +257,14 @@ RSpec.describe NavigationItemsHelper do
NavigationItemsHelper::NavigationItem.new("Organisations", "/organisations", true),
NavigationItemsHelper::NavigationItem.new("Users", "/users", false),
NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false),
NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false),
NavigationItemsHelper::NavigationItem.new("Schemes", "/schemes", false),
]
end
let(:expected_secondary_navigation_items) do
[
NavigationItemsHelper::NavigationItem.new("Logs", "/organisations/#{current_user.organisation.id}/logs", false),
NavigationItemsHelper::NavigationItem.new("Supported housing", "/organisations/#{current_user.organisation.id}/supported-housing", false),
NavigationItemsHelper::NavigationItem.new("Schemes", "/organisations/#{current_user.organisation.id}/schemes", false),
NavigationItemsHelper::NavigationItem.new("Users", "/organisations/#{current_user.organisation.id}/users", true),
NavigationItemsHelper::NavigationItem.new("About this organisation", "/organisations/#{current_user.organisation.id}", false),
]
@ -246,20 +277,20 @@ RSpec.describe NavigationItemsHelper do
end
context "when the user is on organisation schemes page" do
let(:required_sub_path) { "supported-housing" }
let(:required_sub_path) { "schemes" }
let(:expected_navigation_items) do
[
NavigationItemsHelper::NavigationItem.new("Organisations", "/organisations", true),
NavigationItemsHelper::NavigationItem.new("Users", "/users", false),
NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false),
NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false),
NavigationItemsHelper::NavigationItem.new("Schemes", "/schemes", false),
]
end
let(:expected_secondary_navigation_items) do
[
NavigationItemsHelper::NavigationItem.new("Logs", "/organisations/#{current_user.organisation.id}/logs", false),
NavigationItemsHelper::NavigationItem.new("Supported housing", "/organisations/#{current_user.organisation.id}/supported-housing", true),
NavigationItemsHelper::NavigationItem.new("Schemes", "/organisations/#{current_user.organisation.id}/schemes", true),
NavigationItemsHelper::NavigationItem.new("Users", "/organisations/#{current_user.organisation.id}/users", false),
NavigationItemsHelper::NavigationItem.new("About this organisation", "/organisations/#{current_user.organisation.id}", false),
]
@ -278,14 +309,14 @@ RSpec.describe NavigationItemsHelper do
NavigationItemsHelper::NavigationItem.new("Organisations", "/organisations", true),
NavigationItemsHelper::NavigationItem.new("Users", "/users", false),
NavigationItemsHelper::NavigationItem.new("Logs", "/logs", false),
NavigationItemsHelper::NavigationItem.new("Supported housing", "/supported-housing", false),
NavigationItemsHelper::NavigationItem.new("Schemes", "/schemes", false),
]
end
let(:expected_secondary_navigation_items) do
[
NavigationItemsHelper::NavigationItem.new("Logs", "/organisations/#{current_user.organisation.id}/logs", false),
NavigationItemsHelper::NavigationItem.new("Supported housing", "/organisations/#{current_user.organisation.id}/supported-housing", false),
NavigationItemsHelper::NavigationItem.new("Schemes", "/organisations/#{current_user.organisation.id}/schemes", false),
NavigationItemsHelper::NavigationItem.new("Users", "/organisations/#{current_user.organisation.id}/users", false),
NavigationItemsHelper::NavigationItem.new("About this organisation", "/organisations/#{current_user.organisation.id}", true),
]

4
spec/helpers/tab_nav_helper_spec.rb

@ -3,7 +3,7 @@ require "rails_helper"
RSpec.describe TabNavHelper do
let(:organisation) { FactoryBot.create(:organisation) }
let(:user) { FactoryBot.build(:user, organisation:) }
let(:scheme) { FactoryBot.build(:scheme) }
let(:scheme) { FactoryBot.build(:scheme, service_name: "Some name") }
describe "#user_cell" do
it "returns user link and email separated by a newline character" do
@ -21,7 +21,7 @@ RSpec.describe TabNavHelper do
describe "#scheme_cell" do
it "returns the scheme link service name and primary user group separated by a newline character" do
expected_html = "<a class=\"govuk-link\" href=\"/supported-housing\">#{scheme.service_name}</a>\n<span class=\"govuk-visually-hidden\">Scheme </span><span class=\"govuk-!-font-weight-regular app-!-colour-muted\">#{scheme.primary_client_group_display}</span>"
expected_html = "<a class=\"govuk-link\" href=\"/schemes\">#{scheme.service_name}</a>\n<span class=\"govuk-visually-hidden\">Scheme </span><span class=\"govuk-!-font-weight-regular app-!-colour-muted\">#{scheme.primary_client_group_display}</span>"
expect(scheme_cell(scheme)).to match(expected_html)
end
end

4
spec/helpers/tasklist_helper_spec.rb

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

14
spec/lib/tasks/date_import_field_spec.rb

@ -42,6 +42,20 @@ describe "rake core:data_import_field", type: :task do
end
end
context "and we update the lettings_allocation fields" do
let(:field) { "lettings_allocation" }
it "properly configures the storage service" do
expect(StorageService).to receive(:new).with(paas_config_service, instance_name)
task.invoke(field, fixture_path)
end
it "calls the expected update method with parameters" do
expect(import_service).to receive(:update_field).with(field, fixture_path)
task.invoke(field, fixture_path)
end
end
context "and we update the major repairs fields" do
let(:field) { "major_repairs" }

33
spec/models/case_log_spec.rb

@ -211,10 +211,17 @@ RSpec.describe CaseLog do
})
end
it "derives that all forms are general needs" do
record_from_db = ActiveRecord::Base.connection.execute("select needstype from case_logs where id=#{case_log.id}").to_a[0]
expect(record_from_db["needstype"]).to eq(1)
expect(case_log["needstype"]).to eq(1)
context "when a case log is created in production" do
before do
allow(Rails.env).to receive(:production?).and_return(true)
end
it "derives that all forms are general needs" do
case_log = FactoryBot.create(:case_log)
record_from_db = ActiveRecord::Base.connection.execute("select needstype from case_logs where id=#{case_log.id}").to_a[0]
expect(record_from_db["needstype"]).to eq(1)
expect(case_log["needstype"]).to eq(1)
end
end
it "correctly derives and saves partial and full major repairs date" do
@ -2097,4 +2104,22 @@ RSpec.describe CaseLog do
end
end
end
describe "supported_housing_schemes_enabled?" do
it "returns true for the case log if the environment is not production" do
case_log = FactoryBot.create(:case_log)
expect(case_log.supported_housing_schemes_enabled?).to eq(true)
end
context "when in the production environment" do
before do
allow(Rails.env).to receive(:production?).and_return(true)
end
it "returns false for a case log" do
case_log = FactoryBot.create(:case_log)
expect(case_log.supported_housing_schemes_enabled?).to eq(false)
end
end
end
end

4
spec/models/form_handler_spec.rb

@ -17,7 +17,7 @@ RSpec.describe FormHandler do
form_handler = described_class.instance
form = form_handler.get_form(test_form_name)
expect(form).to be_a(Form)
expect(form.pages.count).to eq(34)
expect(form.pages.count).to eq(37)
end
end
@ -32,7 +32,7 @@ RSpec.describe FormHandler do
it "loads the form once at boot time" do
form_handler = described_class.instance
expect(Form).not_to receive(:new).with(:any, test_form_name)
expect(Form).not_to receive(:new).with(:any, test_form_name, :any)
expect(form_handler.get_form(test_form_name)).to be_a(Form)
end
end

2
spec/models/organisation_spec.rb

@ -87,7 +87,7 @@ RSpec.describe Organisation, type: :model do
end
it "has case logs" do
expect(organisation.case_logs.to_a).to eq([owned_case_log, managed_case_log])
expect(organisation.case_logs.to_a).to match_array([owned_case_log, managed_case_log])
end
it "has case log status helper methods" do

3
spec/models/rent_period_spec.rb

@ -2,7 +2,8 @@ require "rails_helper"
RSpec.describe RentPeriod, type: :model do
describe "rent period mapping" do
let(:form) { Form.new("spec/fixtures/forms/2021_2022.json", "2021_2022") }
let(:setup_path) { "spec/fixtures/forms/setup/log_setup.json" }
let(:form) { Form.new("spec/fixtures/forms/2021_2022.json", "2021_2022", setup_path) }
before do
allow(FormHandler.instance).to receive(:current_form).and_return(form)

7
spec/requests/case_logs_controller_spec.rb

@ -288,7 +288,8 @@ RSpec.describe CaseLogsController, type: :request do
mrcdate: Time.zone.local(2022, 2, 1),
startdate: Time.zone.local(2022, 12, 1),
tenancy: 6,
managing_organisation: organisation)
managing_organisation: organisation,
tenant_code: nil)
end
it "shows case logs for multiple selected statuses and years" do
@ -622,7 +623,7 @@ RSpec.describe CaseLogsController, type: :request do
end
it "displays a section status for a case log" do
assert_select ".govuk-tag", text: /Not started/, count: 6
assert_select ".govuk-tag", text: /Not started/, count: 7
assert_select ".govuk-tag", text: /In progress/, count: 1
assert_select ".govuk-tag", text: /Completed/, count: 0
assert_select ".govuk-tag", text: /Cannot start yet/, count: 1
@ -645,7 +646,7 @@ RSpec.describe CaseLogsController, type: :request do
end
it "displays a section status for a case log" do
assert_select ".govuk-tag", text: /Not started/, count: 6
assert_select ".govuk-tag", text: /Not started/, count: 7
assert_select ".govuk-tag", text: /Completed/, count: 1
assert_select ".govuk-tag", text: /Cannot start yet/, count: 1
end

26
spec/requests/organisations_controller_spec.rb

@ -31,8 +31,8 @@ RSpec.describe OrganisationsController, type: :request do
expect(response).to redirect_to("/account/sign-in")
end
it "does not let you see supported housing list" do
get "/organisations/#{organisation.id}/supported-housing", headers: headers, params: {}
it "does not let you see schemes list" do
get "/organisations/#{organisation.id}/schemes", headers: headers, params: {}
expect(response).to redirect_to("/account/sign-in")
end
end
@ -48,11 +48,11 @@ RSpec.describe OrganisationsController, type: :request do
before do
allow(user).to receive(:need_two_factor_authentication?).and_return(false)
sign_in user
get "/organisations/#{organisation.id}/supported-housing", headers:, params: {}
get "/organisations/#{organisation.id}/schemes", headers:, params: {}
end
it "has page heading" do
expect(page).to have_content("Supported housing services")
expect(page).to have_content("Schemes")
end
it "shows a search bar" do
@ -60,7 +60,7 @@ RSpec.describe OrganisationsController, type: :request do
end
it "has hidden accebility field with description" do
expected_field = "<h2 class=\"govuk-visually-hidden\">Supported housing services</h2>"
expected_field = "<h2 class=\"govuk-visually-hidden\">Supported housing schemes</h2>"
expect(CGI.unescape_html(response.body)).to include(expected_field)
end
@ -77,7 +77,7 @@ RSpec.describe OrganisationsController, type: :request do
before do
allow(user).to receive(:need_two_factor_authentication?).and_return(false)
get "/organisations/#{organisation.id}/supported-housing?search=#{search_param}"
get "/organisations/#{organisation.id}/schemes?search=#{search_param}"
end
it "returns matching results" do
@ -104,19 +104,19 @@ RSpec.describe OrganisationsController, type: :request do
before do
sign_in user
get "/organisations/#{organisation.id}/supported-housing", headers:, params: {}
get "/organisations/#{organisation.id}/schemes", headers:, params: {}
end
it "has page heading" do
expect(page).to have_content("Supported housing services")
expect(page).to have_content("Schemes")
end
it "shows a search bar" do
expect(page).to have_field("search", type: "search")
end
it "has hidden accebility field with description" do
expected_field = "<h2 class=\"govuk-visually-hidden\">Supported housing services</h2>"
it "has hidden accessibility field with description" do
expected_field = "<h2 class=\"govuk-visually-hidden\">Supported housing schemes</h2>"
expect(CGI.unescape_html(response.body)).to include(expected_field)
end
@ -131,7 +131,7 @@ RSpec.describe OrganisationsController, type: :request do
let!(:unauthorised_organisation) { FactoryBot.create(:organisation) }
before do
get "/organisations/#{unauthorised_organisation.id}/supported-housing", headers:, params: {}
get "/organisations/#{unauthorised_organisation.id}/schemes", headers:, params: {}
end
it "returns not found 404 from org details route" do
@ -144,7 +144,7 @@ RSpec.describe OrganisationsController, type: :request do
let(:search_param) { "CODE321" }
before do
get "/organisations/#{organisation.id}/supported-housing?search=#{search_param}"
get "/organisations/#{organisation.id}/schemes?search=#{search_param}"
end
it "returns matching results" do
@ -159,7 +159,7 @@ RSpec.describe OrganisationsController, type: :request do
end
it "has search in the title" do
expect(page).to have_title("Supported housing services (1 scheme matching ‘#{search_param}’) - Submit social housing lettings and sales data (CORE) - GOV.UK")
expect(page).to have_title("Supported housing schemes (1 scheme matching ‘#{search_param}’) - Submit social housing lettings and sales data (CORE) - GOV.UK")
end
end
end

235
spec/requests/schemes_controller_spec.rb

@ -10,7 +10,7 @@ RSpec.describe SchemesController, type: :request do
describe "#index" do
context "when not signed in" do
it "redirects to the sign in page" do
get "/supported-housing"
get "/schemes"
expect(response).to redirect_to("/account/sign-in")
end
end
@ -20,7 +20,7 @@ RSpec.describe SchemesController, type: :request do
before do
sign_in user
get "/supported-housing"
get "/schemes"
end
it "returns 401 unauthorized" do
@ -34,12 +34,12 @@ RSpec.describe SchemesController, type: :request do
before do
sign_in user
get "/supported-housing"
get "/schemes"
end
it "redirects to the organisation schemes path" do
follow_redirect!
expect(path).to match("/organisations/#{user.organisation.id}/supported-housing")
expect(path).to match("/organisations/#{user.organisation.id}/schemes")
end
end
@ -47,11 +47,11 @@ RSpec.describe SchemesController, type: :request do
before do
allow(user).to receive(:need_two_factor_authentication?).and_return(false)
sign_in user
get "/supported-housing"
get "/schemes"
end
it "has page heading" do
expect(page).to have_content("Supported housing services")
expect(page).to have_content("Schemes")
end
it "shows all schemes" do
@ -65,7 +65,7 @@ RSpec.describe SchemesController, type: :request do
end
it "has correct title" do
expect(page).to have_title("Supported housing services - Submit social housing lettings and sales data (CORE) - GOV.UK")
expect(page).to have_title("Supported housing schemes - Submit social housing lettings and sales data (CORE) - GOV.UK")
end
it "shows the total organisations count" do
@ -73,7 +73,7 @@ RSpec.describe SchemesController, type: :request do
end
it "has hidden accebility field with description" do
expected_field = "<h2 class=\"govuk-visually-hidden\">Supported housing services</h2>"
expected_field = "<h2 class=\"govuk-visually-hidden\">Supported housing schemes</h2>"
expect(CGI.unescape_html(response.body)).to include(expected_field)
end
@ -86,7 +86,7 @@ RSpec.describe SchemesController, type: :request do
context "when on the first page" do
before do
get "/supported-housing"
get "/schemes"
end
it "shows the total schemes count" do
@ -98,7 +98,7 @@ RSpec.describe SchemesController, type: :request do
end
it "has correct page 1 of 2 title" do
expect(page).to have_title("Supported housing services (page 1 of 2) - Submit social housing lettings and sales data (CORE) - GOV.UK")
expect(page).to have_title("Supported housing schemes (page 1 of 2) - Submit social housing lettings and sales data (CORE) - GOV.UK")
end
it "has pagination links" do
@ -111,7 +111,7 @@ RSpec.describe SchemesController, type: :request do
context "when on the second page" do
before do
get "/supported-housing?page=2"
get "/schemes?page=2"
end
it "shows the total schemes count" do
@ -130,7 +130,7 @@ RSpec.describe SchemesController, type: :request do
end
it "has correct page 1 of 2 title" do
expect(page).to have_title("Supported housing services (page 2 of 2) - Submit social housing lettings and sales data (CORE) - GOV.UK")
expect(page).to have_title("Supported housing schemes (page 2 of 2) - Submit social housing lettings and sales data (CORE) - GOV.UK")
end
end
end
@ -140,7 +140,7 @@ RSpec.describe SchemesController, type: :request do
let(:search_param) { "CODE321" }
before do
get "/supported-housing?search=#{search_param}"
get "/schemes?search=#{search_param}"
end
it "returns matching results" do
@ -155,7 +155,7 @@ RSpec.describe SchemesController, type: :request do
end
it "has search in the title" do
expect(page).to have_title("Supported housing services (1 scheme matching ‘#{search_param}’) - Submit social housing lettings and sales data (CORE) - GOV.UK")
expect(page).to have_title("Supported housing schemes (1 scheme matching ‘#{search_param}’) - Submit social housing lettings and sales data (CORE) - GOV.UK")
end
end
end
@ -166,7 +166,7 @@ RSpec.describe SchemesController, type: :request do
context "when not signed in" do
it "redirects to the sign in page" do
get "/supported-housing/#{specific_scheme.id}"
get "/schemes/#{specific_scheme.id}"
expect(response).to redirect_to("/account/sign-in")
end
end
@ -176,7 +176,7 @@ RSpec.describe SchemesController, type: :request do
before do
sign_in user
get "/supported-housing/#{specific_scheme.id}"
get "/schemes/#{specific_scheme.id}"
end
it "returns 401 unauthorized" do
@ -194,7 +194,7 @@ RSpec.describe SchemesController, type: :request do
end
it "has page heading" do
get "/supported-housing/#{specific_scheme.id}"
get "/schemes/#{specific_scheme.id}"
expect(page).to have_content(specific_scheme.code)
expect(page).to have_content(specific_scheme.service_name)
expect(page).to have_content(specific_scheme.organisation.name)
@ -211,11 +211,11 @@ RSpec.describe SchemesController, type: :request do
expect(page).to have_content(specific_scheme.intended_stay_display)
end
context "when coordinator attempts to see scheme belogning to a different organisation" do
context "when coordinator attempts to see scheme belonging to a different organisation" do
let!(:specific_scheme) { FactoryBot.create(:scheme) }
it "returns 404 not found" do
get "/supported-housing/#{specific_scheme.id}"
get "/schemes/#{specific_scheme.id}"
expect(response).to have_http_status(:not_found)
end
end
@ -225,7 +225,7 @@ RSpec.describe SchemesController, type: :request do
before do
allow(user).to receive(:need_two_factor_authentication?).and_return(false)
sign_in user
get "/supported-housing/#{specific_scheme.id}"
get "/schemes/#{specific_scheme.id}"
end
it "has page heading" do
@ -246,4 +246,199 @@ RSpec.describe SchemesController, type: :request do
end
end
end
describe "#locations" do
let(:specific_scheme) { schemes.first }
context "when not signed in" do
it "redirects to the sign in page" do
get "/schemes/#{specific_scheme.id}/locations"
expect(response).to redirect_to("/account/sign-in")
end
end
context "when signed in as a data provider user" do
let(:user) { FactoryBot.create(:user) }
before do
sign_in user
get "/schemes/#{specific_scheme.id}/locations"
end
it "returns 401 unauthorized" do
request
expect(response).to have_http_status(:unauthorized)
end
end
context "when signed in as a data coordinator user" do
let(:user) { FactoryBot.create(:user, :data_coordinator) }
let!(:scheme) { FactoryBot.create(:scheme, organisation: user.organisation) }
let!(:locations) { FactoryBot.create_list(:location, 3, scheme:) }
before do
sign_in user
get "/schemes/#{scheme.id}/locations"
end
context "when coordinator attempts to see scheme belonging to a different organisation" do
let!(:specific_scheme) { FactoryBot.create(:scheme) }
before do
FactoryBot.create(:location, scheme: specific_scheme)
end
it "returns 404 not found" do
get "/schemes/#{specific_scheme.id}/locations"
expect(response).to have_http_status(:not_found)
end
end
it "shows scheme" do
locations.each do |location|
expect(page).to have_content(location.location_code)
expect(page).to have_content(location.postcode)
expect(page).to have_content(location.county)
expect(page).to have_content(location.type_of_unit)
expect(page).to have_content(location.type_of_building)
expect(page).to have_content(location.wheelchair_adaptation)
expect(page).to have_content(location.address_line1)
expect(page).to have_content(location.address_line2)
end
end
it "has page heading" do
expect(page).to have_content(scheme.service_name)
end
it "has correct title" do
expect(page).to have_title("#{scheme.service_name} - Submit social housing lettings and sales data (CORE) - GOV.UK")
end
context "when paginating over 20 results" do
let!(:locations) { FactoryBot.create_list(:location, 25, scheme:) }
context "when on the first page" do
before do
get "/schemes/#{scheme.id}/locations"
end
it "shows which schemes are being shown on the current page" do
expect(CGI.unescape_html(response.body)).to match("Showing <b>1</b> to <b>20</b> of <b>#{locations.count}</b> locations")
end
it "has correct page 1 of 2 title" do
expect(page).to have_title("#{scheme.service_name} (page 1 of 2) - Submit social housing lettings and sales data (CORE) - GOV.UK")
end
it "has pagination links" do
expect(page).to have_content("Previous")
expect(page).not_to have_link("Previous")
expect(page).to have_content("Next")
expect(page).to have_link("Next")
end
end
context "when on the second page" do
before do
get "/schemes/#{scheme.id}/locations?page=2"
end
it "shows which schemes are being shown on the current page" do
expect(CGI.unescape_html(response.body)).to match("Showing <b>21</b> to <b>25</b> of <b>#{locations.count}</b> locations")
end
it "has correct page 1 of 2 title" do
expect(page).to have_title("#{scheme.service_name} (page 2 of 2) - Submit social housing lettings and sales data (CORE) - GOV.UK")
end
it "has pagination links" do
expect(page).to have_content("Previous")
expect(page).to have_link("Previous")
expect(page).to have_content("Next")
expect(page).not_to have_link("Next")
end
end
end
end
context "when signed in as a support user" do
let(:user) { FactoryBot.create(:user, :support) }
let!(:scheme) { FactoryBot.create(:scheme) }
let!(:locations) { FactoryBot.create_list(:location, 3, scheme:) }
before do
allow(user).to receive(:need_two_factor_authentication?).and_return(false)
sign_in user
get "/schemes/#{scheme.id}/locations"
end
it "shows scheme" do
locations.each do |location|
expect(page).to have_content(location.location_code)
expect(page).to have_content(location.postcode)
expect(page).to have_content(location.county)
expect(page).to have_content(location.type_of_unit)
expect(page).to have_content(location.type_of_building)
expect(page).to have_content(location.wheelchair_adaptation)
expect(page).to have_content(location.address_line1)
expect(page).to have_content(location.address_line2)
end
end
it "has page heading" do
expect(page).to have_content(scheme.service_name)
end
it "has correct title" do
expect(page).to have_title("#{scheme.service_name} - Submit social housing lettings and sales data (CORE) - GOV.UK")
end
context "when paginating over 20 results" do
let!(:locations) { FactoryBot.create_list(:location, 25, scheme:) }
context "when on the first page" do
before do
get "/schemes/#{scheme.id}/locations"
end
it "shows which schemes are being shown on the current page" do
expect(CGI.unescape_html(response.body)).to match("Showing <b>1</b> to <b>20</b> of <b>#{locations.count}</b> locations")
end
it "has correct page 1 of 2 title" do
expect(page).to have_title("#{scheme.service_name} (page 1 of 2) - Submit social housing lettings and sales data (CORE) - GOV.UK")
end
it "has pagination links" do
expect(page).to have_content("Previous")
expect(page).not_to have_link("Previous")
expect(page).to have_content("Next")
expect(page).to have_link("Next")
end
end
context "when on the second page" do
before do
get "/schemes/#{scheme.id}/locations?page=2"
end
it "shows which schemes are being shown on the current page" do
expect(CGI.unescape_html(response.body)).to match("Showing <b>21</b> to <b>25</b> of <b>#{locations.count}</b> locations")
end
it "has correct page 1 of 2 title" do
expect(page).to have_title("#{scheme.service_name} (page 2 of 2) - Submit social housing lettings and sales data (CORE) - GOV.UK")
end
it "has pagination links" do
expect(page).to have_content("Previous")
expect(page).to have_link("Previous")
expect(page).to have_content("Next")
expect(page).not_to have_link("Next")
end
end
end
end
end
end

122
spec/services/imports/case_logs_field_import_service_spec.rb

@ -6,7 +6,8 @@ RSpec.describe Imports::CaseLogsFieldImportService do
let(:storage_service) { instance_double(StorageService) }
let(:logger) { instance_double(ActiveSupport::Logger) }
let(:real_2021_2022_form) { Form.new("config/forms/2021_2022.json", "2021_2022") }
let(:real_setup_path) { "config/forms/setup/log_setup.json" }
let(:real_2021_2022_form) { Form.new("config/forms/2021_2022.json", "2021_2022", real_setup_path) }
let(:fixture_directory) { "spec/fixtures/softwire_imports/case_logs" }
let(:case_log_id) { "0ead17cb-1668-442d-898c-0d52879ff592" }
@ -74,6 +75,125 @@ RSpec.describe Imports::CaseLogsFieldImportService do
end
end
context "when updating letings allocation values" do
let(:field) { "lettings_allocation" }
let(:case_log) { CaseLog.find_by(old_id: case_log_id) }
before do
allow(logger).to receive(:warn)
Imports::CaseLogsImportService.new(storage_service, logger).create_logs(fixture_directory)
case_log_file.rewind
end
context "when cbl" do
let(:case_log_id) { "166fc004-392e-47a8-acb8-1c018734882b" }
context "when it was incorrectly set" do
before do
case_log.update!(cbl: 1)
end
it "updates the value" do
expect(logger).to receive(:info).with(/Case Log \d+'s cbl value has been updated/)
expect { import_service.send(:update_field, field, remote_folder) }
.to(change { case_log.reload.cbl }.from(1).to(0))
end
end
context "when it was correctly set" do
before do
case_log.update!(cbl: 0)
end
it "does not update the value" do
expect { import_service.send(:update_field, field, remote_folder) }
.not_to(change { case_log.reload.cbl })
end
end
end
context "when chr" do
let(:case_log_id) { "166fc004-392e-47a8-acb8-1c018734882b" }
context "when it was incorrectly set" do
before do
case_log.update!(chr: 1)
end
it "updates the value" do
expect(logger).to receive(:info).with(/Case Log \d+'s chr value has been updated/)
expect { import_service.send(:update_field, field, remote_folder) }
.to(change { case_log.reload.chr }.from(1).to(0))
end
end
context "when it was correctly set" do
before do
case_log.update!(chr: 0)
end
it "does not update the value" do
expect { import_service.send(:update_field, field, remote_folder) }
.not_to(change { case_log.reload.chr })
end
end
end
context "when cap" do
let(:case_log_id) { "0ead17cb-1668-442d-898c-0d52879ff592" }
context "when it was incorrectly set" do
before do
case_log.update!(cap: 1)
end
it "updates the value" do
expect(logger).to receive(:info).with(/Case Log \d+'s cap value has been updated/)
expect { import_service.send(:update_field, field, remote_folder) }
.to(change { case_log.reload.cap }.from(1).to(0))
end
end
context "when it was correctly set" do
before do
case_log.update!(cap: 0)
end
it "does not update the value" do
expect { import_service.send(:update_field, field, remote_folder) }
.not_to(change { case_log.reload.cap })
end
end
end
context "when allocation type is none of cap, chr, cbl" do
let(:case_log_id) { "893ufj2s-lq77-42m4-rty6-ej09gh585uy1" }
context "when it did not have a value set for letting_allocation_unknown" do
before do
case_log.update!(letting_allocation_unknown: nil)
end
it "updates the value" do
expect(logger).to receive(:info).with(/Case Log \d+'s letting_allocation_unknown value has been updated/)
expect { import_service.send(:update_field, field, remote_folder) }
.to(change { case_log.reload.letting_allocation_unknown }.from(nil).to(1))
end
end
context "when it had a value set for letting_allocation_unknown" do
before do
case_log.update!(letting_allocation_unknown: 1)
end
it "updates the value" do
expect { import_service.send(:update_field, field, remote_folder) }
.not_to(change { case_log.reload.letting_allocation_unknown })
end
end
end
end
context "when updating major repairs" do
let(:field) { "major_repairs" }

5
spec/services/imports/case_logs_import_service_spec.rb

@ -6,8 +6,9 @@ RSpec.describe Imports::CaseLogsImportService do
let(:storage_service) { instance_double(StorageService) }
let(:logger) { instance_double(ActiveSupport::Logger) }
let(:real_2021_2022_form) { Form.new("config/forms/2021_2022.json", "2021_2022") }
let(:real_2022_2023_form) { Form.new("config/forms/2022_2023.json", "2022_2023") }
let(:real_setup_path) { "config/forms/setup/log_setup.json" }
let(:real_2021_2022_form) { Form.new("config/forms/2021_2022.json", "2021_2022", real_setup_path) }
let(:real_2022_2023_form) { Form.new("config/forms/2022_2023.json", "2022_2023", real_setup_path) }
let(:fixture_directory) { "spec/fixtures/softwire_imports/case_logs" }
def open_file(directory, filename)

34
webpack.config.js

@ -1,18 +1,18 @@
const path = require("path")
const webpack = require("webpack")
const path = require('node:path')
const webpack = require('webpack')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts')
const CopyPlugin = require("copy-webpack-plugin");
const CopyPlugin = require('copy-webpack-plugin')
const mode = process.env.NODE_ENV === 'development' ? 'development' : 'production'
module.exports = {
mode,
devtool: "source-map",
devtool: 'source-map',
entry: {
application: [
"./app/frontend/application.js",
'./app/frontend/application.js'
]
},
module: {
@ -24,9 +24,9 @@ module.exports = {
path.resolve(__dirname, 'node_modules/@stimulus/polyfills'),
path.resolve(__dirname, 'node_modules/@rails/actioncable'),
path.resolve(__dirname, 'node_modules/chartjs'),
path.resolve(__dirname, 'app/frontend'),
path.resolve(__dirname, 'app/frontend')
],
use: ['babel-loader'],
use: ['babel-loader']
},
{
test: /\.(png|jpe?g|gif|eot|woff|woff2|ttf|svg|ico)$/i,
@ -34,9 +34,9 @@ module.exports = {
},
{
test: /\.(scss|css)/i,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
}
],
]
},
resolve: {
alias: {
@ -46,11 +46,11 @@ module.exports = {
modules: ['node_modules', 'node_modules/govuk-frontend/govuk']
},
output: {
filename: "[name].js",
filename: '[name].js',
// we must set publicPath to an empty value to override the default of
// auto which doesn't work in IE11
publicPath: '',
path: path.resolve(__dirname, "app/assets/builds"),
path: path.resolve(__dirname, 'app/assets/builds')
},
plugins: [
new RemoveEmptyScriptsPlugin(),
@ -58,12 +58,12 @@ module.exports = {
new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
new CopyPlugin({
patterns: [
{ from: "node_modules/govuk-frontend/govuk/assets/images", to: "images" },
{ from: "node_modules/govuk-frontend/govuk/assets/fonts", to: "fonts" },
{ from: "node_modules/html5shiv/dist/html5shiv.min.js", to: "vendor" },
{ from: "app/frontend/vendor/outerHTML.js", to: "vendor" },
{ from: "app/frontend/vendor/polyfill-output-value.js", to: "vendor" }
],
{ from: 'node_modules/govuk-frontend/govuk/assets/images', to: 'images' },
{ from: 'node_modules/govuk-frontend/govuk/assets/fonts', to: 'fonts' },
{ from: 'node_modules/html5shiv/dist/html5shiv.min.js', to: 'vendor' },
{ from: 'app/frontend/vendor/outerHTML.js', to: 'vendor' },
{ from: 'app/frontend/vendor/polyfill-output-value.js', to: 'vendor' }
]
})
]
}

951
yarn.lock

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save