Browse Source

Merge branch 'main' into move-privacy-notice-add-final-review

pull/469/head
Kat 3 years ago
parent
commit
255ff2c717
  1. 24
      .erb-lint.yml
  2. 1
      Gemfile
  3. 27
      Gemfile.lock
  4. 15
      app/components/document_list_component.html.erb
  5. 8
      app/components/document_list_component.rb
  6. 17
      app/components/primary_navigation_component.html.erb
  7. 8
      app/components/primary_navigation_component.rb
  8. 15
      app/components/tab_navigation_component.html.erb
  9. 14
      app/components/tab_navigation_component.rb
  10. 14
      app/controllers/organisations_controller.rb
  11. 6
      app/controllers/users_controller.rb
  12. 1
      app/frontend/controllers/accessible_autocomplete_controller.js
  13. 16
      app/frontend/controllers/filter_controller.js
  14. 26
      app/frontend/controllers/filter_layout_controller.js
  15. 4
      app/frontend/controllers/index.js
  16. 112
      app/frontend/modules/filter_toggle.js
  17. 7
      app/frontend/styles/_card.scss
  18. 66
      app/frontend/styles/_document-list.scss
  19. 42
      app/frontend/styles/_filter-layout.scss
  20. 68
      app/frontend/styles/_filter.scss
  21. 23
      app/frontend/styles/_header.scss
  22. 69
      app/frontend/styles/_primary-navigation.scss
  23. 76
      app/frontend/styles/_tab-navigation.scss
  24. 13
      app/frontend/styles/application.scss
  25. 26
      app/models/bulk_upload.rb
  26. 137
      app/models/case_log.rb
  27. 24
      app/models/form/question.rb
  28. 12
      app/models/validations/date_validations.rb
  29. 21
      app/models/validations/financial_validations.rb
  30. 10
      app/models/validations/household_validations.rb
  31. 2
      app/models/validations/property_validations.rb
  32. 6
      app/models/validations/setup_validations.rb
  33. 2
      app/models/validations/tenancy_validations.rb
  34. 392
      app/services/imports/case_logs_import_service.rb
  35. 37
      app/views/case_logs/_log_filters.erb
  36. 11
      app/views/case_logs/_log_list.html.erb
  37. 5
      app/views/case_logs/bulk_upload.html.erb
  38. 2
      app/views/case_logs/edit.html.erb
  39. 21
      app/views/case_logs/index.html.erb
  40. 3
      app/views/devise/confirmations/new.html.erb
  41. 8
      app/views/devise/passwords/edit.html.erb
  42. 5
      app/views/devise/passwords/new.html.erb
  43. 8
      app/views/devise/passwords/reset_password.html.erb
  44. 6
      app/views/devise/sessions/new.html.erb
  45. 12
      app/views/devise/shared/_links.html.erb
  46. 4
      app/views/devise/two_factor_authentication/resend.html.erb
  47. 5
      app/views/devise/two_factor_authentication/show.html.erb
  48. 3
      app/views/devise/unlocks/new.html.erb
  49. 16
      app/views/filters/_checkbox_filter.html.erb
  50. 6
      app/views/form/_check_answers_table.html.erb
  51. 7
      app/views/form/_checkbox_question.html.erb
  52. 11
      app/views/form/_date_question.html.erb
  53. 13
      app/views/form/_interruption_screen_question.html.erb
  54. 2
      app/views/form/_numeric_output_question.html.erb
  55. 18
      app/views/form/_numeric_question.html.erb
  56. 55
      app/views/form/_radio_question.html.erb
  57. 9
      app/views/form/_select_question.html.erb
  58. 11
      app/views/form/_text_question.html.erb
  59. 11
      app/views/form/_textarea_question.html.erb
  60. 14
      app/views/form/check_answers.html.erb
  61. 2
      app/views/form/guidance/_what_counts_as_income.html.erb
  62. 20
      app/views/form/page.html.erb
  63. 16
      app/views/layouts/_collection_resources.html.erb
  64. 95
      app/views/layouts/application.html.erb
  65. 2
      app/views/layouts/mailer.html.erb
  66. 2
      app/views/layouts/organisations.html.erb
  67. 17
      app/views/organisations/edit.html.erb
  68. 0
      app/views/organisations/index.html.erb
  69. 28
      app/views/organisations/show.html.erb
  70. 20
      app/views/organisations/users.html.erb
  71. 8
      app/views/pagy/_nav.html.erb
  72. 35
      app/views/start/index.html.erb
  73. 23
      app/views/users/edit.html.erb
  74. 0
      app/views/users/index.html.erb
  75. 24
      app/views/users/new.html.erb
  76. 122
      app/views/users/show.html.erb
  77. 350
      config/forms/2021_2022.json
  78. 44
      config/locales/en.yml
  79. 0
      db/migrate/20220207112310_additional_user_fields2.rb
  80. 13
      db/migrate/20220411092231_update_case_logs_fields.rb
  81. 30
      db/schema.rb
  82. 4
      docs/api/DLUHC-CORE-Data.v1.json
  83. 19
      spec/components/document_list_component_spec.rb
  84. 27
      spec/components/primary_navigation_component_spec.rb
  85. 27
      spec/components/tab_navigation_component_spec.rb
  86. 4
      spec/controllers/admin/admin_users_controller_spec.rb
  87. 4
      spec/controllers/admin/case_logs_controller_spec.rb
  88. 4
      spec/controllers/admin/organisations_controller_spec.rb
  89. 4
      spec/controllers/admin/users_controller_spec.rb
  90. 42
      spec/factories/case_log.rb
  91. 16
      spec/features/form/check_answers_page_spec.rb
  92. 6
      spec/features/form/conditional_questions_spec.rb
  93. 4
      spec/features/form/form_navigation_spec.rb
  94. 6
      spec/features/form/page_routing_spec.rb
  95. 2
      spec/features/form/saving_data_spec.rb
  96. 4
      spec/features/form/validations_spec.rb
  97. 9
      spec/features/organisation_spec.rb
  98. 8
      spec/features/reset_password.html.erb
  99. 30
      spec/fixtures/complete_case_log.json
  100. 41
      spec/fixtures/exports/case_logs.xml
  101. Some files were not shown because too many files have changed in this diff Show More

24
.erb-lint.yml

@ -0,0 +1,24 @@
---
EnableDefaultLinters: true
linters:
Rubocop:
enabled: true
rubocop_config:
inherit_from:
- .rubocop.yml
Layout/ArgumentAlignment:
Enabled: false
Layout/CommentIndentation:
Enabled: false
Layout/FirstArgumentIndentation:
Enabled: false
Layout/FirstArrayElementIndentation:
Enabled: false
Layout/FirstHashElementIndentation:
Enabled: false
Layout/InitialIndentation:
Enabled: false
Layout/TrailingEmptyLines:
Enabled: false
Lint/UselessAssignment:
Enabled: false

1
Gemfile

@ -78,6 +78,7 @@ group :development do
gem "web-console", ">= 4.1.0"
# Display performance information such as SQL time and flame graphs for each request in your browser.
# Can be configured to work on production as well see: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/README.md
gem "erb_lint", require: false
gem "rack-mini-profiler", "~> 2.0"
gem "rubocop-govuk", require: false
gem "rubocop-performance", require: false

27
Gemfile.lock

@ -69,7 +69,7 @@ GEM
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
activeadmin (2.11.1)
activeadmin (2.11.2)
arbre (~> 1.2, >= 1.2.1)
formtastic (>= 3.1, < 5.0)
formtastic_i18n (~> 0.4)
@ -105,8 +105,8 @@ GEM
ruby2_keywords (>= 0.0.2, < 1.0)
ast (2.4.2)
aws-eventstream (1.2.0)
aws-partitions (1.575.0)
aws-sdk-core (3.130.0)
aws-partitions (1.576.0)
aws-sdk-core (3.130.1)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1)
@ -121,6 +121,14 @@ GEM
aws-sigv4 (1.4.0)
aws-eventstream (~> 1, >= 1.0.2)
bcrypt (3.1.17)
better_html (1.0.16)
actionview (>= 4.0)
activesupport (>= 4.0)
ast (~> 2.0)
erubi (~> 1.4)
html_tokenizer (~> 0.0.6)
parser (>= 2.4)
smart_properties
bindex (0.8.1)
bootsnap (1.11.1)
msgpack (~> 1.2)
@ -159,6 +167,14 @@ GEM
dotenv (= 2.7.6)
railties (>= 3.2)
encryptor (3.0.0)
erb_lint (0.1.1)
activesupport
better_html (~> 1.0.7)
html_tokenizer
parser (>= 2.7.1.4)
rainbow
rubocop
smart_properties
erubi (1.10.0)
excon (0.92.2)
factory_bot (6.2.1)
@ -188,6 +204,7 @@ GEM
actionpack (>= 5.2)
activesupport (>= 5.2)
hashdiff (1.0.1)
html_tokenizer (0.0.7)
i18n (1.10.0)
concurrent-ruby (~> 1.0)
inherited_resources (1.13.1)
@ -269,7 +286,7 @@ GEM
globalid
paper_trail (>= 3.0.0)
parallel (1.22.1)
parser (3.1.1.0)
parser (3.1.2.0)
ast (~> 2.4.1)
pg (1.3.5)
postcodes_io (0.4.0)
@ -420,6 +437,7 @@ GEM
simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3)
simplecov_json_formatter (0.1.4)
smart_properties (1.17.0)
stimulus-rails (1.0.4)
railties (>= 6.0.0)
strscan (3.0.1)
@ -469,6 +487,7 @@ DEPENDENCIES
chartkick
devise!
dotenv-rails
erb_lint
factory_bot_rails
govuk-components
govuk_design_system_formbuilder

15
app/components/document_list_component.html.erb

@ -0,0 +1,15 @@
<ul class="app-document-list">
<% items.each do |item| %>
<li class="app-document-list__item">
<h3 class="app-document-list__item-title">
<%= govuk_link_to item[:name], item[:href] %>
</h3>
<% if item[:description] %>
<p class="app-document-list__item-description"><%= item[:description] %></p>
<% end %>
<% if item[:metadata] %>
<p class="app-document-list__item-metadata"><%= item[:metadata] %></p>
<% end %>
</li>
<% end %>
</ul>

8
app/components/document_list_component.rb

@ -0,0 +1,8 @@
class DocumentListComponent < ViewComponent::Base
attr_reader :items
def initialize(items:)
@items = items
super
end
end

17
app/components/primary_navigation_component.html.erb

@ -0,0 +1,17 @@
<nav class="app-primary-navigation" aria-label="primary">
<div class="govuk-width-container">
<ul class="app-primary-navigation__list">
<% items.each do |item| %>
<% if item.fetch(:current, false) || current_page?(item.fetch(:url)) || current_page?("#{item.fetch(:url)}/details") %>
<li class="app-primary-navigation__item app-primary-navigation__item--current">
<%= govuk_link_to item[:name], item[:url], class: "app-primary-navigation__link", aria: { current: "page" } %>
</li>
<% else %>
<li class="app-primary-navigation__item">
<%= govuk_link_to item[:name], item[:url], class: "app-primary-navigation__link" %>
</li>
<% end %>
<% end %>
</ul>
</div>
</nav>

8
app/components/primary_navigation_component.rb

@ -0,0 +1,8 @@
class PrimaryNavigationComponent < ViewComponent::Base
attr_reader :items
def initialize(items:)
@items = items
super
end
end

15
app/components/tab_navigation_component.html.erb

@ -1,15 +0,0 @@
<nav class="app-tab-navigation" aria-label="sub menu">
<ul class="app-tab-navigation__list">
<% items.each do |item| %>
<% if item.fetch(:current, false) || current_page?(strip_query(item.fetch(:url))) %>
<li class="app-tab-navigation__item app-tab-navigation__item--current">
<%= govuk_link_to item[:name], item[:url], class: 'app-tab-navigation__link', aria: { current: 'page' } %>
</li>
<% else %>
<li class="app-tab-navigation__item">
<%= govuk_link_to item[:name], item[:url], class: 'app-tab-navigation__link' %>
</li>
<% end %>
<% end %>
</ul>
</nav>

14
app/components/tab_navigation_component.rb

@ -1,14 +0,0 @@
class TabNavigationComponent < ViewComponent::Base
attr_reader :items
def initialize(items:)
@items = items
super
end
def strip_query(url)
url = Addressable::URI.parse(url)
url.query_values = nil
url.to_s
end
end

14
app/controllers/organisations_controller.rb

@ -1,8 +1,14 @@
class OrganisationsController < ApplicationController
before_action :authenticate_user!
before_action :find_resource
before_action :authenticate_user!, except: [:index]
before_action :find_resource, except: [:index]
before_action :authenticate_scope!
def index
unless current_user.support?
redirect_to user_path(current_user)
end
end
def show
redirect_to details_organisation_path(@organisation)
end
@ -41,10 +47,12 @@ private
end
def authenticate_scope!
render_not_found if current_user.organisation != @organisation
render_not_found if current_user.organisation != @organisation && !current_user.support?
end
def find_resource
return if current_user.support?
@organisation = Organisation.find(params[:id])
end
end

6
app/controllers/users_controller.rb

@ -5,6 +5,12 @@ class UsersController < ApplicationController
before_action :find_resource, except: %i[new create]
before_action :authenticate_scope!, except: %i[new]
def index
unless current_user.support?
redirect_to user_path(@user)
end
end
def update
if @user.update(user_params)
if @user == current_user

1
app/frontend/controllers/accessible_autocomplete_controller.js

@ -3,7 +3,6 @@ import accessibleAutocomplete from "accessible-autocomplete"
import 'accessible-autocomplete/dist/accessible-autocomplete.min.css'
export default class extends Controller {
connect() {
accessibleAutocomplete.enhanceSelectElement({
defaultValue: '',

16
app/frontend/controllers/filter_controller.js

@ -1,16 +0,0 @@
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
toggleFilter() {
let filter_panel = document.getElementById("filter-panel");
let toggle_filter_button = document.getElementById("toggle-filter-button");
if (filter_panel.style.display === "none" || !filter_panel.style.display) {
filter_panel.style.display = "block";
toggle_filter_button.innerText = "Hide filters";
} else {
filter_panel.style.display = "none";
toggle_filter_button.innerText = "Show filters";
}
}
}

26
app/frontend/controllers/filter_layout_controller.js

@ -0,0 +1,26 @@
import { Controller } from "@hotwired/stimulus";
import { FilterToggle } from "../modules/filter_toggle.js"
export default class extends Controller {
connect() {
const filterToggle = new FilterToggle({
bigModeMediaQuery: '(min-width: 48.0625em)',
closeButton: {
container: this.element.querySelector('.app-filter__header'),
text: 'Close'
},
filter: {
container: this.element.querySelector('.app-filter-layout__filter')
},
startHidden: false,
toggleButton: {
container: this.element.querySelector('.app-filter-toggle'),
showText: 'Show filters',
hideText: 'Hide filters',
classes: 'govuk-button--secondary'
}
})
filterToggle.init()
}
}

4
app/frontend/controllers/index.js

@ -15,5 +15,5 @@ application.register("govukfrontend", GovukfrontendController)
import NumericQuestionController from "./numeric_question_controller.js"
application.register("numeric-question", NumericQuestionController)
import FilterController from "./filter_controller.js"
application.register("filter", FilterController)
import FilterLayoutController from "./filter_layout_controller.js"
application.register("filter-layout", FilterLayoutController)

112
app/frontend/modules/filter_toggle.js

@ -0,0 +1,112 @@
export class FilterToggle {
constructor (options) {
this.options = options
this.container = this.options.toggleButton.container
}
setupResponsiveChecks () {
this.mq = window.matchMedia(this.options.bigModeMediaQuery)
this.mq.addListener(this.checkMode.bind(this))
this.checkMode(this.mq)
}
checkMode (mq) {
if (mq.matches) {
this.enableBigMode()
} else {
this.enableSmallMode()
}
}
enableBigMode () {
this.showMenu()
this.removeMenuButton()
this.removeCloseButton()
}
enableSmallMode () {
this.options.filter.container.setAttribute("tabindex", "-1")
this.hideMenu()
this.addMenuButton()
this.addCloseButton()
}
addCloseButton () {
if (this.options.closeButton) {
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))
this.options.closeButton.container.append(this.closeButton)
}
}
onCloseClick () {
this.hideMenu()
this.menuButton.focus()
}
removeCloseButton () {
if (this.closeButton) {
this.closeButton.remove()
this.closeButton = null
}
}
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.innerText = this.options.toggleButton.showText
this.menuButton.type = "button"
this.menuButton.addEventListener("click", this.onMenuButtonClick.bind(this))
this.options.toggleButton.container.prepend(this.menuButton)
}
removeMenuButton () {
if (this.menuButton) {
this.menuButton.remove()
this.menuButton = null
}
}
hideMenu () {
if (this.menuButton) {
this.menuButton.setAttribute("aria-expanded", "false")
this.menuButton.innerText = this.options.toggleButton.showText
}
this.options.filter.container.setAttribute("hidden", true)
}
showMenu () {
if (this.menuButton) {
this.menuButton.setAttribute("aria-expanded", "true")
this.menuButton.innerText = this.options.toggleButton.hideText
}
this.options.filter.container.removeAttribute("hidden")
}
onMenuButtonClick () {
this.toggle()
}
toggle () {
if (this.options.filter.container.hidden) {
this.showMenu()
this.options.filter.container.focus()
} else {
this.hideMenu()
}
}
init () {
this.setupResponsiveChecks()
if (this.options.startHidden) {
this.hideMenu()
}
}
}

7
app/frontend/styles/_card.scss

@ -0,0 +1,7 @@
.app-card {
@include govuk-responsive-padding(4);
@include govuk-font($size: 19);
background-color: govuk-colour("light-grey");
display: block;
position: relative;
}

66
app/frontend/styles/_document-list.scss

@ -0,0 +1,66 @@
.app-document-list {
list-style: none;
padding: 0;
}
.app-document-list__item {
margin-bottom: govuk-spacing(4);
&:last-child {
border-bottom: 0;
margin-bottom: 0;
padding-bottom: 0;
}
}
.app-document-list__item-title {
@include govuk-font($size: 16, $weight: "bold");
margin: 0 0 govuk-spacing(1);
}
.app-document-list__item-metadata {
padding: 0;
margin: 0;
}
.app-document-list__item-description {
@include govuk-font($size: 16);
margin: govuk-spacing(1) 0;
}
.app-document-list__item-metadata {
@include govuk-font($size: 16);
color: $govuk-secondary-text-colour;
margin: 0;
}
.app-document-list {
list-style: none;
padding: 0;
}
.app-document-list__item {
margin-bottom: govuk-spacing(4);
&:last-child {
border-bottom: 0;
margin-bottom: 0;
padding-bottom: 0;
}
}
.app-document-list__item-title {
@include govuk-font($size: 16, $weight: "bold");
margin: 0 0 govuk-spacing(1);
}
.app-document-list__item-description {
@include govuk-font($size: 16);
margin: govuk-spacing(1) 0;
}
.app-document-list__item-metadata {
@include govuk-font($size: 16);
color: $govuk-secondary-text-colour;
padding: 0;
margin: 0;
}

42
app/frontend/styles/_filter-layout.scss

@ -0,0 +1,42 @@
.app-filter-layout {
@include govuk-clearfix;
}
.app-filter-layout__filter {
@include govuk-media-query(desktop) {
float: left;
min-width: govuk-grid-width("one-quarter");
}
}
.js-enabled .app-filter-layout__filter {
outline: 0 none;
@include govuk-media-query($until: desktop) {
background-color: govuk-colour("light-grey");
bottom: govuk-spacing(1);
border: 1px solid govuk-colour("mid-grey");
max-width: 310px;
min-width: 260px;
width: 100%;
overflow-y: scroll;
position: fixed;
right: govuk-spacing(1);
top: govuk-spacing(1);
z-index: 100;
&:focus {
outline: $govuk-focus-width solid $govuk-focus-colour;
}
}
}
.app-filter-layout__content {
@include govuk-media-query(desktop) {
float: right;
max-width: calc(
#{govuk-grid-width("three-quarters")} - #{govuk-spacing(6)}
);
width: 100%;
}
}

68
app/frontend/styles/_filter.scss

@ -61,63 +61,41 @@
vertical-align: middle;
width: 14px;
}
@include govuk-media-query(desktop) {
display: none;
}
}
.app-filter-layout {
@include govuk-clearfix;
}
.app-filter-toggle__button {
min-width: 128px;
@include govuk-media-query(desktop) {
display: none !important;
}
}
.app-filter-layout__filter {
@include govuk-media-query(desktop) {
float: left;
min-width: govuk-grid-width("one-quarter");
.app-filter__group {
border-bottom: 1px solid $govuk-border-colour;
margin-bottom: govuk-spacing(3);
padding-bottom: govuk-spacing(2);
&:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
display: none;
@include govuk-media-query(desktop) {
display: block;
.govuk-fieldset__legend {
margin-bottom: govuk-spacing(1);
}
}
.js-enabled .app-filter-layout__filter {
outline: 0 none;
@include govuk-media-query($until: desktop) {
background-color: govuk-colour("light-grey");
bottom: govuk-spacing(1);
border: 1px solid govuk-colour("mid-grey");
max-width: 310px;
min-width: 260px;
width: 100%;
overflow-y: scroll;
position: fixed;
right: govuk-spacing(1);
top: govuk-spacing(1);
z-index: 100;
&:focus {
outline: $govuk-focus-width solid $govuk-focus-colour;
.govuk-form-group {
margin-bottom: 0;
&:last-child {
margin-bottom: 0;
}
}
}
.app-filter-layout__content {
@include govuk-media-query(desktop) {
float: right;
max-width: calc(
#{govuk-grid-width("three-quarters")} - #{govuk-spacing(6)}
);
width: 100%;
.govuk-checkboxes__label,
.govuk-radios__label {
@include govuk-font(16);
&::before {
background-color: govuk-colour("white");
}
}
}

23
app/frontend/styles/_header.scss

@ -0,0 +1,23 @@
.app-header {
border-bottom: govuk-spacing(2) solid $govuk-brand-colour;
.govuk-header__logo {
@include govuk-media-query($from: desktop) {
width: 60%;
}
@include govuk-media-query($from: 860px) {
width: 75%;
}
}
.govuk-header__content {
@include govuk-media-query($from: desktop) {
width: 40%;
}
@include govuk-media-query($from: 860px) {
width: 25%;
}
}
}

69
app/frontend/styles/_primary-navigation.scss

@ -0,0 +1,69 @@
.app-primary-navigation {
@include govuk-font(19, $weight: bold);
background-color: govuk-colour("light-grey");
border-bottom: 1px solid $govuk-border-colour;
}
.govuk-phase-banner + .app-primary-navigation {
margin-top: -1px;
}
.app-primary-navigation__list {
@include govuk-clearfix;
left: govuk-spacing(-3);
list-style: none;
margin: 0;
padding: 0;
position: relative;
right: govuk-spacing(-3);
width: calc(100% + #{govuk-spacing(6)});
}
.app-primary-navigation__item {
box-sizing: border-box;
display: block;
float: left;
line-height: 50px;
height: 50px;
padding: 0 govuk-spacing(3);
position: relative;
}
.app-primary-navigation__item--current {
border-bottom: $govuk-border-width-narrow solid $govuk-link-colour;
&:hover {
border-bottom-color: $govuk-link-hover-colour;
}
&:active {
border-bottom-color: $govuk-link-active-colour;
}
}
.app-primary-navigation__item--align-right {
@include govuk-media-query($from: tablet) {
float: right;
}
}
.app-primary-navigation__link {
@include govuk-link-common;
@include govuk-link-style-no-visited-state;
@include govuk-link-style-no-underline;
@include govuk-typography-weight-bold;
// Extend the touch area of the link to the list
&:after {
bottom: 0;
content: "";
left: 0;
position: absolute;
right: 0;
top: 0;
}
}
.app-primary-navigation__item--current .app-primary-navigation__link:hover {
text-decoration: none;
}

76
app/frontend/styles/_tab-navigation.scss

@ -1,76 +0,0 @@
.app-tab-navigation {
@include govuk-font(19, $weight: bold);
@include govuk-responsive-margin(6, "bottom");
}
.app-tab-navigation__list {
@include govuk-clearfix;
left: govuk-spacing(-3);
list-style: none;
margin: 0;
padding: 0;
position: relative;
right: govuk-spacing(-3);
width: calc(100% + #{govuk-spacing(6)});
@include govuk-media-query($from: tablet) {
box-shadow: inset 0 -1px 0 $govuk-border-colour;
}
}
.app-tab-navigation__item {
box-sizing: border-box;
display: block;
line-height: 40px;
height: 40px;
padding: 0 govuk-spacing(3);
@include govuk-media-query($from: tablet) {
box-shadow: none;
display: block;
float: left;
line-height: 50px;
height: 50px;
padding: 0 govuk-spacing(3);
position: relative;
}
}
.app-tab-navigation__item--current {
@include govuk-media-query($until: tablet) {
border-left: 4px solid $govuk-link-colour;
padding-left: 11px;
}
@include govuk-media-query($from: tablet) {
border-bottom: 4px solid $govuk-link-colour;
padding-left: govuk-spacing(3);
}
}
.app-tab-navigation__link {
@include govuk-link-common;
@include govuk-link-style-no-visited-state;
@include govuk-link-style-no-underline;
@include govuk-typography-weight-bold;
&:not(:focus):hover {
color: $govuk-link-colour;
}
// Extend the touch area of the link to the list
&:after {
bottom: 0;
content: "";
left: 0;
position: absolute;
right: 0;
top: 0;
}
}
.app-tab-navigation__item--current .app-tab-navigation__link {
&:hover {
text-decoration: none;
}
}

13
app/frontend/styles/application.scss

@ -1,31 +1,36 @@
@function frontend-font-url($filename) {
@return url("~assets/fonts/"+$filename);
@return url("~assets/fonts/" + $filename);
}
@function frontend-image-url($filename) {
@return url("~assets/images/"+$filename);
@return url("~assets/images/" + $filename);
}
$govuk-font-url-function: frontend-font-url;
$govuk-image-url-function: frontend-image-url;
$govuk-global-styles: true;
$govuk-new-link-styles: true;
@import "govuk-frontend-styles";
@import "govuk-prototype-styles";
@import "accessible-autocomplete";
@import "button";
@import "card";
@import "document-list";
@import "figure";
@import "filter";
@import "filter-layout";
@import "header";
@import "input";
@import "related-navigation";
@import "section-skip-link";
@import "tab-navigation";
@import "table-group";
@import "task-list";
@import "template";
@import "pagination";
@import "panel";
@import "filter";
@import "primary-navigation";
// App utilities
.app-\!-colour-muted {

26
app/models/bulk_upload.rb

@ -57,7 +57,6 @@ class BulkUpload
def map_row(row)
{
lettype: row[1],
landlord: row[2],
# reg_num_la_core_code: row[3],
# managementgroup: row[4],
# schemecode: row[5],
@ -67,8 +66,7 @@ class BulkUpload
tenancy: row[9],
tenancyother: row[10],
# tenancyduration: row[11],
other_hhmemb: other_hhmemb(row),
hhmemb: other_hhmemb(row) + 1,
hhmemb: hhmemb(row),
age1: row[12],
age2: row[13],
age3: row[14],
@ -111,7 +109,7 @@ class BulkUpload
earnings: row[50],
# increfused: row[51],
reason: row[52],
other_reason_for_leaving_last_settled_home: row[53],
reasonother: row[53],
underoccupation_benefitcap: row[54],
housingneeds_a: row[55],
housingneeds_b: row[56],
@ -121,11 +119,9 @@ class BulkUpload
housingneeds_h: row[60],
prevten: row[61],
prevloc: row[62],
# ppostc1: row[63],
# ppostc2: row[64],
# prevpco_unknown: row[65],
layear: row[66],
lawaitlist: row[67],
waityear: row[67],
homeless: row[68],
reasonpref: row[69],
rp_homeless: row[70],
@ -147,15 +143,9 @@ class BulkUpload
# no_rent_or_charge: row[86],
hbrentshortfall: row[87],
tshortfall: row[88],
property_void_date: row[89].to_s + row[90].to_s + row[91].to_s,
# property_void_date_day: row[89],
# property_void_date_month: row[90],
# property_void_date_year: row[91],
voiddate: row[89].to_s + row[90].to_s + row[91].to_s,
majorrepairs: row[92].present? ? "1" : nil,
mrcdate: row[92].to_s + row[93].to_s + row[94].to_s,
mrcday: row[92],
mrcmonth: row[93],
mrcyear: row[94],
# supported_scheme: row[95],
startdate: date_time(row[98], row[97], row[96]),
# startdate_day: row[96],
@ -170,8 +160,6 @@ class BulkUpload
property_relet: row[105],
rsnvac: row[106],
la: row[107],
# postcode: row[108],
# postcod2: row[109],
# row[110] removed
# row[111] is owning organisation used above
# username: row[112],
@ -193,7 +181,7 @@ class BulkUpload
illness_type_10: row[128],
# london_affordable: row[129],
rent_type: row[130],
intermediate_rent_product_name: row[131],
irproduct_other: row[131],
# data_protection: row[132],
sale_or_letting: "letting",
declaration: 1,
@ -206,7 +194,7 @@ class BulkUpload
Time.zone.local("20#{year}", month.to_s, day.to_s)
end
def other_hhmemb(row)
[13, 14, 15, 16, 17, 18, 19].count { |idx| row[idx].present? }
def hhmemb(row)
[14, 15, 16, 17, 18, 19, 20].count { |idx| row[idx].present? }
end
end

137
app/models/case_log.rb

@ -34,7 +34,7 @@ class CaseLog < ApplicationRecord
belongs_to :managing_organisation, class_name: "Organisation"
scope :for_organisation, ->(org) { where(owning_organisation: org).or(where(managing_organisation: org)) }
scope :filter_by_status, ->(status, _user = nil) { where status: status }
scope :filter_by_status, ->(status, _user = nil) { where status: }
scope :filter_by_years, lambda { |years, _user = nil|
first_year = years.shift
query = filter_by_year(first_year)
@ -100,7 +100,7 @@ class CaseLog < ApplicationRecord
elsif net_income_is_monthly?
((earnings * 12) / 52.0).round(0)
elsif net_income_is_yearly?
(earnings / 12.0).round(0)
(earnings / 52.0).round(0)
end
end
@ -122,19 +122,23 @@ class CaseLog < ApplicationRecord
end
def net_income_refused?
# 2: Tenant prefers not to say
net_income_known == 2
end
def net_income_is_weekly?
!!(incfreq && incfreq.zero?)
# 1: Weekly
!!(incfreq && incfreq == 1)
end
def net_income_is_monthly?
incfreq == 1
# 2: Monthly
incfreq == 2
end
def net_income_is_yearly?
incfreq == 2
# 3: Yearly
incfreq == 3
end
def net_income_soft_validation_triggered?
@ -142,138 +146,181 @@ class CaseLog < ApplicationRecord
end
def given_reasonable_preference?
# 1: Yes
reasonpref == 1
end
def is_renewal?
# 1: Yes
renewal == 1
end
def is_general_needs?
# 1: General Needs
needstype == 1
end
def is_supported_housing?
!!(needstype && needstype.zero?)
# 2: Supported Housing
needstype == 2
end
def has_hbrentshortfall?
!!(hbrentshortfall && hbrentshortfall.zero?)
# 0: Yes
!!hbrentshortfall&.zero?
end
def postcode_known?
# 1: Yes
postcode_known == 1
end
def previous_postcode_known?
# 1: Yes
previous_postcode_known == 1
end
def la_known?
# 1: Yes
la_known == 1
end
def previous_la_known?
# 1: Yes
previous_la_known == 1
end
def is_secure_tenancy?
tenancy == 3
# 1: Secure (including flexible)
tenancy == 1
end
def is_assured_shorthold_tenancy?
tenancy == 1
# 4: Assured Shorthold
tenancy == 4
end
def is_internal_transfer?
# 1: Internal Transfer
referral == 1
end
def is_relet_to_temp_tenant?
rsnvac == 2
# 9: Re-let to tenant who occupied same property as temporary accommodation
rsnvac == 9
end
def is_bedsit?
unittype_gn == 1
# 2: Bedsit
unittype_gn == 2
end
def is_shared_housing?
[4, 5, 6].include?(unittype_gn)
# 4: Shared flat or maisonette
# 9: Shared house
# 10: Shared bungalow
[4, 9, 10].include?(unittype_gn)
end
def has_first_let_vacancy_reason?
# 15: First let of new-build property
# 16: First let of conversion, rehabilitation or acquired property
# 17: First let of leased property
[15, 16, 17].include?(rsnvac)
end
def previous_tenancy_was_temporary?
![4, 5, 16, 21, 22].include?(prevten)
# 4: Tied housing or renting with job
# 6: Supported housing
# 8: Sheltered accomodation
# 24: Housed by National Asylum Support Service (prev Home Office)
# 25: Other
![4, 6, 8, 24, 25].include?(prevten)
end
def armed_forces_regular?
!!(armedforces && armedforces.zero?)
# 1: Yes – the person is a current or former regular
!!(armedforces && armedforces == 1)
end
def armed_forces_no?
armedforces == 3
# 2: No
armedforces == 2
end
def armed_forces_refused?
armedforces == 4
# 3: Person prefers not to say / Refused
armedforces == 3
end
def has_pregnancy?
!!(preg_occ && preg_occ.zero?)
# 1: Yes
!!(preg_occ && preg_occ == 1)
end
def pregnancy_refused?
preg_occ == 2
# 3: Tenant prefers not to say / Refused
preg_occ == 3
end
def is_assessed_homeless?
# 11: Assessed as homeless (or threatened with homelessness within 56 days) by a local authority and owed a homelessness duty
homeless == 11
end
def is_other_homeless?
# 7: Other homeless – not found statutorily homeless but considered homeless by landlord
homeless == 7
end
def is_not_homeless?
# 1: No
homeless == 1
end
def is_london_rent?
# 2: London Affordable Rent
# 4: London Living Rent
rent_type == 2 || rent_type == 4
end
def previous_tenancy_was_foster_care?
# 13: Children's home or foster care
prevten == 13
end
def previous_tenancy_was_refuge?
# 21: Refuge
prevten == 21
end
def is_reason_permanently_decanted?
# 1: Permanently decanted from another property owned by this landlord
reason == 1
end
def receives_housing_benefit_only?
# 1: Housing benefit
hb == 1
end
def receives_housing_benefit_and_universal_credit?
# 8: Housing benefit and Universal Credit (without housing element)
hb == 8
end
def receives_uc_with_housing_element_excl_housing_benefit?
# 6: Universal Credit with housing element (excluding housing benefit)
hb == 6
end
def receives_no_benefits?
# 9: None
hb == 9
end
def receives_universal_credit_but_no_housing_benefit?
# 7: Universal Credit (without housing element)
hb == 7
end
@ -283,22 +330,18 @@ class CaseLog < ApplicationRecord
end
def benefits_unknown?
# 3: Don’t know
hb == 3
end
def this_landlord?
landlord == 1
end
def other_landlord?
landlord == 2
end
def local_housing_referral?
# 3: PRP lettings only - Nominated by local housing authority
referral == 3
end
def is_prevten_la_general_needs?
# 30: Fixed term Local Authority General Needs tenancy
# 31: Lifetime Local Authority General Needs tenancy
[30, 31].any?(prevten)
end
@ -379,30 +422,10 @@ private
end
def set_derived_fields!
if ppostcode_full.present?
self.ppostc1 = UKPostcode.parse(ppostcode_full).outcode
self.ppostc2 = UKPostcode.parse(ppostcode_full).incode
end
if mrcdate.present?
self.mrcday = mrcdate.day
self.mrcmonth = mrcdate.month
self.mrcyear = mrcdate.year
end
if startdate.present?
self.day = startdate.day
self.month = startdate.month
self.year = startdate.year
end
if property_void_date.present?
self.vday = property_void_date.day
self.vmonth = property_void_date.month
self.vyear = property_void_date.year
end
if rsnvac.present?
self.newprop = has_first_let_vacancy_reason? ? 1 : 2
end
self.incref = 1 if net_income_refused?
self.other_hhmemb = hhmemb - 1 if hhmemb.present?
self.renttype = RENT_TYPE_MAPPING[rent_type]
self.lettype = get_lettype
self.totchild = get_totchild
@ -431,14 +454,14 @@ private
weekly_value(tshortfall)
end
self.nocharge = household_charge&.zero? ? 1 : 0
self.underoccupation_benefitcap = 3 if renewal == 1 && year == 2021
self.housingneeds = get_housingneeds
if is_renewal?
self.underoccupation_benefitcap = 2 if year == 2021
self.underoccupation_benefitcap = 2 if collection_start_year == 2021
self.homeless = 2
self.referral = 0
self.layear = 1
if is_general_needs?
# fixed term
self.prevten = 32 if managing_organisation.provider_type == "PRP"
self.prevten = 30 if managing_organisation.provider_type == "LA"
end
@ -450,47 +473,41 @@ private
self["ecstat#{idx}"] = nil
end
end
self.landlord = 1 if owning_organisation.provider_type == "LA"
self.landlord = 2 if owning_organisation.provider_type == "PRP"
end
def process_postcode_changes!
self.postcode_full = postcode_full.present? ? postcode_full.upcase.gsub(/\s+/, "") : postcode_full
process_postcode(postcode_full, "postcode_known", "is_la_inferred", "la", "postcode", "postcod2")
process_postcode(postcode_full, "postcode_known", "is_la_inferred", "la")
end
def process_previous_postcode_changes!
self.ppostcode_full = ppostcode_full.present? ? ppostcode_full.upcase.gsub(/\s+/, "") : ppostcode_full
process_postcode(ppostcode_full, "previous_postcode_known", "is_previous_la_inferred", "prevloc", "ppostc1", "ppostc2")
process_postcode(ppostcode_full, "previous_postcode_known", "is_previous_la_inferred", "prevloc")
end
def process_postcode(postcode, postcode_known_key, la_inferred_key, la_key, outcode_key, incode_key)
def process_postcode(postcode, postcode_known_key, la_inferred_key, la_key)
return if postcode.blank?
self[postcode_known_key] = 1
inferred_la = get_inferred_la(postcode)
self[la_inferred_key] = inferred_la.present?
self[la_key] = inferred_la if inferred_la.present?
self[outcode_key] = UKPostcode.parse(postcode).outcode
self[incode_key] = UKPostcode.parse(postcode).incode
end
def reset_location_fields!
reset_location(is_la_inferred, "la", "is_la_inferred", "postcode_full", "postcode", "postcod2", la_known)
reset_location(is_la_inferred, "la", "is_la_inferred", "postcode_full", la_known)
end
def reset_previous_location_fields!
reset_location(is_previous_la_inferred, "prevloc", "is_previous_la_inferred", "ppostcode_full", "ppostc1", "ppostc2", previous_la_known)
reset_location(is_previous_la_inferred, "prevloc", "is_previous_la_inferred", "ppostcode_full", previous_la_known)
end
def reset_location(is_inferred, la_key, is_inferred_key, postcode_key, incode_key, outcode_key, is_la_known)
def reset_location(is_inferred, la_key, is_inferred_key, postcode_key, is_la_known)
if is_inferred || is_la_known != 1
self[la_key] = nil
end
self[is_inferred_key] = false
self[postcode_key] = nil
self[incode_key] = nil
self[outcode_key] = nil
end
def get_totelder

24
app/models/form/question.rb

@ -226,9 +226,9 @@ private
end
ANSWER_SUFFIX_LABELS = {
0 => " every week",
1 => " every month",
2 => " every year",
1 => " every week",
2 => " every month",
3 => " every year",
}.freeze
RADIO_YES_VALUE = {
@ -242,11 +242,11 @@ private
majorrepairs: [1],
startertenancy: [0],
letting_in_sheltered_accommodation: [0, 1],
armedforces: [0, 1, 2],
armedforces: [1, 4, 5],
leftreg: [0],
reservist: [0],
preg_occ: [0],
illness: [0],
preg_occ: [1],
illness: [1],
underoccupation_benefitcap: [4, 5, 6],
reasonpref: [1],
net_income_known: [0],
@ -267,11 +267,11 @@ private
majorrepairs: [0],
startertenancy: [1],
letting_in_sheltered_accommodation: [2],
armedforces: [3],
armedforces: [2],
leftreg: [1],
reservist: [1],
preg_occ: [1],
illness: [1],
preg_occ: [2],
illness: [2],
underoccupation_benefitcap: [2],
reasonpref: [2],
net_income_known: [1],
@ -291,6 +291,7 @@ private
hb: [5],
benefits: [3],
unitletas: [3],
illness: [3],
}.freeze
RADIO_REFUSED_VALUE = {
@ -318,11 +319,10 @@ private
ecstat7: [10],
ecstat8: [10],
letting_in_sheltered_accommodation: [3],
armedforces: [4],
armedforces: [3],
leftreg: [3],
reservist: [2],
preg_occ: [2],
illness: [2],
preg_occ: [3],
hb: [6],
}.freeze
end

12
app/models/validations/date_validations.rb

@ -15,16 +15,16 @@ module Validations::DateValidations
end
def validate_property_void_date(record)
if record["property_void_date"].present? && record["startdate"].present? && record["startdate"].to_date - record["property_void_date"].to_date > 3650
record.errors.add :property_void_date, I18n.t("validations.property.void_date.ten_years_before_tenancy_start")
if record["voiddate"].present? && record["startdate"].present? && record["startdate"].to_date - record["voiddate"].to_date > 3650
record.errors.add :voiddate, I18n.t("validations.property.void_date.ten_years_before_tenancy_start")
end
if record["property_void_date"].present? && record["startdate"].present? && record["startdate"].to_date < record["property_void_date"].to_date
record.errors.add :property_void_date, I18n.t("validations.property.void_date.before_tenancy_start")
if record["voiddate"].present? && record["startdate"].present? && record["startdate"].to_date < record["voiddate"].to_date
record.errors.add :voiddate, I18n.t("validations.property.void_date.before_tenancy_start")
end
if record["property_void_date"].present? && record["mrcdate"].present? && record["mrcdate"].to_date < record["property_void_date"].to_date
record.errors.add :property_void_date, I18n.t("validations.property.void_date.after_mrcdate")
if record["voiddate"].present? && record["mrcdate"].present? && record["mrcdate"].to_date < record["voiddate"].to_date
record.errors.add :voiddate, I18n.t("validations.property.void_date.after_mrcdate")
end
end

21
app/models/validations/financial_validations.rb

@ -84,46 +84,47 @@ private
CHARGE_MAXIMUMS = {
scharge: {
this_landlord: {
private_registered_provider: {
general_needs: 55,
supported_housing: 280,
},
other_landlord: {
local_authority: {
general_needs: 45,
supported_housing: 165,
},
},
pscharge: {
this_landlord: {
private_registered_provider: {
general_needs: 30,
supported_housing: 200,
},
other_landlord: {
local_authority: {
general_needs: 35,
supported_housing: 75,
},
},
supcharg: {
this_landlord: {
private_registered_provider: {
general_needs: 40,
supported_housing: 465,
},
other_landlord: {
local_authority: {
general_needs: 60,
supported_housing: 120,
},
},
}.freeze
LANDLORD_VALUES = { 1 => :this_landlord, 2 => :other_landlord }.freeze
NEEDSTYPE_VALUES = { 0 => :supported_housing, 1 => :general_needs }.freeze
PROVIDER_TYPE = { 1 => :local_authority, 2 => :private_registered_provider }.freeze
NEEDSTYPE_VALUES = { 2 => :supported_housing, 1 => :general_needs }.freeze
def validate_charges(record)
provider_type = record.owning_organisation.provider_type_before_type_cast
%i[scharge pscharge supcharg].each do |charge|
maximum = CHARGE_MAXIMUMS.dig(charge, LANDLORD_VALUES[record.landlord], NEEDSTYPE_VALUES[record.needstype])
maximum = CHARGE_MAXIMUMS.dig(charge, PROVIDER_TYPE[provider_type], NEEDSTYPE_VALUES[record.needstype])
if maximum.present? && record[:period].present? && record[charge].present? && !weekly_value_in_range(record, charge, 0.0, maximum)
record.errors.add charge, I18n.t("validations.financial.rent.#{charge}.#{LANDLORD_VALUES[record.landlord]}.#{NEEDSTYPE_VALUES[record.needstype]}")
record.errors.add charge, I18n.t("validations.financial.rent.#{charge}.#{PROVIDER_TYPE[provider_type]}.#{NEEDSTYPE_VALUES[record.needstype]}")
end
end
end

10
app/models/validations/household_validations.rb

@ -18,7 +18,7 @@ module Validations::HouseholdValidations
record.errors.add :underoccupation_benefitcap, I18n.t("validations.household.underoccupation_benefitcap.dont_know_required")
record.errors.add :reason, I18n.t("validations.household.underoccupation_benefitcap.dont_know_required")
end
validate_other_field(record, 31, :reason, :other_reason_for_leaving_last_settled_home)
validate_other_field(record, 31, :reason, :reasonother)
if record.is_reason_permanently_decanted? && record.referral.present? && !record.is_internal_transfer?
record.errors.add :referral, I18n.t("validations.household.referral.reason_permanently_decanted")
@ -56,7 +56,7 @@ module Validations::HouseholdValidations
end
def validate_accessibility_requirements(record)
all_options = [record.housingneeds_a, record.housingneeds_b, record.housingneeds_c, record.housingneeds_f, record.housingneeds_g, record.housingneeds_h, record.accessibility_requirements_prefer_not_to_say]
all_options = [record.housingneeds_a, record.housingneeds_b, record.housingneeds_c, record.housingneeds_f, record.housingneeds_g, record.housingneeds_h]
if all_options.count(1) > 1
mobility_accessibility_options = [record.housingneeds_a, record.housingneeds_b, record.housingneeds_c]
unless all_options.count(1) == 2 && record.housingneeds_f == 1 && mobility_accessibility_options.any? { |x| x == 1 }
@ -105,12 +105,12 @@ module Validations::HouseholdValidations
record.errors.add :homeless, I18n.t("validations.household.homeless.other.internal_transfer")
end
if record.is_internal_transfer? && record.this_landlord? && record.is_prevten_la_general_needs?
if record.is_internal_transfer? && record.owning_organisation.provider_type == "PRP" && record.is_prevten_la_general_needs?
record.errors.add :referral, I18n.t("validations.household.referral.la_general_needs.internal_transfer")
record.errors.add :prevten, I18n.t("validations.household.prevten.la_general_needs.internal_transfer")
end
if record.other_landlord? && record.local_housing_referral?
if record.owning_organisation.provider_type == "LA" && record.local_housing_referral?
record.errors.add :referral, I18n.t("validations.household.referral.prp.local_housing_referral")
end
end
@ -124,7 +124,7 @@ module Validations::HouseholdValidations
private
def household_no_illness?(record)
record.illness != 0
record.illness != 1
end
def women_of_child_bearing_age_in_household(record)

2
app/models/validations/property_validations.rb

@ -112,7 +112,7 @@ module Validations::PropertyValidations
record.errors.add :beds, I18n.t("validations.property.unittype_gn.one_bedroom_bedsit")
end
if record.other_hhmemb&.zero? && record.is_shared_housing? &&
if record.hhmemb == 1 && record.is_shared_housing? &&
!record.beds.to_i.between?(1, 3) && record.beds.present?
record.errors.add :unittype_gn, I18n.t("validations.property.unittype_gn.one_three_bedroom_single_tenant_shared")
record.errors.add :beds, I18n.t("validations.property.unittype_gn.one_three_bedroom_single_tenant_shared")

6
app/models/validations/setup_validations.rb

@ -1,7 +1,7 @@
module Validations::SetupValidations
def validate_intermediate_rent_product_name(record)
if intermediate_product_rent_type?(record) && record.intermediate_rent_product_name.blank?
record.errors.add :intermediate_rent_product_name, I18n.t("validations.setup.intermediate_rent_product_name.blank")
def validate_irproduct_other(record)
if intermediate_product_rent_type?(record) && record.irproduct_other.blank?
record.errors.add :irproduct_other, I18n.t("validations.setup.intermediate_rent_product_name.blank")
end
end

2
app/models/validations/tenancy_validations.rb

@ -30,6 +30,6 @@ module Validations::TenancyValidations
end
def validate_other_tenancy_type(record)
validate_other_field(record, 4, :tenancy, :tenancyother)
validate_other_field(record, 3, :tenancy, :tenancyother)
end
end

392
app/services/imports/case_logs_import_service.rb

@ -0,0 +1,392 @@
module Imports
class CaseLogsImportService < ImportService
def create_logs(folder)
import_from(folder, :create_log)
end
private
GN_SH = {
general_needs: 1,
supported_housing: 2,
}.freeze
SR_AR_IR = {
social_rent: 1,
affordable_rent: 2,
intermediate_rent: 3,
}.freeze
# For providertype, values are reversed!!!
PRP_LA = {
private_registered_provider: 1,
local_authority: 2,
}.freeze
IRPRODUCT = {
rent_to_buy: 1,
london_living_rent: 2,
other_intermediate_rent_product: 3,
}.freeze
# These must match our form
RENT_TYPE = {
social_rent: 0,
affordable_rent: 1,
london_affordable_rent: 2,
rent_to_buy: 3,
london_living_rent: 4,
other_intermediate_rent_product: 5,
}.freeze
def create_log(xml_doc)
attributes = {}
# Required fields for status complete or logic to work
# Note: order matters when we derive from previous values (attributes parameter)
attributes["startdate"] = compose_date(xml_doc, "DAY", "MONTH", "YEAR")
attributes["owning_organisation_id"] = find_organisation_id(xml_doc, "OWNINGORGID", "OWNINGORGNAME", "HCNUM")
attributes["managing_organisation_id"] = find_organisation_id(xml_doc, "MANINGORGID", "MANINGORGNAME", "MANHCNUM")
attributes["startertenancy"] = unsafe_string_as_integer(xml_doc, "_2a")
attributes["tenancy"] = unsafe_string_as_integer(xml_doc, "Q2b")
attributes["tenancyother"] = string_or_nil(xml_doc, "Q2ba")
attributes["tenancylength"] = safe_string_as_integer(xml_doc, "_2cYears")
attributes["needstype"] = needs_type(xml_doc)
attributes["lar"] = london_affordable_rent(xml_doc)
attributes["irproduct"] = unsafe_string_as_integer(xml_doc, "IRProduct")
attributes["irproduct_other"] = string_or_nil(xml_doc, "IRProductOther")
attributes["rent_type"] = rent_type(xml_doc, attributes["lar"], attributes["irproduct"])
attributes["hhmemb"] = safe_string_as_integer(xml_doc, "HHMEMB")
(1..8).each do |index|
attributes["age#{index}"] = safe_string_as_integer(xml_doc, "P#{index}Age")
attributes["age#{index}_known"] = age_known(xml_doc, index, attributes["hhmemb"])
attributes["sex#{index}"] = sex(xml_doc, index)
attributes["ecstat#{index}"] = unsafe_string_as_integer(xml_doc, "P#{index}Eco")
end
(2..8).each do |index|
attributes["relat#{index}"] = relat(xml_doc, index)
end
attributes["ethnic"] = unsafe_string_as_integer(xml_doc, "P1Eth")
attributes["ethnic_group"] = ethnic_group(attributes["ethnic"])
attributes["national"] = unsafe_string_as_integer(xml_doc, "P1Nat")
attributes["preg_occ"] = unsafe_string_as_integer(xml_doc, "Preg")
attributes["armedforces"] = unsafe_string_as_integer(xml_doc, "ArmedF")
attributes["leftreg"] = unsafe_string_as_integer(xml_doc, "LeftAF")
attributes["reservist"] = unsafe_string_as_integer(xml_doc, "Inj")
attributes["hb"] = unsafe_string_as_integer(xml_doc, "Q6Ben")
attributes["benefits"] = unsafe_string_as_integer(xml_doc, "Q7Ben")
attributes["earnings"] = safe_string_as_decimal(xml_doc, "Q8Money")
attributes["net_income_known"] = net_income_known(xml_doc, attributes["earnings"])
attributes["incfreq"] = unsafe_string_as_integer(xml_doc, "Q8a")
attributes["reason"] = unsafe_string_as_integer(xml_doc, "Q9a")
attributes["reasonother"] = string_or_nil(xml_doc, "Q9aa")
attributes["underoccupation_benefitcap"] = unsafe_string_as_integer(xml_doc, "_9b")
%w[a b c f g h].each do |letter|
attributes["housingneeds_#{letter}"] = housing_needs(xml_doc, letter)
end
attributes["illness"] = unsafe_string_as_integer(xml_doc, "Q10ia")
(1..10).each do |index|
attributes["illness_type_#{index}"] = illness_type(xml_doc, index)
end
attributes["prevten"] = unsafe_string_as_integer(xml_doc, "Q11")
attributes["prevloc"] = string_or_nil(xml_doc, "Q12aONS")
attributes["previous_postcode_known"] = previous_postcode_known(xml_doc)
attributes["ppostcode_full"] = compose_postcode(xml_doc, "PPOSTC1", "PPOSTC2")
attributes["layear"] = unsafe_string_as_integer(xml_doc, "Q12c")
attributes["waityear"] = unsafe_string_as_integer(xml_doc, "Q12d")
attributes["homeless"] = unsafe_string_as_integer(xml_doc, "Q13")
attributes["reasonpref"] = unsafe_string_as_integer(xml_doc, "Q14a")
attributes["rp_homeless"] = unsafe_string_as_integer(xml_doc, "Q14b1")
attributes["rp_insan_unsat"] = unsafe_string_as_integer(xml_doc, "Q14b2")
attributes["rp_medwel"] = unsafe_string_as_integer(xml_doc, "Q14b3")
attributes["rp_hardship"] = unsafe_string_as_integer(xml_doc, "Q14b4")
attributes["rp_dontknow"] = unsafe_string_as_integer(xml_doc, "Q14b5")
attributes["cbl"] = unsafe_string_as_integer(xml_doc, "Q15CBL")
attributes["chr"] = unsafe_string_as_integer(xml_doc, "Q15CHR")
attributes["cap"] = unsafe_string_as_integer(xml_doc, "Q15CAP")
attributes["referral"] = unsafe_string_as_integer(xml_doc, "Q16")
attributes["period"] = unsafe_string_as_integer(xml_doc, "Q17")
attributes["brent"] = safe_string_as_decimal(xml_doc, "Q18ai")
attributes["scharge"] = safe_string_as_decimal(xml_doc, "Q18aii")
attributes["pscharge"] = safe_string_as_decimal(xml_doc, "Q18aiii")
attributes["supcharg"] = safe_string_as_decimal(xml_doc, "Q18aiv")
attributes["tcharge"] = safe_string_as_decimal(xml_doc, "Q18av")
attributes["hbrentshortfall"] = unsafe_string_as_integer(xml_doc, "Q18d")
attributes["voiddate"] = compose_date(xml_doc, "VDAY", "VMONTH", "VYEAR")
attributes["mrcdate"] = compose_date(xml_doc, "MRCDAY", "MRCMONTH", "MRCYEAR")
attributes["offered"] = safe_string_as_integer(xml_doc, "Q20")
attributes["propcode"] = string_or_nil(xml_doc, "Q21a")
attributes["beds"] = safe_string_as_integer(xml_doc, "Q22")
attributes["unittype_gn"] = unsafe_string_as_integer(xml_doc, "Q23")
attributes["builtype"] = unsafe_string_as_integer(xml_doc, "Q24")
attributes["wchair"] = unsafe_string_as_integer(xml_doc, "Q25")
attributes["unitletas"] = unsafe_string_as_integer(xml_doc, "Q26")
attributes["rsnvac"] = unsafe_string_as_integer(xml_doc, "Q27")
attributes["renewal"] = renewal(attributes["rsnvac"])
attributes["la"] = string_or_nil(xml_doc, "Q28ONS")
attributes["postcode_full"] = compose_postcode(xml_doc, "POSTCODE", "POSTCOD2")
attributes["postcode_known"] = attributes["postcode_full"].nil? ? 0 : 1
# Not specific to our form but required for CDS and can't be inferred
attributes["old_form_id"] = Integer(field_value(xml_doc, "xmlns", "FORM"))
# Specific to us
attributes["previous_la_known"] = 1 # Defaulting to Yes (Required)
attributes["la_known"] = 1 # Defaulting to Yes (Required)
attributes["created_at"] = Date.parse(field_value(xml_doc, "meta", "created-date"))
attributes["updated_at"] = Date.parse(field_value(xml_doc, "meta", "modified-date"))
case_log = CaseLog.new(attributes)
case_log.save!
end
# Safe: A string that represents only an integer (or empty/nil)
def safe_string_as_integer(xml_doc, attribute)
str = field_value(xml_doc, "xmlns", attribute)
Integer(str, exception: false)
end
# Safe: A string that represents only a decimal (or empty/nil)
def safe_string_as_decimal(xml_doc, attribute)
str = field_value(xml_doc, "xmlns", attribute)
BigDecimal(str, exception: false)
end
# Unsafe: A string that has more than just the integer value
def unsafe_string_as_integer(xml_doc, attribute)
str = field_value(xml_doc, "xmlns", attribute)
if str.blank?
nil
else
str.to_i
end
end
def compose_date(xml_doc, day_str, month_str, year_str)
day = Integer(field_value(xml_doc, "xmlns", day_str), exception: false)
month = Integer(field_value(xml_doc, "xmlns", month_str), exception: false)
year = Integer(field_value(xml_doc, "xmlns", year_str), exception: false)
if day.nil? || month.nil? || year.nil?
nil
else
Date.new(year, month, day)
end
end
def get_form_name_component(xml_doc, index)
form_name = field_value(xml_doc, "meta", "form-name")
form_type_components = form_name.split("-")
form_type_components[index]
end
def needs_type(xml_doc)
gn_sh = get_form_name_component(xml_doc, -1)
case gn_sh
when "GN"
GN_SH[:general_needs]
when "SH"
GN_SH[:supported_housing]
else
raise "Unknown needstype value: #{gn_sh}"
end
end
# This does not match renttype (CDS) which is derived by case log logic
def rent_type(xml_doc, lar, irproduct)
sr_ar_ir = get_form_name_component(xml_doc, -2)
case sr_ar_ir
when "SR"
RENT_TYPE[:social_rent]
when "AR"
if lar == 1
RENT_TYPE[:london_affordable_rent]
else
RENT_TYPE[:affordable_rent]
end
when "IR"
if irproduct == IRPRODUCT[:rent_to_buy]
RENT_TYPE[:rent_to_buy]
elsif irproduct == IRPRODUCT[:london_living_rent]
RENT_TYPE[:london_living_rent]
elsif irproduct == IRPRODUCT[:other_intermediate_rent_product]
RENT_TYPE[:other_intermediate_rent_product]
end
else
raise "Could not infer rent type with '#{sr_ar_ir}'"
end
end
def find_organisation_id(xml_doc, id_field, name_field, reg_field)
old_visible_id = unsafe_string_as_integer(xml_doc, id_field)
organisation = Organisation.find_by(old_visible_id:)
# Quick hack: should be removed when all organisations are imported
# Will fail in the future if the organisation is missing
if organisation.nil?
organisation = Organisation.new
organisation.old_visible_id = old_visible_id
let_type = unsafe_string_as_integer(xml_doc, "landlord")
organisation.provider_type = if let_type == PRP_LA[:local_authority]
1
else
2
end
organisation.name = string_or_nil(xml_doc, name_field)
organisation.housing_registration_no = string_or_nil(xml_doc, reg_field)
organisation.save!
end
organisation.id
end
def sex(xml_doc, index)
sex = field_value(xml_doc, "xmlns", "P#{index}Sex")
case sex
when "Male"
"M"
when "Female"
"F"
when "Other", "Non-binary"
"X"
when "Refused"
"R"
end
end
def relat(xml_doc, index)
relat = field_value(xml_doc, "xmlns", "P#{index}Rel")
case relat
when "Child"
"C"
when "Partner"
"P"
when "Other", "Non-binary"
"X"
when "Refused"
"R"
end
end
def age_known(xml_doc, index, hhmemb)
return nil if index > hhmemb
age_refused = field_value(xml_doc, "xmlns", "P#{index}AR")
if age_refused == "AGE_REFUSED"
1 # No
else
0 # Yes
end
end
def previous_postcode_known(xml_doc)
previous_postcode_known = field_value(xml_doc, "xmlns", "Q12bnot")
if previous_postcode_known == "Temporary or Unknown"
0
else
1
end
end
def compose_postcode(xml_doc, outcode, incode)
outcode_value = field_value(xml_doc, "xmlns", outcode)
incode_value = field_value(xml_doc, "xmlns", incode)
if outcode_value.blank? || incode_value.blank?
nil
else
"#{outcode_value} #{incode_value}"
end
end
def london_affordable_rent(xml_doc)
lar = unsafe_string_as_integer(xml_doc, "LAR")
if lar == 1
1
else
# We default to No for any other values (nil, not known)
2
end
end
def renewal(rsnvac)
# Relet – renewal of fixed-term tenancy
if rsnvac == 14
1
else
0
end
end
def string_or_nil(xml_doc, attribute)
str = field_value(xml_doc, "xmlns", attribute)
str.presence
end
def ethnic_group(ethnic)
case ethnic
when 1, 2, 3, 18
# White
0
when 4, 5, 6, 7
# Mixed
1
when 8, 9, 10, 11, 15
# Asian
2
when 12, 13, 14
# Black
3
when 16, 19
# Others
4
when 17
# Refused
5
end
end
# Letters should be lowercase to match case
def housing_needs(xml_doc, letter)
housing_need = field_value(xml_doc, "xmlns", "Q10-#{letter}")
if housing_need == "Yes"
1
else
0
end
end
def net_income_known(xml_doc, earnings)
incref = field_value(xml_doc, "xmlns", "Q8Refused")
if incref == "Refused"
# Tenant prefers not to say
2
elsif earnings.nil?
# No
1
else
# Yes
0
end
end
def illness_type(xml_doc, index)
illness_type = string_or_nil(xml_doc, "Q10ib-#{index}")
if illness_type == "Yes"
1
else
0
end
end
end
end

37
app/views/case_logs/_log_filters.erb

@ -1,25 +1,16 @@
<div class="app-filter-layout__filter" tabindex="-1" id="filter-panel">
<div class="app-filter" >
<div class="app-filter__header">
<h2 class="govuk-heading-m">Filters</h2>
<button
class="app-filter__close"
type="button"
data-controller="filter"
data-action="click->filter#toggleFilter">
Close
</button>
</div>
<div class="app-filter__content">
<div class="govuk-form-group app-filter__group">
<%= form_with url: "/logs", html: { method: :get } do |f| %>
<% years = {"2021": "2021/22", "2022": "2022/23"} %>
<%= render partial: "filters/checkbox_filter", locals: { f: f, options: years, label: "Collection year", category: "years" } %>
<%= render partial: "filters/checkbox_filter", locals: { f: f, options: status_filters, label: "Status", category: "status" } %>
<%= render partial: "filters/checkbox_filter", locals: { f: f, options: {"all": "All", "yours": "Yours"}, label: "Logs", category: "user" } %>
<%= f.govuk_submit "Apply filters", class: "govuk-!-margin-top-4" %>
<% end %>
</div>
</div>
<div class="app-filter-layout__filter">
<div class="app-filter">
<div class="app-filter__header">
<h2 class="govuk-heading-m">Filters</h2>
</div>
<div class="app-filter__content">
<%= form_with url: "/logs", html: { method: :get } do |f| %>
<% years = {"2021": "2021/22", "2022": "2022/23"} %>
<%= render partial: "filters/checkbox_filter", locals: { f: f, options: years, label: "Collection year", category: "years" } %>
<%= render partial: "filters/checkbox_filter", locals: { f: f, options: status_filters, label: "Status", category: "status" } %>
<%= render partial: "filters/checkbox_filter", locals: { f: f, options: {"all": "All", "yours": "Yours"}, label: "Logs", category: "user" } %>
<%= f.govuk_submit "Apply filters", class: "govuk-!-margin-bottom-0" %>
<% end %>
</div>
</div>
</div>

11
app/views/case_logs/_log_list.html.erb

@ -28,21 +28,21 @@
<%= govuk_link_to log.id, case_log_path(log) %>
</th>
<td class="govuk-table__cell app-!-font-tabular">
<%= log.tenant_code? ? log.tenant_code : '–' %>
<%= log.tenant_code? ? log.tenant_code : "–" %>
</td>
<td class="govuk-table__cell app-!-font-tabular">
<%= log.propcode? ? log.propcode : '–' %>
<%= log.propcode? ? log.propcode : "–" %>
</td>
<td class="govuk-table__cell">
<%= log.startdate.present? ? log.startdate.to_formatted_s(:govuk_date) : '–' %>
<%= log.startdate.present? ? log.startdate.to_formatted_s(:govuk_date) : "–" %>
</td>
<td class="govuk-table__cell">
<%= log.created_at.to_formatted_s(:govuk_date) %>
</td>
<td class="govuk-table__cell">
<%= govuk_tag(
colour: log.status == 'completed' ? 'blue' : 'grey',
text: log.status.humanize
colour: log.status == "completed" ? "blue" : "grey",
text: log.status.humanize,
) %>
</td>
<% if current_user.support? %>
@ -59,4 +59,3 @@
</table>
</section>
</figure>

5
app/views/case_logs/bulk_upload.html.erb

@ -5,8 +5,7 @@
<%= f.govuk_file_field :case_log_bulk_upload,
label: { text: content_for(:title), size: "l" },
hint: { text: "Upload a spreadsheet using the template" }
%>
hint: { text: "Upload a spreadsheet using the template" } %>
<%= f.govuk_submit "Upload" %>
<%= f.govuk_submit "Upload" %>
<% end %>

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

@ -1,7 +1,7 @@
<% content_for :title, "Log #{@case_log.id}" %>
<% content_for :breadcrumbs, govuk_breadcrumbs(breadcrumbs: {
"Logs" => "/logs",
content_for(:title) => ""
content_for(:title) => "",
}) %>
<div class="govuk-grid-row">

21
app/views/case_logs/index.html.erb

@ -3,28 +3,17 @@
<h1 class="govuk-heading-l">
<%= content_for(:title) %>
</h1>
<div class="app-filter-layout" data-module="filter-layout" data-module-started="true">
<div class="govuk-button-group">
<button
aria-expanded="true"
aria-has-popup="true"
class="govuk-button govuk-button--secondary app-filter-toggle__button"
type="button"
id="toggle-filter-button"
data-controller="filter"
data-action="click->filter#toggleFilter"
>Show filters
</button>
<div class="app-filter-layout" data-controller="filter-layout">
<div class="govuk-button-group app-filter-toggle">
<%= govuk_button_to "Create a new lettings log", case_logs_path %>
<%#= govuk_link_to "Upload logs", bulk_upload_case_logs_path %>
</div>
<%= render partial: "log_filters"%>
<%= render partial: "log_filters" %>
<% if @case_logs.present? %>
<div class="app-filter-layout__content">
<%= render partial: "log_list", locals: { case_logs: @case_logs, title: "Logs", pagy: @pagy } %>
<%== render partial: 'pagy/nav', locals: { pagy: @pagy, item_name: "logs" } %>
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "logs" } %>
</div>
<% end %>
</div>

3
app/views/devise/confirmations/new.html.erb

@ -7,8 +7,7 @@
label: { text: "Email address" },
autocomplete: "email",
spellcheck: "false",
value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email)
%>
value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %>
<%= f.govuk_submit "Resend confirmation instructions" %>
<% end %>

8
app/views/devise/passwords/edit.html.erb

@ -2,7 +2,7 @@
<% content_for :before_content do %>
<%= govuk_back_link(
text: 'Back',
text: "Back",
href: :back,
) %>
<% end %>
@ -19,12 +19,10 @@
<%= f.govuk_password_field :password,
label: { text: "New password" },
hint: @minimum_password_length ? { text: "Your password must be at least #{@minimum_password_length} characters and hard to guess." } : nil,
autocomplete: "new-password"
%>
autocomplete: "new-password" %>
<%= f.govuk_password_field :password_confirmation,
label: { text: "Confirm new password" }
%>
label: { text: "Confirm new password" } %>
<%= f.govuk_submit "Update" %>
</div>

5
app/views/devise/passwords/new.html.erb

@ -2,7 +2,7 @@
<% content_for :before_content do %>
<%= govuk_back_link(
text: 'Back',
text: "Back",
href: :back,
) %>
<% end %>
@ -22,8 +22,7 @@
<%= f.govuk_email_field :email,
label: { text: "Email address" },
autocomplete: "email",
spellcheck: "false"
%>
spellcheck: "false" %>
<%= f.govuk_submit "Send email" %>
</div>

8
app/views/devise/passwords/reset_password.html.erb

@ -2,7 +2,7 @@
<% content_for :before_content do %>
<%= govuk_back_link(
text: 'Back',
text: "Back",
href: :back,
) %>
<% end %>
@ -20,12 +20,10 @@
<%= f.govuk_password_field :password,
label: { text: "New password" },
hint: @minimum_password_length ? { text: "Your password must be at least #{@minimum_password_length} characters and hard to guess." } : nil,
autocomplete: "new-password"
%>
autocomplete: "new-password" %>
<%= f.govuk_password_field :password_confirmation,
label: { text: "Confirm new password" }
%>
label: { text: "Confirm new password" } %>
<%= f.govuk_submit "Update" %>
</div>

6
app/views/devise/sessions/new.html.erb

@ -19,12 +19,10 @@
<%= f.govuk_email_field :email,
label: { text: "Email address" },
autocomplete: "email",
spellcheck: "false"
%>
spellcheck: "false" %>
<%= f.govuk_password_field :password,
autocomplete: "current-password"
%>
autocomplete: "current-password" %>
<%= f.hidden_field :start, value: request["start"] %>
<%= f.govuk_submit "Sign in" %>

12
app/views/devise/shared/_links.html.erb

@ -1,25 +1,25 @@
<%- if controller_name != 'sessions' %>
<p class="govuk-body">Already have an account? <%= govuk_link_to "Sign in", new_session_path(resource_name) %>.</p><br />
<p class="govuk-body">Already have an account? <%= govuk_link_to "Sign in", new_session_path(resource_name) %>.</p>
<% end %>
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
<%= govuk_link_to "Sign up", new_registration_path(resource_name) %><br />
<%= govuk_link_to "Sign up", new_registration_path(resource_name) %><br>
<% end %>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<p class="govuk-body"> You can <%= govuk_link_to "reset your password", new_password_path(resource_name) %> if you’ve forgotten it.<p><br />
<p class="govuk-body">You can <%= govuk_link_to "reset your password", new_password_path(resource_name) %> if you’ve forgotten it.</p>
<% end %>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
<%= govuk_link_to "Didn’t receive confirmation instructions?", new_confirmation_path(resource_name) %><br />
<%= govuk_link_to "Didn’t receive confirmation instructions?", new_confirmation_path(resource_name) %><br>
<% end %>
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
<%= govuk_link_to "Didn’t receive unlock instructions?", new_unlock_path(resource_name) %><br />
<%= govuk_link_to "Didn’t receive unlock instructions?", new_unlock_path(resource_name) %><br>
<% end %>
<%- if devise_mapping.omniauthable? %>
<%- resource_class.omniauth_providers.each do |provider| %>
<%= govuk_link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), method: :post %><br />
<%= govuk_link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider), method: :post %><br>
<% end %>
<% end %>

4
app/views/devise/two_factor_authentication/resend.html.erb

@ -2,8 +2,8 @@
<% content_for :before_content do %>
<%= govuk_back_link(
text: 'Back',
href: 'javascript:history.back()',
text: "Back",
href: "javascript:history.back()",
) %>
<% end %>

5
app/views/devise/two_factor_authentication/show.html.erb

@ -15,9 +15,8 @@
<%= f.govuk_number_field :code,
label: { text: "Security code", size: "m" },
width: 5,
autocomplete: 'one-time-code',
autofocus: true
%>
autocomplete: "one-time-code",
autofocus: true %>
<%= f.govuk_submit "Submit" %>
</div>

3
app/views/devise/unlocks/new.html.erb

@ -6,8 +6,7 @@
<%= f.govuk_email_field :email,
label: { text: "Email address" },
autocomplete: "email",
spellcheck: "false"
%>
spellcheck: "false" %>
<%= f.govuk_submit "Resend unlock instructions" %>
<% end %>

16
app/views/filters/_checkbox_filter.html.erb

@ -1,10 +1,8 @@
<%= f.govuk_check_boxes_fieldset category.to_sym, legend: { text: label, size: "s"} do %>
<div class="govuk-checkboxes govuk-checkboxes--small" data-module="govuk-checkboxes">
<% options.map do |key, option| %>
<%= f.govuk_check_box category, "#{key}",
label: { text: option },
checked: filter_selected?(category, key),
size: "s" %>
<% end %>
</div>
<%= f.govuk_check_boxes_fieldset category.to_sym, legend: { text: label, size: "s" }, small: true, form_group: { classes: "app-filter__group" } do %>
<% options.map do |key, option| %>
<%= f.govuk_check_box category, key.to_s,
label: { text: option },
checked: filter_selected?(category, key),
size: "s" %>
<% end %>
<% end %>

6
app/views/form/_check_answers_table.html.erb

@ -1,11 +1,11 @@
<div class="govuk-summary-list__row">
<dt class="govuk-summary-list__key">
<%= question.check_answer_label.to_s.present? ? question.check_answer_label.to_s : question.header.to_s %>
<%= question.check_answer_label.to_s.presence || question.header.to_s %>
</dt>
<dd class="govuk-summary-list__value">
<%= get_answer_label(question, @case_log) %><br/>
<%= get_answer_label(question, @case_log) %><br>
<% question.get_inferred_answers(@case_log).each do |inferred_answer| %>
<span class="govuk-!-font-weight-regular app-!-colour-muted"><%= inferred_answer %></span><br/>
<span class="govuk-!-font-weight-regular app-!-colour-muted"><%= inferred_answer %></span><br>
<% end %>
</dd>
<dd class="govuk-summary-list__actions">

7
app/views/form/_checkbox_question.html.erb

@ -12,12 +12,11 @@
<%= f.govuk_check_box_divider %>
<% else %>
<%= f.govuk_check_box question.id, key,
label: { text: options['value'] },
hint: { text: options['hint'] },
label: { text: options["value"] },
hint: { text: options["hint"] },
checked: @case_log[key] == 1,
exclusive: after_divider,
**stimulus_html_attributes(question)
%>
**stimulus_html_attributes(question) %>
<% end %>
<% end %>
<% end %>

11
app/views/form/_date_question.html.erb

@ -1,9 +1,8 @@
<%= render partial: "form/guidance/#{question.guidance_partial}" if question.guidance_partial %>
<%= f.govuk_date_field question.id.to_sym,
caption: caption(caption_text, page_header, conditional),
legend: legend(question, page_header, conditional),
hint: { text: question.hint_text&.html_safe },
width: 20,
**stimulus_html_attributes(question)
%>
caption: caption(caption_text, page_header, conditional),
legend: legend(question, page_header, conditional),
hint: { text: question.hint_text&.html_safe },
width: 20,
**stimulus_html_attributes(question) %>

13
app/views/form/_interruption_screen_question.html.erb

@ -1,6 +1,6 @@
<%= govuk_panel(
title_text: title_text,
classes: 'app-panel--interruption',
title_text:,
classes: "app-panel--interruption",
) do %>
<%= display_informative_text(informative_text, case_log) %>
<%= f.govuk_radio_buttons_fieldset question.id.to_sym,
@ -12,12 +12,11 @@
<% else %>
<%= f.govuk_radio_button question.id,
key,
label: { text: options['value'] },
hint: { text: options['hint'] },
**stimulus_html_attributes(question)
%>
label: { text: options["value"] },
hint: { text: options["hint"] },
**stimulus_html_attributes(question) %>
<% end %>
<% end %>
<% end %>
<%= f.govuk_submit "Save and continue", accesskey: "s", class: "app-button--inverse govuk-!-margin-bottom-0" %>
<% end %>
<% end %>

2
app/views/form/_numeric_output_question.html.erb

@ -15,7 +15,7 @@
step="<%= question.step %>"
type="number"
name="case_log[tcharge]"
for="<%= question.fields_added.present? ? question.fields_added.map { |x| "case-log-#{x}-field"}.join(" ") : "" %>">
for="<%= question.fields_added.present? ? question.fields_added.map { |x| "case-log-#{x}-field" }.join(" ") : "" %>">
<%= case_log[question.id] %></output>
<span class="govuk-input__suffix"><%= question.suffix %></span>
</div>

18
app/views/form/_numeric_question.html.erb

@ -1,12 +1,12 @@
<%= render partial: "form/guidance/#{question.guidance_partial}" if question.guidance_partial %>
<%= f.govuk_number_field question.id.to_sym,
caption: caption(caption_text, page_header, conditional),
label: legend(question, page_header, conditional),
hint: { text: question.hint_text&.html_safe },
min: question.min, max: question.max, step: question.step,
width: question.width, :readonly => question.read_only?,
prefix_text: question.prefix.to_s,
suffix_text: question.suffix.is_a?(String) ? question.suffix : nil,
**stimulus_html_attributes(question)
%>
caption: caption(caption_text, page_header, conditional),
label: legend(question, page_header, conditional),
hint: { text: question.hint_text&.html_safe },
min: question.min, max: question.max, step: question.step,
width: question.width,
readonly: question.read_only?,
prefix_text: question.prefix.to_s,
suffix_text: question.suffix.is_a?(String) ? question.suffix : nil,
**stimulus_html_attributes(question) %>

55
app/views/form/_radio_question.html.erb

@ -1,37 +1,36 @@
<%= render partial: "form/guidance/#{question.guidance_partial}" if question.guidance_partial %>
<%= f.govuk_radio_buttons_fieldset question.id.to_sym,
caption: caption(caption_text, page_header, conditional),
legend: legend(question, page_header, conditional),
hint: { text: question.hint_text&.html_safe } do %>
caption: caption(caption_text, page_header, conditional),
legend: legend(question, page_header, conditional),
hint: { text: question.hint_text&.html_safe } do %>
<% question.displayed_answer_options.map do |key, options| %>
<% if key.starts_with?("divider") %>
<%= f.govuk_radio_divider %>
<% question.displayed_answer_options.map do |key, options| %>
<% if key.starts_with?("divider") %>
<%= f.govuk_radio_divider %>
<% else %>
<% conditional_question = find_conditional_question(@page, question, key) %>
<% if conditional_question.nil? %>
<%= f.govuk_radio_button question.id,
key,
label: { text: options["value"] },
hint: { text: options["hint"] },
**stimulus_html_attributes(question) %>
<% else %>
<% conditional_question = find_conditional_question(@page, question, key) %>
<% if conditional_question.nil? %>
<%= f.govuk_radio_button question.id,
key,
label: { text: options['value'] },
hint: { text: options['hint'] },
**stimulus_html_attributes(question)
%>
<% else %>
<%= f.govuk_radio_button question.id,
key,
label: { text: options['value'] },
hint: { text: options['hint'] },
**stimulus_html_attributes(question) do %>
<%= render partial: "#{conditional_question.type}_question", locals: {
question: conditional_question,
caption_text: caption_text,
page_header: page_header,
f: f,
conditional: true
} %>
<% end %>
<%= f.govuk_radio_button question.id,
key,
label: { text: options["value"] },
hint: { text: options["hint"] },
**stimulus_html_attributes(question) do %>
<%= render partial: "#{conditional_question.type}_question", locals: {
question: conditional_question,
caption_text:,
page_header:,
f:,
conditional: true,
} %>
<% end %>
<% end %>
<% end %>
<% end %>
<% end %>

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

@ -1,14 +1,13 @@
<%= render partial: "form/guidance/#{question.guidance_partial}" if question.guidance_partial %>
<% selected = @case_log.public_send(question.id) || "" %>
<%= answers = question.displayed_answer_options.map { |key, value| OpenStruct.new(id: key, name: value) }
f.govuk_collection_select question.id.to_sym,
<% answers = question.displayed_answer_options.map { |key, value| OpenStruct.new(id: key, name: value) } %>
<%= f.govuk_collection_select question.id.to_sym,
answers,
:id,
:name,
caption: caption(caption_text, page_header, conditional),
label: legend(question, page_header, conditional),
hint: { text: question.hint_text&.html_safe },
options: { disabled: [""], selected: selected },
"data-controller": "accessible-autocomplete"
%>
options: { disabled: [""], selected: },
"data-controller": "accessible-autocomplete" %>

11
app/views/form/_text_question.html.erb

@ -1,9 +1,8 @@
<%= render partial: "form/guidance/#{question.guidance_partial}" if question.guidance_partial %>
<%= f.govuk_text_field question.id.to_sym,
caption: caption(caption_text, page_header, conditional),
label: legend(question, page_header, conditional),
hint: { text: question.hint_text&.html_safe },
width: question.width ? question.width : nil,
**stimulus_html_attributes(question)
%>
caption: caption(caption_text, page_header, conditional),
label: legend(question, page_header, conditional),
hint: { text: question.hint_text&.html_safe },
width: question.width || nil,
**stimulus_html_attributes(question) %>

11
app/views/form/_textarea_question.html.erb

@ -1,9 +1,8 @@
<%= render partial: "form/guidance/#{question.guidance_partial}" if question.guidance_partial %>
<%= f.govuk_text_area question.id.to_sym,
caption: caption(caption_text, page_header, conditional),
label: legend(question, page_header, conditional),
hint: { text: question.hint_text&.html_safe },
width: question.width ? question.width : nil,
**stimulus_html_attributes(question)
%>
caption: caption(caption_text, page_header, conditional),
label: legend(question, page_header, conditional),
hint: { text: question.hint_text&.html_safe },
width: question.width || nil,
**stimulus_html_attributes(question) %>

14
app/views/form/check_answers.html.erb

@ -1,8 +1,8 @@
<% content_for :title, "#{subsection.id.humanize} - Check your answers" %>
<% content_for :breadcrumbs, govuk_breadcrumbs(breadcrumbs: {
"Logs" => "/logs",
"Log #{@case_log.id.to_s}" => "/logs/" + @case_log.id.to_s,
subsection.label => ""
"Log #{@case_log.id}" => "/logs/#{@case_log.id}",
subsection.label => "",
}) %>
<div class="govuk-grid-row">
@ -16,17 +16,17 @@
<dl class="govuk-summary-list">
<% subsection.applicable_questions(@case_log).each do |question| %>
<%= render partial: 'form/check_answers_table', locals: { question: question, case_log: @case_log } %>
<%= render partial: "form/check_answers_table", locals: { question:, case_log: @case_log } %>
<% end %>
</dl>
<%= form_with model: @case_log, method: "get" do |f| %>
<%= f.govuk_submit 'Save and return to log' do %>
<%= f.govuk_submit "Save and return to log" do %>
<% if @case_log.status == "in_progress" && @case_log.status == "completed" || @case_log.form.all_subsections_except_declaration_completed?(@case_log) == false %>
<%= govuk_button_link_to 'Save and go to next incomplete section', "/logs/#{@case_log.id}/#{@case_log.form.next_incomplete_section_redirect_path(subsection, @case_log)}", secondary: true %>
<%= govuk_button_link_to "Save and go to next incomplete section", "/logs/#{@case_log.id}/#{@case_log.form.next_incomplete_section_redirect_path(subsection, @case_log)}", secondary: true %>
<% elsif @case_log.status == "completed" || @case_log.form.all_subsections_except_declaration_completed?(@case_log) %>
<%= govuk_button_link_to 'Save and go to submit', "/logs/#{@case_log.id}/#{@case_log.form.next_incomplete_section_redirect_path(subsection, @case_log)}", secondary: true %>
<% end%>
<%= govuk_button_link_to "Save and go to submit", "/logs/#{@case_log.id}/#{@case_log.form.next_incomplete_section_redirect_path(subsection, @case_log)}", secondary: true %>
<% end %>
<% end %>
<% end %>
</div>

2
app/views/form/guidance/_what_counts_as_income.html.erb

@ -1,4 +1,4 @@
<%= govuk_details(summary_text: 'What counts as income?') do %>
<%= govuk_details(summary_text: "What counts as income?") do %>
<p class="govuk-body">You should include any income after tax from:</p>
<ul class="govuk-list govuk-list--bullet">
<li>employment</li>

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

@ -1,9 +1,9 @@
<% content_for :title, @page.header.present? ? @page.header : @page.questions.first().header.html_safe %>
<% content_for :title, @page.header.presence || @page.questions.first.header.html_safe %>
<% content_for :before_content do %>
<%= govuk_back_link(
text: 'Back',
href: 'javascript:history.back()',
text: "Back",
href: "javascript:history.back()",
) %>
<% end %>
@ -11,7 +11,7 @@
<%= form_with model: @case_log, url: form_case_log_path(@case_log), method: "post", local: true do |f| %>
<div class="govuk-grid-row">
<div class=<%= @page.questions[0].type == "interruption_screen" ? "govuk-grid-column-full-from-desktop" : "govuk-grid-column-two-thirds-from-desktop"%>>
<div class="govuk-grid-column-<%= @page.questions[0].type == "interruption_screen" ? "full-from-desktop" : "two-thirds-from-desktop" %>">
<% remove_other_page_errors(@case_log, @page) %>
<%= f.govuk_error_summary %>
@ -29,14 +29,14 @@
<% end %>
<% @page.non_conditional_questions.map do |question| %>
<div id=<%= question.id + "_div " %><%= display_question_key_div(@page, question) %> >
<div id="<%= question.id %>_div" <%= display_question_key_div(@page, question) %>>
<% if question.read_only? %>
<hr class="govuk-section-break govuk-section-break--visible govuk-section-break--m">
<% end %>
<% if question.type == "interruption_screen" %>
<%= render partial: "form/#{question.type}_question", locals: { question: question, caption_text: @subsection.label, page_header: @page.header, case_log: @case_log, title_text: @page.title_text, informative_text: @page.informative_text, form: @form, f: f, conditional: false } %>
<%= render partial: "form/#{question.type}_question", locals: { question:, caption_text: @subsection.label, page_header: @page.header, case_log: @case_log, title_text: @page.title_text, informative_text: @page.informative_text, form: @form, f:, conditional: false } %>
<% else %>
<%= render partial: "form/#{question.type}_question", locals: { question: question, caption_text: @subsection.label, page_header: @page.header, case_log: @case_log, f: f, conditional: false } %>
<%= render partial: "form/#{question.type}_question", locals: { question:, caption_text: @subsection.label, page_header: @page.header, case_log: @case_log, f:, conditional: false } %>
<% end %>
</div>
<% end %>
@ -44,11 +44,11 @@
<%= f.hidden_field :page, value: @page.id %>
<% if @case_log.form.is_last_question?(@page, @subsection, @case_log) %>
<%= f.govuk_submit "Submit lettings log", accesskey: "s" %>
<%else %>
<% else %>
<% if !@page.id.include?("value_check") %>
<%= f.govuk_submit "Save and continue", accesskey: "s" %>
<%= f.govuk_submit "Save and continue", accesskey: "s" %>
<% end %>
<%end %>
<% end %>
</div>
</div>
<% end %>

16
app/views/layouts/_collection_resources.html.erb

@ -0,0 +1,16 @@
<div class="app-card">
<h2 class="govuk-heading-s">Collection resources</h2>
<%= render DocumentListComponent.new(items: [
{
name: "Lettings log for tenants (2022/23)",
href: "https://core.communities.gov.uk/public/download/guides-and-manuals/2022-23%20Lettings%20paper%20form.pdf?download-format=pdf",
metadata: "PDF, 654 KB, 4 pages",
},
{
name: "Lettings log for tenants (2021/22)",
href: "https://core.communities.gov.uk/public/download/guides-and-manuals/2021_22%20Lettings%20Log.pdf?download-format=pdf",
metadata: "PDF, 302 KB, 3 pages",
},
]) %>
</div>

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

@ -4,21 +4,21 @@
<title><%= browser_title(yield(:title), @pagy, @admin_user, @user, @organisation, @case_log, @resource) %></title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= tag :meta, name: 'viewport', content: 'width=device-width, initial-scale=1' %>
<%= tag :meta, property: 'og:image', content: asset_path('images/govuk-opengraph-image.png') %>
<%= tag :meta, name: 'theme-color', content: '#0b0c0c' %>
<%= favicon_link_tag asset_path('images/favicon.ico') %>
<%= favicon_link_tag asset_path('images/govuk-mask-icon.svg'), rel: 'mask-icon', type: 'image/svg', color: "#0b0c0c" %>
<%= favicon_link_tag asset_path('images/govuk-apple-touch-icon.png'), rel: 'apple-touch-icon', type: 'image/png' %>
<%= favicon_link_tag asset_path('images/govuk-apple-touch-icon-152x152.png'), rel: 'apple-touch-icon', type: 'image/png', size: '152x152' %>
<%= favicon_link_tag asset_path('images/govuk-apple-touch-icon-167x167.png'), rel: 'apple-touch-icon', type: 'image/png', size: '167x167' %>
<%= favicon_link_tag asset_path('images/govuk-apple-touch-icon-180x180.png'), rel: 'apple-touch-icon', type: 'image/png', size: '180x180' %>
<%= tag.meta name: "viewport", content: "width=device-width, initial-scale=1" %>
<%= tag.meta property: "og:image", content: asset_path("images/govuk-opengraph-image.png") %>
<%= tag.meta name: "theme-color", content: "#0b0c0c" %>
<%= favicon_link_tag asset_path("images/favicon.ico") %>
<%= favicon_link_tag asset_path("images/govuk-mask-icon.svg"), rel: "mask-icon", type: "image/svg", color: "#0b0c0c" %>
<%= favicon_link_tag asset_path("images/govuk-apple-touch-icon.png"), rel: "apple-touch-icon", type: "image/png" %>
<%= favicon_link_tag asset_path("images/govuk-apple-touch-icon-152x152.png"), rel: "apple-touch-icon", type: "image/png", size: "152x152" %>
<%= favicon_link_tag asset_path("images/govuk-apple-touch-icon-167x167.png"), rel: "apple-touch-icon", type: "image/png", size: "167x167" %>
<%= favicon_link_tag asset_path("images/govuk-apple-touch-icon-180x180.png"), rel: "apple-touch-icon", type: "image/png", size: "180x180" %>
<%= stylesheet_link_tag "application" %>
<%= javascript_include_tag "vendor/html5shiv.min.js" %>
<%= javascript_tag do -%>
window.html5.elements = 'output';
<script>
window.html5.elements = "output";
html5.shivDocument(document);
<% end -%>
</script>
<%= javascript_include_tag "vendor/polyfill-output-value.js" %>
<%= javascript_include_tag "vendor/outerHTML.js" %>
<%= javascript_include_tag "application", defer: true %>
@ -37,46 +37,61 @@
<body class="govuk-template__body app-template--wide">
<script>
document.body.className = ((document.body.className) ? document.body.className + ' js-enabled' : 'js-enabled');
document.body.className = ((document.body.className) ? document.body.className + " js-enabled" : "js-enabled");
</script>
<%= govuk_skip_link %>
<%= govuk_header(
logotype: 'GOV.UK',
service_name: t('service_name'),
service_url: current_user.nil? ? "/" : '/logs'
) do |component|
if current_user.nil?
component.navigation_item(text: 'Sign in', href: user_session_path)
elsif
component.navigation_item(text: 'Logs', href: case_logs_path)
component.navigation_item(text: 'Your organisation', href: "/organisations/#{current_user.organisation.id}")
component.navigation_item(text: 'Your account', href: account_path)
component.navigation_item(text: 'Sign out', href: destroy_user_session_path)
end
classes: "app-header",
service_url: current_user.nil? ? "/" : "/logs",
navigation_classes: "govuk-header__navigation--end"
) do |component|
component.product_name(name: t("service_name"))
if current_user.nil?
component.navigation_item(text: "Sign in", href: user_session_path)
else
component.navigation_item(text: "Your account", href: account_path)
component.navigation_item(text: "Sign out", href: destroy_user_session_path)
end
%>
end %>
<%= govuk_phase_banner(
classes: "govuk-width-container",
tag: { text: "Beta" },
text: "This is a new service – help us improve it by <a class=\"govuk-link\" href=\"#{t('feedback_form')}\" rel=\"noreferrer noopener\" target=\"_blank\">giving us your feedback (opens in a new tab)</a>".html_safe,
) %>
<% if !current_user.nil? %>
<% if current_user.support? %>
<% items = [
{ name: "Organisations", url: "/organisations" },
{ name: "Users", url: "/users" },
{ name: "Logs", url: case_logs_path },
] %>
<% else %>
<% items = [
{ name: "Logs", url: case_logs_path },
{ name: "Users", url: users_organisation_path(current_user.organisation) },
{ name: "About your organisation", url: "/organisations/#{current_user.organisation.id}" },
] %>
<% end %>
<%= render PrimaryNavigationComponent.new(items:) %>
<% end %>
<div class="govuk-width-container">
<aside>
<%= govuk_phase_banner(
tag: { text: 'Beta' },
text: "This is a new service – help us improve it by <a class=\"govuk-link\" href=\"#{t('feedback_form')}\" rel=\"noreferrer noopener\" target=\"_blank\">giving us your feedback (opens in a new tab)</a>".html_safe
) %>
<%= content_for(:breadcrumbs) %>
<%= content_for(:before_content) %>
</aside>
<%= content_for(:breadcrumbs) %>
<%= content_for(:before_content) %>
<main class="govuk-main-wrapper" id="main-content" role="main">
<% if flash.notice && !flash.notice.include?('translation missing') %>
<% if flash.notice && !flash.notice.include?("translation missing") %>
<%= govuk_notification_banner(
title_text: 'Success',
title_text: "Success",
success: true, title_heading_level: 3,
title_id: "swanky-notifications") do |notification_banner|
notification_banner.heading(text: flash.notice)
end
%>
title_id: "swanky-notifications"
) do |notification_banner|
notification_banner.heading(text: flash.notice)
end %>
<% end %>
<%= content_for?(:content) ? yield(:content) : yield %>
</main>

2
app/views/layouts/mailer.html.erb

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style>
/* Email styles need to be inline */
</style>

2
app/views/layouts/organisations.html.erb

@ -5,8 +5,6 @@
<% items = tab_items(current_user) %>
<%= render TabNavigationComponent.new(items: items) %>
<h2 class="govuk-visually-hidden"><%= content_for(:tab_title) %></h2>
<%= content_for?(:organisations_content) ? yield(:organisations_content) : yield %>

17
app/views/organisations/edit.html.erb

@ -2,7 +2,7 @@
<% content_for :before_content do %>
<%= govuk_back_link(
text: 'Back',
text: "Back",
href: :back,
) %>
<% end %>
@ -15,29 +15,24 @@
</h1>
<%= f.govuk_text_field :name,
autocomplete: "name"
%>
autocomplete: "name" %>
<%= f.govuk_text_field :address_line1,
label: { text: "Address line 1" },
autocomplete: "address-line1"
%>
autocomplete: "address-line1" %>
<%= f.govuk_text_field :address_line2,
label: { text: "Address line 2" },
autocomplete: "address-line2"
%>
autocomplete: "address-line2" %>
<%= f.govuk_text_field :postcode,
autocomplete: "postal-code",
width: 10
%>
width: 10 %>
<%= f.govuk_phone_field :phone,
label: { text: "Telephone number" },
autocomplete: "tel",
width: 20
%>
width: 20 %>
<%= f.govuk_submit "Save changes" %>
</div>

0
app/views/organisations/index.html.erb

28
app/views/organisations/show.html.erb

@ -10,20 +10,28 @@
<% @organisation.display_attributes.each do |attr| %>
<% if current_user.data_coordinator? && attr[:editable] %>
<%= summary_list.row do |row|
row.key { attr[:name].to_s.humanize }
row.value { simple_format(attr[:value].to_s, {}, wrapper_tag: "div") }
row.action(visually_hidden_text: 'name', href: edit_organisation_path, html_attributes: { 'data-qa': "change-#{attr[:name]}" })
end %>
<%= summary_list.row do |row| %>
<% row.key { attr[:name].to_s.humanize } %>
<% row.value { simple_format(attr[:value].to_s, {}, wrapper_tag: "div") } %>
<% row.action(
visually_hidden_text: "name",
href: edit_organisation_path,
html_attributes: { "data-qa": "change-#{attr[:name]}" },
) %>
<% end %>
<% else %>
<%= summary_list.row do |row|
row.key { attr[:name].to_s.humanize }
row.value { simple_format(attr[:value].to_s, {}, wrapper_tag: "div") }
row.action()
end %>
<%= summary_list.row do |row| %>
<% row.key { attr[:name].to_s.humanize } %>
<% row.value { simple_format(attr[:value].to_s, {}, wrapper_tag: "div") } %>
<% row.action %>
<% end %>
<% end %>
<% end %>
<% end %>
</div>
<div class="govuk-grid-column-one-third-from-desktop">
<%= render partial: "layouts/collection_resources" %>
</div>
</div>

20
app/views/organisations/users.html.erb

@ -9,19 +9,19 @@
<% end %>
<%= govuk_table do |table| %>
<%= table.head do |head| %>
<%= head.row do |row|
row.cell(header: true, text: "Name and email adress")
row.cell(header: true, text: "Organisation and role")
row.cell(header: true, text: "Last logged in")
end %>
<%= head.row do |row| %>
<% row.cell(header: true, text: "Name and email adress") %>
<% row.cell(header: true, text: "Organisation and role") %>
<% row.cell(header: true, text: "Last logged in") %>
<% end %>
<% end %>
<% @organisation.users.each do |user| %>
<%= table.body do |body| %>
<%= body.row do |row|
row.cell(text: simple_format(user_cell(user), {}, wrapper_tag: "div"))
row.cell(text: simple_format(org_cell(user), {}, wrapper_tag: "div"))
row.cell(text: user.last_sign_in_at&.to_formatted_s(:govuk_date) )
end %>
<%= body.row do |row| %>
<% row.cell(text: simple_format(user_cell(user), {}, wrapper_tag: "div")) %>
<% row.cell(text: simple_format(org_cell(user), {}, wrapper_tag: "div")) %>
<% row.cell(text: user.last_sign_in_at&.to_formatted_s(:govuk_date)) %>
<% end %>
<% end %>
<% end %>
<% end %>

8
app/views/pagy/_nav.html.erb

@ -5,7 +5,7 @@
<ul class="app-pagination__list">
<li class="app-pagination__item app-pagination__item--prev">
<% if pagy.prev %>
<a class="app-pagination__link" href=<%= "/logs?page=#{pagy.prev}" %>>
<a class="app-pagination__link" href="<%= "/logs?page=#{pagy.prev}" %>">
<% end %>
<span class="app-pagination__link-title">
<svg class="app-pagination__icon" xmlns="http://www.w3.org/2000/svg" height="13" width="17">
@ -16,16 +16,16 @@
</li>
<% pagy.series.each do |item| %>
<% if item == :gap %>
<li class="app-pagination__item app-pagination__item--ellipses">...</li>
<li class="app-pagination__item app-pagination__item--ellipses"></li>
<% elsif item.is_a?(String) %>
<li class="app-pagination__item app-pagination__item--current"><span class="govuk-visually-hidden">Page </span><%= item %><span class="govuk-visually-hidden"> (current page) </span></li>
<% else %>
<li class="app-pagination__item"><a class="app-pagination__link" href=<%= "/logs?page=#{item}" %>><span class="govuk-visually-hidden">Page </span><%= item %></a></li>
<li class="app-pagination__item"><a class="app-pagination__link" href="<%= "/logs?page=#{item}" %>"><span class="govuk-visually-hidden">Page </span><%= item %></a></li>
<% end %>
<% end %>
<li class="app-pagination__item app-pagination__item--next">
<% if pagy.next %>
<a class="app-pagination__link" href=<%= "/logs?page=#{pagy.next}" %>>
<a class="app-pagination__link" href="<%= "/logs?page=#{pagy.next}" %>">
<% end %>
Next <span class="govuk-visually-hidden">page</span>
<span class="app-pagination__link-title">

35
app/views/start/index.html.erb

@ -3,23 +3,17 @@
</h1>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<div class="govuk-grid-column-two-thirds-from-desktop">
<p class="govuk-body">Use this service to submit social housing lettings and sales data to the Department for Levelling Up, Housing and Communities (DLUHC).</p>
<p class="govuk-body">We’ll ask you questions about a letting or sale, like details about the household or property. Your answers will create a log that you can submit directly to us.</p>
<p class="govuk-body">Your organisation can also:</p>
<ul class="govuk-list govuk-list--bullet">
<li>upload data for multiple sales and lettings</li>
<li>transfer data using an API</li>
<%# TODO: Add link to lettings log form %>
<li><%= govuk_link_to("download a copy of the 2021 to 2022 lettings log as a PDF", "#") %> (2MB)</li>
</ul>
<p class="govuk-body">Your organisation can also set up and manage user accounts.</p>
<p class="govuk-body">The data will be used to update the national record for social housing. It will also help to inform policy about the cost of social housing and what type of housing needs to be built.</p>
<p class="govuk-body">This service is only for social housing in England.</p>
<% start_path = current_user ? case_logs_path : (user_session_path + "?start=true") %>
<% start_path = current_user ? case_logs_path : "#{user_session_path}?start=true" %>
<%= govuk_start_button(
text: 'Start now',
href: start_path
text: "Start now",
href: start_path,
) %>
<h2 class="govuk-heading-m">Before you start</h2>
@ -28,22 +22,7 @@
<p class="govuk-body">You can <%= govuk_link_to("request an account", "https://digital.dclg.gov.uk/jira/servicedesk/customer/portal/4/group/21") %> if your organisation doesn’t have one.</p>
</div>
<div class="govuk-grid-column-one-third">
<div class="app-related-navigation">
<nav class="app-related-navigation__nav-section" role="navigation" aria-labelledby="related-content">
<h2 class="app-related-navigation__main-heading" id="related-content">
Related content
</h2>
<ul class="app-related-navigation__link-list">
<li class="app-related-navigation__link">
<%# TODO: Add link to guidance %>
<%= govuk_link_to("How to submit social housing lettings and sales data (CORE)", "#", classes: "app-related-navigation__section-link app-related-navigation__section-link--other") %>
</li>
<li>
<%= govuk_link_to("Data sharing agreement", data_sharing_agreement_path, classes: "app-related-navigation__section-link app-related-navigation__section-link--other") %>
</li>
</ul>
</nav>
</div>
<div class="govuk-grid-column-one-third-from-desktop">
<%= render partial: "layouts/collection_resources" %>
</div>
</div>

23
app/views/users/edit.html.erb

@ -2,7 +2,7 @@
<% content_for :before_content do %>
<%= govuk_back_link(
text: 'Back',
text: "Back",
href: :back,
) %>
<% end %>
@ -17,36 +17,35 @@
</h1>
<%= f.govuk_text_field :name,
autocomplete: "name"
%>
autocomplete: "name" %>
<%= f.govuk_email_field :email,
label: { text: "Email address" },
autocomplete: "email",
spellcheck: "false"
%>
spellcheck: "false" %>
<% if current_user.data_coordinator? || current_user.support? %>
<%= roles = current_user.assignable_roles.map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) }
<%= roles = current_user.assignable_roles.map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %>
f.govuk_collection_radio_buttons :role, roles, :id, :name, legend: { text: "Role", size: "m" }
%>
<%= f.govuk_collection_radio_buttons :role,
roles,
:id,
:name,
legend: { text: "Role", size: "m" } %>
<%= f.govuk_collection_radio_buttons :is_dpo,
[OpenStruct.new(id: false, name: "No"), OpenStruct.new(id: true, name: "Yes")],
:id,
:name,
inline: true,
legend: { text: "Are #{pronoun(@user, current_user)} a data protection officer?", size: "m" }
%>
legend: { text: "Are #{pronoun(@user, current_user)} a data protection officer?", size: "m" } %>
<%= f.govuk_collection_radio_buttons :is_key_contact,
[OpenStruct.new(id: false, name: "No"), OpenStruct.new(id: true, name: "Yes")],
:id,
:name,
inline: true,
legend: { text: "Are #{pronoun(@user, current_user)} a key contact?", size: "m" }
%>
legend: { text: "Are #{pronoun(@user, current_user)} a key contact?", size: "m" } %>
<% end %>
<%= f.govuk_submit "Save changes" %>

0
app/views/users/index.html.erb

24
app/views/users/new.html.erb

@ -2,7 +2,7 @@
<% content_for :before_content do %>
<%= govuk_back_link(
text: 'Back',
text: "Back",
href: :back,
) %>
<% end %>
@ -17,35 +17,35 @@
</h1>
<%= f.govuk_text_field :name,
autocomplete: "name"
%>
autocomplete: "name" %>
<%= f.govuk_email_field :email,
label: { text: "Email address" },
autocomplete: "email",
spellcheck: "false",
value: @resource.email
%>
value: @resource.email %>
<%= roles = current_user.assignable_roles.map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) }
f.govuk_collection_radio_buttons :role, roles, :id, :name, legend: { text: "Role", size: "m" }
%>
<%= roles = current_user.assignable_roles.map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %>
<%= f.govuk_collection_radio_buttons :role,
roles,
:id,
:name,
legend: { text: "Role", size: "m" } %>
<%= f.govuk_collection_radio_buttons :is_dpo,
[OpenStruct.new(id: false, name: "No"), OpenStruct.new(id: true, name: "Yes")],
:id,
:name,
inline: true,
legend: { text: "Are #{pronoun(@user, current_user)} a data protection officer?", size: "m" }
%>
legend: { text: "Are #{pronoun(@user, current_user)} a data protection officer?", size: "m" } %>
<%= f.govuk_collection_radio_buttons :is_key_contact,
[OpenStruct.new(id: false, name: "No"), OpenStruct.new(id: true, name: "Yes")],
:id,
:name,
inline: true,
legend: { text: "Are #{pronoun(@user, current_user)} a key contact?", size: "m" }
%>
legend: { text: "Are #{pronoun(@user, current_user)} a key contact?", size: "m" } %>
<%= f.govuk_submit "Continue" %>
</div>

122
app/views/users/show.html.erb

@ -12,70 +12,86 @@
<%= govuk_summary_list do |summary_list| %>
<%= summary_list.row do |row|
row.key { "Name" }
row.value { @user.name }
if can_edit_names?(@user, current_user)
row.action(visually_hidden_text: "name", href: aliased_user_edit(@user, current_user), html_attributes: { "data-qa": "change-name" })
else
row.action()
end
end %>
row.key { "Name" }
row.value { @user.name }
if can_edit_names?(@user, current_user)
row.action(visually_hidden_text: "name", href: aliased_user_edit(@user, current_user), html_attributes: { "data-qa": "change-name" })
else
row.action
end
end %>
<%= summary_list.row() do |row|
row.key { "Email address" }
row.value { @user.email }
if can_edit_emails?(@user, current_user)
row.action(visually_hidden_text: "email address", href: aliased_user_edit(@user, current_user), html_attributes: { "data-qa": "change-email-address" })
else
row.action()
end
end %>
<%= summary_list.row do |row|
row.key { "Email address" }
row.value { @user.email }
if can_edit_emails?(@user, current_user)
row.action(visually_hidden_text: "email address", href: aliased_user_edit(@user, current_user), html_attributes: { "data-qa": "change-email-address" })
else
row.action
end
end %>
<%= summary_list.row do |row|
row.key { "Password" }
row.value { "••••••••" }
if can_edit_password?(@user, current_user)
row.action(visually_hidden_text: "password", href: edit_password_account_path, html_attributes: { "data-qa": "change-password" })
else
row.action()
end
end %>
row.key { "Password" }
row.value { "••••••••" }
if can_edit_password?(@user, current_user)
row.action(
visually_hidden_text: "password",
href: edit_password_account_path,
html_attributes: { "data-qa": "change-password" },
)
else
row.action
end
end %>
<%= summary_list.row do |row|
row.key { "Organisation" }
row.value { @user.organisation.name }
row.action()
end %>
row.key { "Organisation" }
row.value { @user.organisation.name }
row.action
end %>
<%= summary_list.row do |row|
row.key { "Role" }
row.value { @user.role.humanize }
if can_edit_roles?(@user, current_user)
row.action(visually_hidden_text: "role", href: aliased_user_edit(@user, current_user), html_attributes: { "data-qa": "change-role" })
else
row.action()
end
end %>
row.key { "Role" }
row.value { @user.role.humanize }
if can_edit_roles?(@user, current_user)
row.action(
visually_hidden_text: "role",
href: aliased_user_edit(@user, current_user),
html_attributes: { "data-qa": "change-role" },
)
else
row.action
end
end %>
<%= summary_list.row do |row|
row.key { "Data protection officer" }
row.value { @user.is_data_protection_officer? ? "Yes" : "No" }
if can_edit_dpo?(@user, current_user)
row.action(visually_hidden_text: "are #{pronoun(@user, current_user)} a data protection officer?", href: aliased_user_edit(@user, current_user), html_attributes: { "data-qa": "change-are-#{pronoun(@user, current_user)}-a-data-protection-officer" })
else
row.action()
end
end %>
row.key { "Data protection officer" }
row.value { @user.is_data_protection_officer? ? "Yes" : "No" }
if can_edit_dpo?(@user, current_user)
row.action(
visually_hidden_text: "are #{pronoun(@user, current_user)} a data protection officer?",
href: aliased_user_edit(@user, current_user),
html_attributes: { "data-qa": "change-are-#{pronoun(@user, current_user)}-a-data-protection-officer" },
)
else
row.action
end
end %>
<%= summary_list.row do |row|
row.key { "Key contact" }
row.value { @user.is_key_contact? ? "Yes" : "No" }
if can_edit_key_contact?(@user, current_user)
row.action(visually_hidden_text: "are #{pronoun(@user, current_user)} a key contact?", href: aliased_user_edit(@user, current_user), html_attributes: { "data-qa": "change-are-#{pronoun(@user, current_user)}-a-key-contact" })
else
row.action()
end
end %>
row.key { "Key contact" }
row.value { @user.is_key_contact? ? "Yes" : "No" }
if can_edit_key_contact?(@user, current_user)
row.action(
visually_hidden_text: "are #{pronoun(@user, current_user)} a key contact?",
href: aliased_user_edit(@user, current_user),
html_attributes: { "data-qa": "change-are-#{pronoun(@user, current_user)}-a-key-contact" },
)
else
row.action
end
end %>
<% end %>
</div>
</div>

350
config/forms/2021_2022.json

@ -22,7 +22,7 @@
"1": {
"value": "General needs"
},
"0": {
"2": {
"value": "Supported housing"
}
}
@ -90,12 +90,12 @@
}
},
"conditional_for": {
"intermediate_rent_product_name": [
"irproduct_other": [
5
]
}
},
"intermediate_rent_product_name": {
"irproduct_other": {
"check_answer_label": "Product name",
"header": "Name of rent product",
"type": "text"
@ -587,13 +587,13 @@
"type": "radio",
"answer_options": {
"1": {
"value": "Affordable rent basis"
"value": "Social rent basis"
},
"2": {
"value": "Intermediate rent basis"
"value": "Affordable rent basis"
},
"0": {
"value": "Social rent basis"
"4": {
"value": "Intermediate rent basis"
},
"divider": {
"value": true
@ -621,39 +621,39 @@
"hint_text": "",
"type": "radio",
"answer_options": {
"1": {
"13": {
"value": "Internal transfer",
"hint": "Excluding renewals of a fixed-term tenancy"
},
"10": {
"5": {
"value": "Previous tenant died with no succession"
},
"2": {
"9": {
"value": "Re-let to tenant who occupied same property as temporary accommodation"
},
"0": {
"14": {
"value": "Renewal of fixed-term tenancy"
},
"7": {
"value": "Tenant abandoned property"
},
"3": {
"19": {
"value": "Tenant involved in a succession downsize"
},
"6": {
"value": "Tenant moved to care home"
"8": {
"value": "Tenant moved to private sector or other accommodation"
},
"5": {
"12": {
"value": "Tenant moved to other social housing provider"
},
"4": {
"value": "Tenant moved to private sector or other accommodation"
"18": {
"value": "Tenant moved to care home"
},
"9": {
"value": "Tenant was evicted due to anti-social behaviour"
"6": {
"value": "Tenant abandoned property"
},
"8": {
"10": {
"value": "Tenant was evicted due to rent arrears"
},
"11": {
"value": "Tenant was evicted due to anti-social behaviour"
}
}
}
@ -675,14 +675,14 @@
"hint_text": "",
"type": "radio",
"answer_options": {
"15": {
"value": "First let of new-build property"
},
"16": {
"value": "First let of conversion, rehabilitation or acquired property"
},
"17": {
"value": "First let of leased property"
},
"15": {
"value": "First let of new-build property"
}
}
}
@ -748,28 +748,28 @@
"hint_text": "",
"type": "radio",
"answer_options": {
"1": {
"2": {
"value": "Bedsit"
},
"3": {
"8": {
"value": "Bungalow"
},
"0": {
"1": {
"value": "Flat or maisonette"
},
"2": {
"7": {
"value": "House"
},
"6": {
"10": {
"value": "Shared bungalow"
},
"4": {
"value": "Shared flat or maisonette"
},
"5": {
"9": {
"value": "Shared house"
},
"7": {
"6": {
"value": "Other"
}
}
@ -786,10 +786,10 @@
"hint_text": "",
"type": "radio",
"answer_options": {
"1": {
"2": {
"value": "Converted from previous residential or non-residential property"
},
"0": {
"1": {
"value": "Purpose built"
}
}
@ -806,10 +806,10 @@
"hint_text": "",
"type": "radio",
"answer_options": {
"0": {
"1": {
"value": "Yes"
},
"1": {
"2": {
"value": "No"
}
}
@ -841,7 +841,7 @@
"header": "",
"description": "",
"questions": {
"property_void_date": {
"voiddate": {
"check_answer_label": "Void or renewal date",
"header": "What is the void or renewal date?",
"hint_text": "For example, 27 3 2021.",
@ -863,7 +863,7 @@
"header": "",
"description": "",
"questions": {
"property_void_date": {
"voiddate": {
"check_answer_label": "New-build handover date",
"header": "What is the new-build handover date?",
"hint_text": "",
@ -938,10 +938,10 @@
"hint_text": "",
"type": "radio",
"answer_options": {
"0": {
"1": {
"value": "Yes"
},
"1": {
"2": {
"value": "No"
}
}
@ -958,25 +958,25 @@
"hint_text": "",
"type": "radio",
"answer_options": {
"0": {
"2": {
"value": "Assured"
},
"1": {
"4": {
"value": "Assured Shorthold"
},
"2": {
"5": {
"value": "Licence agreement (almshouses only)"
},
"3": {
"1": {
"value": "Secure (including flexible)"
},
"4": {
"3": {
"value": "Other"
}
},
"conditional_for": {
"tenancyother": [
4
3
]
}
},
@ -1002,25 +1002,25 @@
"hint_text": "This is also known as an ‘introductory period’.",
"type": "radio",
"answer_options": {
"0": {
"2": {
"value": "Assured"
},
"1": {
"4": {
"value": "Assured Shorthold"
},
"2": {
"5": {
"value": "Licence agreement (almshouses only)"
},
"3": {
"1": {
"value": "Secure (including flexible)"
},
"4": {
"3": {
"value": "Other"
}
},
"conditional_for": {
"tenancyother": [
4
3
]
}
},
@ -1053,10 +1053,10 @@
},
"depends_on": [
{
"tenancy": 3
"tenancy": 1
},
{
"tenancy": 1
"tenancy": 4
}
]
},
@ -1064,25 +1064,25 @@
"header": "",
"description": "",
"questions": {
"letting_in_sheltered_accommodation": {
"shelteredaccom": {
"check_answer_label": "Is this letting in sheltered accommodation?",
"header": "Is this letting in sheltered accommodation?",
"hint_text": "",
"type": "radio",
"answer_options": {
"0": {
"1": {
"value": "Yes – sheltered housing"
},
"1": {
"2": {
"value": "Yes – extra care housing"
},
"2": {
"3": {
"value": "No"
},
"divider": {
"value": true
},
"3": {
"4": {
"value": "Don’t know"
}
}
@ -1090,7 +1090,7 @@
},
"depends_on": [
{
"needstype": 0
"needstype": 2
}
]
}
@ -1243,7 +1243,7 @@
"divider": {
"value": true
},
"5": {
"17": {
"value": "Tenant prefers not to say"
}
}
@ -1260,16 +1260,16 @@
"hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.",
"type": "radio",
"answer_options": {
"0": {
"19": {
"value": "Arab"
},
"1": {
"16": {
"value": "Other ethnic group"
}
},
"conditional_for": {
"ethnic_other": [
1
16
]
}
},
@ -1295,25 +1295,25 @@
"hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.",
"type": "radio",
"answer_options": {
"0": {
"10": {
"value": "Bangladeshi"
},
"1": {
"15": {
"value": "Chinese"
},
"2": {
"8": {
"value": "Indian"
},
"3": {
"9": {
"value": "Pakistani"
},
"4": {
"11": {
"value": "Other ethnic group"
}
},
"conditional_for": {
"ethnic_other": [
4
11
]
}
},
@ -1339,13 +1339,13 @@
"hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.",
"type": "radio",
"answer_options": {
"0": {
"13": {
"value": "African"
},
"1": {
"12": {
"value": "Caribbean"
},
"2": {
"14": {
"value": "Any other Black, African or Caribbean background"
}
},
@ -1377,19 +1377,22 @@
"hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.",
"type": "radio",
"answer_options": {
"0": {
"4": {
"value": "White and Black Caribbean"
},
"1": {
"5": {
"value": "White and Black African"
},
"2": {
"6": {
"value": "White and Asian"
},
"7": {
"value": "Any other Mixed or Multiple ethnic background"
}
},
"conditional_for": {
"ethnic_other": [
2
7
]
}
},
@ -1415,17 +1418,14 @@
"hint_text": "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest.",
"type": "radio",
"answer_options": {
"0": {
"1": {
"value": "English, Welsh, Northern Irish, Scottish or British"
},
"1": {
"2": {
"value": "Irish"
},
"2": {
"18": {
"value": "Gypsy or Irish Traveller"
},
"3": {
"value": "Any other White background"
}
},
"conditional_for": {
@ -1579,25 +1579,25 @@
},
"depends_on": [
{
"other_hhmemb": 1
"hhmemb": 2
},
{
"other_hhmemb": 2
"hhmemb": 3
},
{
"other_hhmemb": 3
"hhmemb": 4
},
{
"other_hhmemb": 4
"hhmemb": 5
},
{
"other_hhmemb": 5
"hhmemb": 6
},
{
"other_hhmemb": 6
"hhmemb": 7
},
{
"other_hhmemb": 7
"hhmemb": 8
}
]
},
@ -1792,22 +1792,22 @@
},
"depends_on": [
{
"other_hhmemb": 2
"hhmemb": 3
},
{
"other_hhmemb": 3
"hhmemb": 4
},
{
"other_hhmemb": 4
"hhmemb": 5
},
{
"other_hhmemb": 5
"hhmemb": 6
},
{
"other_hhmemb": 6
"hhmemb": 7
},
{
"other_hhmemb": 7
"hhmemb": 8
}
]
},
@ -2002,19 +2002,19 @@
},
"depends_on": [
{
"other_hhmemb": 3
"hhmemb": 4
},
{
"other_hhmemb": 4
"hhmemb": 5
},
{
"other_hhmemb": 5
"hhmemb": 6
},
{
"other_hhmemb": 6
"hhmemb": 7
},
{
"other_hhmemb": 7
"hhmemb": 8
}
]
},
@ -2209,16 +2209,16 @@
},
"depends_on": [
{
"other_hhmemb": 4
"hhmemb": 5
},
{
"other_hhmemb": 5
"hhmemb": 6
},
{
"other_hhmemb": 6
"hhmemb": 7
},
{
"other_hhmemb": 7
"hhmemb": 8
}
]
},
@ -2413,13 +2413,13 @@
},
"depends_on": [
{
"other_hhmemb": 5
"hhmemb": 6
},
{
"other_hhmemb": 6
"hhmemb": 7
},
{
"other_hhmemb": 7
"hhmemb": 8
}
]
},
@ -2614,10 +2614,10 @@
},
"depends_on": [
{
"other_hhmemb": 6
"hhmemb": 7
},
{
"other_hhmemb": 7
"hhmemb": 8
}
]
},
@ -2812,7 +2812,7 @@
},
"depends_on": [
{
"other_hhmemb": 7
"hhmemb": 8
}
]
},
@ -3007,22 +3007,22 @@
"type": "radio",
"check_answer_label": "Household links to UK armed forces",
"answer_options": {
"0": {
"1": {
"value": "Yes – the person is a current or former regular"
},
"1": {
"4": {
"value": "Yes – the person is a current or former reserve"
},
"2": {
"5": {
"value": "Yes – the person is a spouse or civil partner of a UK armed forces member and has been bereaved or separated from them within the last 2 years"
},
"3": {
"2": {
"value": "No"
},
"divider": {
"value": true
},
"4": {
"3": {
"value": "Person prefers not to say"
}
}
@ -3034,7 +3034,7 @@
"description": "",
"depends_on": [
{
"armedforces": 0
"armedforces": 1
}
],
"questions": {
@ -3068,10 +3068,10 @@
"description": "",
"depends_on": [
{
"armedforces": 0
"armedforces": 1
},
{
"armedforces": 1
"armedforces": 4
}
],
"questions": {
@ -3107,16 +3107,16 @@
"type": "radio",
"check_answer_label": "Anybody in household pregnant",
"answer_options": {
"0": {
"1": {
"value": "Yes"
},
"1": {
"2": {
"value": "No"
},
"divider": {
"value": true
},
"2": {
"3": {
"value": "Tenant prefers not to say"
}
}
@ -3168,16 +3168,16 @@
"type": "radio",
"check_answer_label": "Anybody in household with physical or mental health condition",
"answer_options": {
"0": {
"1": {
"value": "Yes"
},
"1": {
"2": {
"value": "No"
},
"divider": {
"value": true
},
"2": {
"3": {
"value": "Tenant prefers not to say"
}
}
@ -3189,7 +3189,7 @@
"description": "",
"depends_on": [
{
"illness": 0
"illness": 1
}
],
"questions": {
@ -3299,37 +3299,37 @@
"header": "",
"description": "",
"questions": {
"lawaitlist": {
"waityear": {
"check_answer_label": "Length of time on local authority waiting list",
"header": "How long has the household been on the local authority waiting list for the new letting?",
"hint_text": "",
"type": "radio",
"answer_options": {
"0": {
"1": {
"value": "Just moved to local authority area"
},
"1": {
"2": {
"value": "Less than 1 year"
},
"2": {
"7": {
"value": "1 year but under 2 years"
},
"3": {
"8": {
"value": "2 years but under 3 years"
},
"4": {
"9": {
"value": "3 years but under 4 years"
},
"5": {
"10": {
"value": "4 years but under 5 years"
},
"6": {
"5": {
"value": "5 years or more"
},
"divider": {
"value": true
},
"7": {
"6": {
"value": "Don’t know"
}
}
@ -3452,18 +3452,15 @@
},
"28": {
"value": "Don’t know"
},
"10000": {
"value": "Tenant prefers not to say"
}
},
"conditional_for": {
"other_reason_for_leaving_last_settled_home": [
31
"reasonother": [
20
]
}
},
"other_reason_for_leaving_last_settled_home": {
"reasonother": {
"header": "What is the reason?",
"hint_text": "",
"type": "text"
@ -3610,7 +3607,7 @@
"depends_on": [
{
"renewal": 1,
"needstype": 0
"needstype": 2
}
]
},
@ -4406,7 +4403,7 @@
"depends_on": [
{
"managing_organisation.provider_type": "LA",
"needstype": 0,
"needstype": 2,
"renewal": 0
}
]
@ -4466,7 +4463,7 @@
"depends_on": [
{
"managing_organisation.provider_type": "PRP",
"needstype": 0,
"needstype": 2,
"renewal": 0
}
]
@ -4535,19 +4532,19 @@
{
"label": "every week",
"depends_on": {
"incfreq": 0
"incfreq": 1
}
},
{
"label": "every month",
"depends_on": {
"incfreq": 1
"incfreq": 2
}
},
{
"label": "every month",
"label": "every year",
"depends_on": {
"incfreq": 2
"incfreq": 3
}
}
]
@ -4558,13 +4555,13 @@
"hint_text": "",
"type": "radio",
"answer_options": {
"0": {
"1": {
"value": "Weekly"
},
"1": {
"2": {
"value": "Monthly"
},
"2": {
"3": {
"value": "Yearly"
}
},
@ -4626,9 +4623,6 @@
},
"3": {
"value": "Don’t know"
},
"6": {
"value": "Tenant prefers not to say"
}
}
}
@ -4644,19 +4638,19 @@
"hint_text": "This excludes child and housing benefit, council tax support and tax credits.",
"type": "radio",
"answer_options": {
"0": {
"1": {
"value": "All"
},
"1": {
"2": {
"value": "Some"
},
"2": {
"3": {
"value": "None"
},
"divider": {
"value": true
},
"3": {
"4": {
"value": "Don’t know"
}
}
@ -4684,7 +4678,7 @@
},
"depends_on": [
{
"needstype": 0
"needstype": 2
}
]
},
@ -4774,62 +4768,62 @@
"depends_on": [
{
"period": 1,
"needstype": 0,
"needstype": 2,
"household_charge": 0
},
{
"period": 1,
"needstype": 0,
"needstype": 2,
"household_charge": null
},
{
"period": 5,
"needstype": 0,
"needstype": 2,
"household_charge": 0
},
{
"period": 5,
"needstype": 0,
"needstype": 2,
"household_charge": null
},
{
"period": 6,
"needstype": 0,
"needstype": 2,
"household_charge": 0
},
{
"period": 6,
"needstype": 0,
"needstype": 2,
"household_charge": null
},
{
"period": 7,
"needstype": 0,
"needstype": 2,
"household_charge": 0
},
{
"period": 7,
"needstype": 0,
"needstype": 2,
"household_charge": null
},
{
"period": 8,
"needstype": 0,
"needstype": 2,
"household_charge": 0
},
{
"period": 8,
"needstype": 0,
"needstype": 2,
"household_charge": null
},
{
"period": 9,
"needstype": 0,
"needstype": 2,
"household_charge": 0
},
{
"period": 9,
"needstype": 0,
"needstype": 2,
"household_charge": null
}
]
@ -4871,12 +4865,12 @@
"depends_on": [
{
"period": 2,
"needstype": 0,
"needstype": 2,
"household_charge": 0
},
{
"period": 2,
"needstype": 0,
"needstype": 2,
"household_charge": null
}
]
@ -4918,12 +4912,12 @@
"depends_on": [
{
"period": 3,
"needstype": 0,
"needstype": 2,
"household_charge": 0
},
{
"period": 3,
"needstype": 0,
"needstype": 2,
"household_charge": null
}
]
@ -4965,12 +4959,12 @@
"depends_on": [
{
"period": 4,
"needstype": 0,
"needstype": 2,
"household_charge": 0
},
{
"period": 4,
"needstype": 0,
"needstype": 2,
"household_charge": null
}
]

44
config/locales/en.yml

@ -93,34 +93,34 @@ en:
benefits:
part_or_full_time: "Answer cannot be 'all' for income from Universal Credit, state pensions or benefits if the tenant or their partner works part-time or full-time"
earnings:
over_hard_max: "Net income cannot be greater than %{hard_max} given the tenant’s working situation"
under_hard_min: "Net income cannot be less than %{hard_min} given the tenant’s working situation"
over_hard_max: "Net income cannot be greater than £%{hard_max} per week given the tenant’s working situation"
under_hard_min: "Net income cannot be less than £%{hard_min} per week given the tenant’s working situation"
freq_missing: "Select how often the household receives income"
earnings_missing: "Enter how much income the household has in total"
negative_currency: "Enter an amount above 0"
rent:
less_than_double_shortfall: "Answer must be more than double the shortfall in basic rent"
scharge:
this_landlord:
general_needs: "Service charge must be between £0 and £55 per week if the landlord is the same and it is a general needs letting"
supported_housing: "Service charge must be between £0 and £280 per week if the landlord is the same and it is a supported housing letting"
other_landlord:
general_needs: "Service charge must be between £0 and £45 per week if the landlord is another registered provider and it is a general needs letting"
supported_housing: "Service charge must be between £0 and £165 per week if the landlord is another registered provider and it is a supported housing letting"
private_registered_provider:
general_needs: "Service charge must be between £0 and £55 per week if the landlord is a private registered provider and it is a general needs letting"
supported_housing: "Service charge must be between £0 and £280 per week if the landlord is a private registered provider and it is a supported housing letting"
local_authority:
general_needs: "Service charge must be between £0 and £45 per week if the landlord is a local authority and it is a general needs letting"
supported_housing: "Service charge must be between £0 and £165 per week if the landlord is a local authority and it is a supported housing letting"
pscharge:
this_landlord:
general_needs: "Personal service charge must be between £0 and £30 per week if the landlord is the same and it is a general needs letting"
supported_housing: "Personal service charge must be between £0 and £200 per week if the landlord is the same and it is a supported housing letting"
other_landlord:
general_needs: "Personal service charge must be between £0 and £35 per week if the landlord is another registered provider and it is a general needs letting"
supported_housing: "Personal service charge must be between £0 and £75 per week if the landlord is another registered provider and it is a supported housing letting"
private_registered_provider:
general_needs: "Personal service charge must be between £0 and £30 per week if the landlord is a private registered provider and it is a general needs letting"
supported_housing: "Personal service charge must be between £0 and £200 per week if the landlord is a private registered provider and it is a supported housing letting"
local_authority:
general_needs: "Personal service charge must be between £0 and £35 per week if the landlord is a local authority and it is a general needs letting"
supported_housing: "Personal service charge must be between £0 and £75 per week if the landlord is a local authority and it is a supported housing letting"
supcharg:
this_landlord:
general_needs: "Support charge must be between £0 and £40 per week if the landlord is the same and it is a general needs letting"
supported_housing: "Support charge must be between £0 and £465 per week if the landlord is the same and it is a supported housing letting"
other_landlord:
general_needs: "Support charge must be between £0 and £60 per week if the landlord is another registered provider and it is a general needs letting"
supported_housing: "Support charge must be between £0 and £120 per week if the landlord is another registered provider and it is a supported housing letting"
private_registered_provider:
general_needs: "Support charge must be between £0 and £40 per week if the landlord is a private registered provider and it is a general needs letting"
supported_housing: "Support charge must be between £0 and £465 per week if the landlord is a private registered provider and it is a supported housing letting"
local_authority:
general_needs: "Support charge must be between £0 and £60 per week if the landlord is a local authority and it is a general needs letting"
supported_housing: "Support charge must be between £0 and £120 per week if the landlord is a local authority and it is a supported housing letting"
brent:
not_in_range: "Basic rent is outside of the expected range based on the lettings type, local authority and number of bedrooms"
la:
@ -183,7 +183,7 @@ en:
male_refuge: "Answer cannot be refuge as the lead tenant identifies as male"
internal_transfer: "Answer cannot be %{prevten} as you already told us this tenancy is an internal transfer"
la_general_needs:
internal_transfer: "Answer cannot be a fixed-term or lifetime local authority general needs tenancy as you already told us it's the same landlord on the tenancy agreement and it is an internal transfer"
internal_transfer: "Answer cannot be a fixed-term or lifetime local authority general needs tenancy as you already told us it's a private registered provider on the tenancy agreement and it is an internal transfer"
referral:
secure_tenancy: "Answer must be internal transfer as you already told us this is a secure tenancy"
rsnvac_non_temp: "Answer cannot be this source of referral as you already told us this is a re-let to tenant who occupied the same property as temporary accommodation"
@ -195,7 +195,7 @@ en:
la_general_needs:
internal_transfer: "Answer cannot be internal transfer as you already told us it's the same landlord on the tenancy agreement and the household had either a fixed-term or lifetime local authority general needs tenancy immediately before this letting"
prp:
local_housing_referral: "Answer cannot be 'nominated by a local housing authority' as you already told us it's another landlord on the tenancy agreement"
local_housing_referral: "Answer cannot be 'nominated by a local housing authority' as you already told us it is a local authority on the tenancy agreement"
homeless:
assessed:
internal_transfer: "Answer cannot be assessed as homeless as you already told us this tenancy is an internal transfer"

0
db/migrate/202202071123100_additional_user_fields2.rb → db/migrate/20220207112310_additional_user_fields2.rb

13
db/migrate/20220411092231_update_case_logs_fields.rb

@ -0,0 +1,13 @@
class UpdateCaseLogsFields < ActiveRecord::Migration[7.0]
def change
change_table :case_logs, bulk: true do |t|
t.integer :old_form_id, :lar, :irproduct
t.remove :day, :month, :year, :vday, :vmonth, :vyear, :mrcday, :mrcmonth, :mrcyear, :other_hhmemb, :accessibility_requirements_prefer_not_to_say, :landlord, type: :integer
t.remove :ppostc1, :ppostc2, :postcode, :postcod2, type: :string
t.rename :intermediate_rent_product_name, :irproduct_other
t.rename :lawaitlist, :waityear
t.rename :other_reason_for_leaving_last_settled_home, :reasonother
t.rename :property_void_date, :voiddate
end
end
end

30
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: 202202071123100) do
ActiveRecord::Schema[7.0].define(version: 2022_04_11_092231) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -86,7 +86,6 @@ ActiveRecord::Schema[7.0].define(version: 202202071123100) do
t.integer "startertenancy"
t.integer "tenancylength"
t.integer "tenancy"
t.integer "landlord"
t.string "ppostcode_full"
t.integer "rsnvac"
t.integer "unittype_gn"
@ -98,20 +97,19 @@ ActiveRecord::Schema[7.0].define(version: 202202071123100) do
t.integer "benefits"
t.integer "period"
t.integer "layear"
t.integer "lawaitlist"
t.integer "waityear"
t.string "postcode_full"
t.integer "reasonpref"
t.integer "cbl"
t.integer "chr"
t.integer "cap"
t.string "other_reason_for_leaving_last_settled_home"
t.string "reasonother"
t.integer "housingneeds_a"
t.integer "housingneeds_b"
t.integer "housingneeds_c"
t.integer "housingneeds_f"
t.integer "housingneeds_g"
t.integer "housingneeds_h"
t.integer "accessibility_requirements_prefer_not_to_say"
t.integer "illness_type_1"
t.integer "illness_type_2"
t.integer "illness_type_3"
@ -132,7 +130,7 @@ ActiveRecord::Schema[7.0].define(version: 202202071123100) do
t.string "property_owner_organisation"
t.string "property_manager_organisation"
t.string "sale_or_letting"
t.string "intermediate_rent_product_name"
t.string "irproduct_other"
t.string "purchaser_code"
t.integer "reason"
t.string "propcode"
@ -141,16 +139,8 @@ ActiveRecord::Schema[7.0].define(version: 202202071123100) do
t.string "prevloc"
t.integer "hb"
t.integer "hbrentshortfall"
t.string "postcode"
t.string "postcod2"
t.string "ppostc1"
t.string "ppostc2"
t.integer "property_relet"
t.datetime "mrcdate", precision: nil
t.integer "mrcday"
t.integer "mrcmonth"
t.integer "mrcyear"
t.integer "other_hhmemb"
t.integer "incref"
t.datetime "sale_completion_date", precision: nil
t.datetime "startdate", precision: nil
@ -158,7 +148,7 @@ ActiveRecord::Schema[7.0].define(version: 202202071123100) do
t.integer "first_time_property_let_as_social_housing"
t.integer "unitletas"
t.integer "builtype"
t.datetime "property_void_date", precision: nil
t.datetime "voiddate", precision: nil
t.bigint "owning_organisation_id"
t.bigint "managing_organisation_id"
t.integer "renttype"
@ -167,9 +157,6 @@ ActiveRecord::Schema[7.0].define(version: 202202071123100) do
t.integer "postcode_known"
t.integer "la_known"
t.boolean "is_la_inferred"
t.integer "day"
t.integer "month"
t.integer "year"
t.integer "totchild"
t.integer "totelder"
t.integer "totadult"
@ -219,9 +206,6 @@ ActiveRecord::Schema[7.0].define(version: 202202071123100) do
t.decimal "wtshortfall", precision: 10, scale: 2
t.integer "refused"
t.integer "housingneeds"
t.integer "vday"
t.integer "vmonth"
t.integer "vyear"
t.decimal "wchchrg", precision: 10, scale: 2
t.integer "newprop"
t.string "relat2"
@ -232,6 +216,9 @@ ActiveRecord::Schema[7.0].define(version: 202202071123100) do
t.string "relat7"
t.string "relat8"
t.integer "rent_value_check"
t.integer "old_form_id"
t.integer "lar"
t.integer "irproduct"
t.index ["managing_organisation_id"], name: "index_case_logs_on_managing_organisation_id"
t.index ["owning_organisation_id"], name: "index_case_logs_on_owning_organisation_id"
end
@ -318,6 +305,7 @@ ActiveRecord::Schema[7.0].define(version: 202202071123100) do
t.string "last_sign_in_ip"
t.integer "role"
t.string "old_user_id"
t.string "phone"
t.integer "failed_attempts", default: 0
t.string "unlock_token"
t.datetime "locked_at", precision: nil

4
docs/api/DLUHC-CORE-Data.v1.json

@ -361,7 +361,7 @@
"cap": 0,
"hbrentshortfall": 0,
"tshortfall": 12,
"other_reason_for_leaving_last_settled_home": null,
"reasonother": null,
"housingneeds_a": 1,
"housingneeds_b": 0,
"housingneeds_c": 0,
@ -940,4 +940,4 @@
}
}
}
}
}

19
spec/components/document_list_component_spec.rb

@ -0,0 +1,19 @@
require "rails_helper"
RSpec.describe DocumentListComponent, type: :component do
let(:items) do
[{ name: "PDF Form", href: "/forms/form.pdf", description: "An important form", metadata: "4 pages" },
{ name: "Website", href: "https://example.com" }]
end
context "when rendering tabs" do
it "all of the nav tabs specified in the items hash are passed to it" do
result = render_inline(described_class.new(items:))
expect(result.text).to include("PDF Form")
expect(result.text).to include("An important form")
expect(result.text).to include("4 pages")
expect(result.text).to include("Website")
end
end
end

27
spec/components/primary_navigation_component_spec.rb

@ -0,0 +1,27 @@
require "rails_helper"
RSpec.describe PrimaryNavigationComponent, type: :component do
let(:items) do
[{ name: "Organisations", url: "#", current: true },
{ name: "Users", url: "#" },
{ name: "Logs ", url: "#" }]
end
context "when the item is 'current' in nav tabs" do
it "then that tab appears as selected" do
result = render_inline(described_class.new(items:))
expect(result.css('.app-primary-navigation__link[aria-current="page"]').text).to include("Organisations")
end
end
context "when rendering tabs" do
it "all of the nav tabs specified in the items hash are passed to it" do
result = render_inline(described_class.new(items:))
expect(result.text).to include("Organisations")
expect(result.text).to include("Users")
expect(result.text).to include("Logs")
end
end
end

27
spec/components/tab_navigation_component_spec.rb

@ -1,27 +0,0 @@
require "rails_helper"
RSpec.describe TabNavigationComponent, type: :component do
let(:items) do
[{ name: "Application", url: "#", current: true },
{ name: "Notes", url: "#" },
{ name: "Timeline", url: "#" }]
end
context "when the item is 'current' in nav tabs" do
it "then that tab appears as selected" do
result = render_inline(described_class.new(items:))
expect(result.css('.app-tab-navigation__link[aria-current="page"]').text).to include("Application")
end
end
context "when rendering tabs" do
it "all of the nav tabs specified in the items hash are passed to it" do
result = render_inline(described_class.new(items:))
expect(result.text).to include("Application")
expect(result.text).to include("Notes")
expect(result.text).to include("Timeline")
end
end
end

4
spec/controllers/admin/admin_users_controller_spec.rb

@ -28,7 +28,7 @@ describe Admin::AdminUsersController, type: :controller do
let(:params) { { admin_user: { email: "test2@example.com", password: "pAssword1", phone: "07566126368" } } }
it "creates a new admin user" do
expect { post :create, session: valid_session, params: params }.to change(AdminUser, :count).by(1)
expect { post :create, session: valid_session, params: }.to change(AdminUser, :count).by(1)
end
it "tracks who created the record" do
@ -59,7 +59,7 @@ describe Admin::AdminUsersController, type: :controller do
let(:params) { { id: admin_user.id, admin_user: { email: } } }
before do
patch :update, session: valid_session, params: params
patch :update, session: valid_session, params:
end
it "updates the user without needing to input a password" do

4
spec/controllers/admin/case_logs_controller_spec.rb

@ -40,7 +40,7 @@ describe Admin::CaseLogsController, type: :controller do
end
it "creates a new case log" do
expect { post :create, session: valid_session, params: params }.to change(CaseLog, :count).by(1)
expect { post :create, session: valid_session, params: }.to change(CaseLog, :count).by(1)
end
it "tracks who created the record" do
@ -71,7 +71,7 @@ describe Admin::CaseLogsController, type: :controller do
let(:params) { { id: case_log.id, case_log: { tenant_code: } } }
before do
patch :update, session: valid_session, params: params
patch :update, session: valid_session, params:
end
it "updates the case log" do

4
spec/controllers/admin/organisations_controller_spec.rb

@ -29,7 +29,7 @@ describe Admin::OrganisationsController, type: :controller do
let(:params) { { organisation: { name: "DLUHC", provider_type: "LA" } } }
it "creates a organisation" do
expect { post :create, session: valid_session, params: params }.to change(Organisation, :count).by(1)
expect { post :create, session: valid_session, params: }.to change(Organisation, :count).by(1)
end
it "tracks who created the record" do
@ -59,7 +59,7 @@ describe Admin::OrganisationsController, type: :controller do
let(:params) { { id: organisation.id, organisation: { name: } } }
before do
patch :update, session: valid_session, params: params
patch :update, session: valid_session, params:
end
it "updates the organisation" do

4
spec/controllers/admin/users_controller_spec.rb

@ -40,7 +40,7 @@ describe Admin::UsersController, type: :controller do
end
it "creates a new user" do
expect { post :create, session: valid_session, params: params }.to change(User, :count).by(1)
expect { post :create, session: valid_session, params: }.to change(User, :count).by(1)
end
it "tracks who created the record" do
@ -73,7 +73,7 @@ describe Admin::UsersController, type: :controller do
let(:params) { { id: user.id, user: { name: } } }
before do
patch :update, session: valid_session, params: params
patch :update, session: valid_session, params:
end
it "updates the user without needing to input a password" do

42
spec/factories/case_log.rb

@ -7,7 +7,6 @@ FactoryBot.define do
needstype { 1 }
rent_type { 1 }
startdate { Time.zone.local(2022, 5, 1) }
year { 2022 }
end
trait :in_progress do
status { 1 }
@ -21,7 +20,7 @@ FactoryBot.define do
status { 1 }
ecstat1 { 1 }
earnings { 750 }
incfreq { 0 }
incfreq { 1 }
end
trait :conditional_section_complete do
tenant_code { "TH356" }
@ -30,19 +29,17 @@ FactoryBot.define do
ethnic { 2 }
national { 4 }
ecstat1 { 2 }
other_hhmemb { 0 }
hhmemb { 1 }
end
trait :completed do
status { 2 }
tenant_code { "BZ737" }
postcode { "NW1 7TY" }
age1 { 35 }
sex1 { "F" }
ethnic { 2 }
national { 4 }
prevten { 6 }
ecstat1 { 0 }
other_hhmemb { 1 }
hhmemb { 2 }
relat2 { "P" }
age2 { 32 }
@ -52,25 +49,21 @@ FactoryBot.define do
underoccupation_benefitcap { 0 }
leftreg { 1 }
reservist { 0 }
illness { 0 }
preg_occ { 1 }
illness { 1 }
preg_occ { 2 }
tenancy_code { "BZ757" }
startertenancy { 0 }
tenancylength { 5 }
tenancy { 3 }
landlord { 1 }
tenancy { 1 }
ppostcode_full { "SE2 6RT" }
rsnvac { 7 }
unittype_gn { 2 }
rsnvac { 6 }
unittype_gn { 7 }
beds { 3 }
property_void_date { "03/11/2019" }
vday { 3 }
vmonth { 11 }
vyear { 2019 }
voiddate { "03/11/2019" }
offered { 2 }
wchair { 1 }
earnings { 68 }
incfreq { 0 }
incfreq { 1 }
benefits { 1 }
period { 2 }
brent { 200 }
@ -79,20 +72,19 @@ FactoryBot.define do
supcharg { 35 }
tcharge { 325 }
layear { 2 }
lawaitlist { 1 }
waityear { 1 }
postcode_full { "NW1 5TY" }
reasonpref { 1 }
cbl { 1 }
chr { 1 }
cap { 0 }
other_reason_for_leaving_last_settled_home { nil }
reasonother { nil }
housingneeds_a { 1 }
housingneeds_b { 0 }
housingneeds_c { 0 }
housingneeds_f { 0 }
housingneeds_g { 0 }
housingneeds_h { 0 }
accessibility_requirements_prefer_not_to_say { 0 }
illness_type_1 { 0 }
illness_type_2 { 1 }
illness_type_3 { 0 }
@ -115,7 +107,6 @@ FactoryBot.define do
property_manager_organisation { "Test" }
renewal { 0 }
rent_type { 1 }
intermediate_rent_product_name { 2 }
needstype { 1 }
purchaser_code { 798_794 }
reason { 4 }
@ -126,21 +117,12 @@ FactoryBot.define do
hb { 6 }
hbrentshortfall { 0 }
tshortfall { 12 }
postcod2 { "w3" }
ppostc1 { "w3" }
ppostc2 { "w3" }
property_relet { 0 }
mrcdate { Time.utc(2020, 5, 0o5, 10, 36, 49) }
mrcday { mrcdate.day }
mrcmonth { mrcdate.month }
mrcyear { mrcdate.year }
incref { 0 }
sale_completion_date { nil }
startdate { Time.utc(2022, 2, 2, 10, 36, 49) }
day { startdate.day }
month { startdate.month }
year { startdate.year }
armedforces { 0 }
armedforces { 1 }
builtype { 1 }
unitletas { 2 }
has_benefits { 1 }

16
spec/features/form/check_answers_page_spec.rb

@ -53,7 +53,7 @@ RSpec.describe "Form Check Answers Page" do
it "has question headings based on the subsection" do
visit("/logs/#{id}/#{subsection}/check-answers")
question_labels = ["Tenant code", "Lead tenant’s age", "Lead tenant’s gender identity", "Number of Other Household Members"]
question_labels = ["Tenant code", "Lead tenant’s age", "Lead tenant’s gender identity", "Number of Household Members"]
question_labels.each do |label|
expect(page).to have_content(label)
end
@ -113,7 +113,7 @@ RSpec.describe "Form Check Answers Page" do
it "displays conditional question that were visited" do
visit("/logs/#{id}/conditional-question")
choose("case-log-preg-occ-1-field", allow_label_click: true)
choose("case-log-preg-occ-2-field", allow_label_click: true)
click_button("Save and continue")
visit("/logs/#{id}/#{conditional_subsection}/check-answers")
question_labels = ["Has the condition been met?", "Has the condition not been met?"]
@ -151,7 +151,7 @@ RSpec.describe "Form Check Answers Page" do
tenant_code: "123",
age1: 35,
sex1: "M",
other_hhmemb: 0,
hhmemb: 1,
)
end
@ -164,9 +164,9 @@ RSpec.describe "Form Check Answers Page" do
tenant_code: "123",
age1: 35,
sex1: "M",
other_hhmemb: 0,
hhmemb: 1,
armedforces: 3,
illness: 0,
illness: 1,
)
end
@ -179,9 +179,9 @@ RSpec.describe "Form Check Answers Page" do
tenant_code: "123",
age1: 35,
sex1: "M",
other_hhmemb: 0,
hhmemb: 1,
armedforces: 3,
illness: 0,
illness: 1,
housingneeds_h: 1,
la: "E06000014",
illness_type_1: 1,
@ -197,7 +197,7 @@ RSpec.describe "Form Check Answers Page" do
tenant_code: nil,
age1: nil,
layear: 2,
lawaitlist: 1,
waityear: 1,
postcode_full: "NW1 5TY",
reason: 4,
ppostcode_full: "SE2 6RT",

6
spec/features/form/conditional_questions_spec.rb

@ -27,11 +27,11 @@ RSpec.describe "Form Conditional Questions" do
it "shows conditional questions if the required answer is selected and hides it again when a different answer option is selected", js: true do
visit("/logs/#{id}/armed-forces")
# Something about our styling makes the selenium webdriver think the actual radio buttons are not visible so we allow label click here
choose("case-log-armedforces-0-field", allow_label_click: true)
fill_in("case-log-leftreg-field", with: "text")
choose("case-log-armedforces-1-field", allow_label_click: true)
fill_in("case-log-leftreg-field", with: "text")
choose("case-log-armedforces-4-field", allow_label_click: true)
expect(page).not_to have_field("case-log-leftreg-field")
choose("case-log-armedforces-0-field", allow_label_click: true)
choose("case-log-armedforces-1-field", allow_label_click: true)
expect(page).to have_field("case-log-leftreg-field", with: "")
end
end

4
spec/features/form/form_navigation_spec.rb

@ -19,7 +19,7 @@ RSpec.describe "Form Navigation" do
age1: { type: "numeric", answer: 25, path: "person-1-age" },
sex1: { type: "radio", answer: "Female", path: "person-1-gender" },
ecstat1: { type: "radio", answer: 3, path: "person-1-working-situation" },
other_hhmemb: { type: "numeric", answer: 2, path: "household-number-of-other-members" },
hhmemb: { type: "numeric", answer: 1, path: "household-number-of-members" },
}
end
@ -71,7 +71,7 @@ RSpec.describe "Form Navigation" do
visit("/logs")
visit("/logs/#{id}/net-income")
fill_in("case-log-earnings-field", with: 740)
choose("case-log-incfreq-0-field", allow_label_click: true)
choose("case-log-incfreq-1-field", allow_label_click: true)
click_button("Save and continue")
click_link(text: "Back")
click_link(text: "Back")

6
spec/features/form/page_routing_spec.rb

@ -24,12 +24,12 @@ RSpec.describe "Form Page Routing" do
visit("/logs/#{id}/conditional-question")
# using a question name that is already in the db to avoid
# having to add a new column to the db for this test
choose("case-log-preg-occ-0-field", allow_label_click: true)
choose("case-log-preg-occ-1-field", allow_label_click: true)
click_button("Save and continue")
expect(page).to have_current_path("/logs/#{id}/conditional-question-yes-page")
click_link(text: "Back")
expect(page).to have_current_path("/logs/#{id}/conditional-question")
choose("case-log-preg-occ-1-field", allow_label_click: true)
choose("case-log-preg-occ-2-field", allow_label_click: true)
click_button("Save and continue")
expect(page).to have_current_path("/logs/#{id}/conditional-question-no-page")
end
@ -40,7 +40,7 @@ RSpec.describe "Form Page Routing" do
click_button("Save and continue")
expect(page).to have_current_path("/logs/#{id}/person-1-working-situation")
visit("/logs/#{id}/conditional-question")
choose("case-log-preg-occ-1-field", allow_label_click: true)
choose("case-log-preg-occ-2-field", allow_label_click: true)
click_button("Save and continue")
expect(page).to have_current_path("/logs/#{id}/conditional-question-no-page")
click_button("Save and continue")

2
spec/features/form/saving_data_spec.rb

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

4
spec/features/form/validations_spec.rb

@ -127,7 +127,7 @@ RSpec.describe "validations" do
it "prompts the user to confirm the value is correct with an interruption screen" do
visit("/logs/#{case_log.id}/net-income")
fill_in("case-log-earnings-field", with: income_over_soft_limit)
choose("case-log-incfreq-0-field", allow_label_click: true)
choose("case-log-incfreq-1-field", allow_label_click: true)
click_button("Save and continue")
expect(page).to have_current_path("/logs/#{case_log.id}/net-income-value-check")
expect(page).to have_content("Net income is outside the expected range based on the lead tenant’s working situation")
@ -141,7 +141,7 @@ RSpec.describe "validations" do
it "returns the user to the previous question if they do not confirm the value as correct on the interruption screen" do
visit("/logs/#{case_log.id}/net-income")
fill_in("case-log-earnings-field", with: income_over_soft_limit)
choose("case-log-incfreq-0-field", allow_label_click: true)
choose("case-log-incfreq-1-field", allow_label_click: true)
click_button("Save and continue")
choose("case-log-net-income-value-check-1-field", allow_label_click: true)
click_button("Save and continue")

9
spec/features/organisation_spec.rb

@ -24,7 +24,7 @@ RSpec.describe "User Features" do
context "when viewing organisation page" do
it "defaults to organisation details" do
visit("/logs")
click_link("Your organisation")
click_link("About your organisation")
expect(page).to have_content(user.organisation.name)
end
@ -32,7 +32,7 @@ RSpec.describe "User Features" do
visit("/organisations/#{org_id}")
click_link("Users")
expect(page).to have_current_path("/organisations/#{org_id}/users")
click_link("Details")
click_link("About your organisation")
expect(page).to have_current_path("/organisations/#{org_id}/details")
end
end
@ -72,10 +72,11 @@ RSpec.describe "User Features" do
context "when viewing organisation page" do
it "can see the details tab and users tab" do
visit("/logs")
click_link("Your organisation")
click_link("About your organisation")
expect(page).to have_current_path("/organisations/#{org_id}/details")
expect(page).to have_link("Details")
expect(page).to have_link("Logs")
expect(page).to have_link("Users")
expect(page).to have_link("About your organisation")
end
end
end

8
spec/features/reset_password.html.erb

@ -2,7 +2,7 @@
<% content_for :before_content do %>
<%= govuk_back_link(
text: 'Back',
text: "Back",
href: :back,
) %>
<% end %>
@ -20,12 +20,10 @@
<%= f.govuk_password_field :password,
label: { text: "New password" },
hint: @minimum_password_length ? { text: "Your password must be at least #{@minimum_password_length} characters and hard to guess." } : nil,
autocomplete: "new-password"
%>
autocomplete: "new-password" %>
<%= f.govuk_password_field :password_confirmation,
label: { text: "Confirm new password" }
%>
label: { text: "Confirm new password" } %>
<%= f.govuk_submit "Update" %>
</div>

30
spec/fixtures/complete_case_log.json vendored

@ -6,10 +6,9 @@
"ethnic": 0,
"national": 0,
"prevten": 6,
"armedforces": 0,
"armedforces": 1,
"armed_forces_partner": "",
"ecstat1": 1,
"other_hhmemb": 7,
"hhmemb": 8,
"relat2": "P",
"age2": 32,
@ -43,39 +42,30 @@
"underoccupation_benefitcap": 0,
"leftreg": 1,
"reservist": 0,
"illness": 0,
"preg_occ": 0,
"illness": 1,
"preg_occ": 1,
"tenancy_code": "BZ757",
"startdate": "12/12/2021",
"day": 12,
"month": 12,
"year": 2021,
"startertenancy": 0,
"tenancylength": 5,
"tenancy": 3,
"tenancy": 1,
"landlord": 1,
"la": "Barnet",
"postcode_full": "NW1 5TY",
"property_relet": 0,
"rsnvac": 0,
"rsnvac": 14,
"property_reference": "P9876",
"unittype_gn": 2,
"unittype_gn": 7,
"property_building_type": "dummy",
"beds": 3,
"property_void_date": "10/10/2020",
"vday": 10,
"vmonth": 10,
"vyear": 2020,
"voiddate": "10/10/2020",
"majorrepairs": 1,
"mrcdate": "11/11/2020",
"mrcday": 11,
"mrcmonth": 11,
"mrcyear": 2020,
"offered": 2,
"wchair": 1,
"net_income_known": 1,
"earnings": 150,
"incfreq": 0,
"incfreq": 1,
"benefits": 1,
"hb": 1,
"period": 2,
@ -95,7 +85,7 @@
"cap": 0,
"hbrentshortfall": 0,
"tshortfall": 12,
"other_reason_for_leaving_last_settled_home": null,
"reasonother": null,
"housingneeds_a": 1,
"housingneeds_b": 0,
"housingneeds_c": 0,
@ -131,8 +121,6 @@
"propcode": "123",
"postcode": "a1",
"postcod2": "w3",
"ppostc1": "w3",
"ppostc2": "w3",
"first_time_property_let_as_social_housing": 0,
"unitletas": 1,
"builtype": 0,

41
spec/fixtures/exports/case_logs.xml vendored

@ -38,38 +38,36 @@
<underoccupation_benefitcap>0</underoccupation_benefitcap>
<leftreg>1</leftreg>
<reservist>0</reservist>
<illness>0</illness>
<preg_occ>1</preg_occ>
<illness>1</illness>
<preg_occ>2</preg_occ>
<tenancy_code>BZ757</tenancy_code>
<startertenancy>0</startertenancy>
<tenancylength>5</tenancylength>
<tenancy>3</tenancy>
<landlord>1</landlord>
<tenancy>1</tenancy>
<ppostcode_full>SE26RT</ppostcode_full>
<rsnvac>7</rsnvac>
<unittype_gn>2</unittype_gn>
<rsnvac>6</rsnvac>
<unittype_gn>7</unittype_gn>
<beds>3</beds>
<offered>2</offered>
<wchair>1</wchair>
<earnings>68</earnings>
<incfreq>0</incfreq>
<incfreq>1</incfreq>
<benefits>1</benefits>
<period>2</period>
<layear>2</layear>
<lawaitlist>1</lawaitlist>
<waityear>1</waityear>
<postcode_full>NW15TY</postcode_full>
<reasonpref>1</reasonpref>
<cbl>1</cbl>
<chr>1</chr>
<cap>0</cap>
<other_reason_for_leaving_last_settled_home/>
<reasonother/>
<housingneeds_a>1</housingneeds_a>
<housingneeds_b>0</housingneeds_b>
<housingneeds_c>0</housingneeds_c>
<housingneeds_f>0</housingneeds_f>
<housingneeds_g>0</housingneeds_g>
<housingneeds_h>0</housingneeds_h>
<accessibility_requirements_prefer_not_to_say>0</accessibility_requirements_prefer_not_to_say>
<illness_type_1>0</illness_type_1>
<illness_type_2>1</illness_type_2>
<illness_type_3>0</illness_type_3>
@ -90,7 +88,7 @@
<property_owner_organisation>Test</property_owner_organisation>
<property_manager_organisation>Test</property_manager_organisation>
<sale_or_letting/>
<intermediate_rent_product_name>2</intermediate_rent_product_name>
<irproduct_other/>
<purchaser_code>798794</purchaser_code>
<reason>4</reason>
<propcode>123</propcode>
@ -99,24 +97,16 @@
<prevloc>E07000105</prevloc>
<hb>6</hb>
<hbrentshortfall>0</hbrentshortfall>
<postcode>NW1</postcode>
<postcod2>5TY</postcod2>
<ppostc1>SE2</ppostc1>
<ppostc2>6RT</ppostc2>
<property_relet>0</property_relet>
<mrcdate>2020-05-05 10:36:49 UTC</mrcdate>
<mrcday>5</mrcday>
<mrcmonth>5</mrcmonth>
<mrcyear>2020</mrcyear>
<other_hhmemb>1</other_hhmemb>
<incref>0</incref>
<sale_completion_date/>
<startdate>2022-02-02 10:36:49 UTC</startdate>
<armedforces>0</armedforces>
<armedforces>1</armedforces>
<first_time_property_let_as_social_housing/>
<unitletas>2</unitletas>
<builtype>1</builtype>
<property_void_date>2019-11-03 00:00:00 UTC</property_void_date>
<voiddate>2019-11-03 00:00:00 UTC</voiddate>
<owning_organisation_id>{owning_org_id}</owning_organisation_id>
<managing_organisation_id>{managing_org_id}</managing_organisation_id>
<renttype>2</renttype>
@ -125,9 +115,6 @@
<postcode_known>1</postcode_known>
<la_known>1</la_known>
<is_la_inferred>false</is_la_inferred>
<day>2</day>
<month>2</month>
<year>2022</year>
<totchild>0</totchild>
<totelder>0</totelder>
<totadult>2</totadult>
@ -161,9 +148,6 @@
<wtshortfall>6.0</wtshortfall>
<refused>0</refused>
<housingneeds>1</housingneeds>
<vday>3</vday>
<vmonth>11</vmonth>
<vyear>2019</vyear>
<wchchrg/>
<newprop>2</newprop>
<relat2>P</relat2>
@ -174,6 +158,9 @@
<relat7/>
<relat8/>
<rent_value_check/>
<old_form_id/>
<lar/>
<irproduct/>
<providertype>1</providertype>
</form>
</forms>

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

Loading…
Cancel
Save