66 changed files with 4851 additions and 884 deletions
@ -1,10 +1,10 @@ |
|||||||
import GOVUKFrontend from 'govuk-frontend' |
import { initAll as GOVUKFrontend } from 'govuk-frontend' |
||||||
import GOVUKPrototypeComponents from 'govuk-prototype-components' |
import { initAll as GOVUKPrototypeComponents } from 'govuk-prototype-components' |
||||||
import { Controller } from '@hotwired/stimulus' |
import { Controller } from '@hotwired/stimulus' |
||||||
|
|
||||||
export default class extends Controller { |
export default class extends Controller { |
||||||
connect () { |
connect () { |
||||||
GOVUKFrontend.initAll() |
GOVUKFrontend() |
||||||
GOVUKPrototypeComponents.initAll() |
GOVUKPrototypeComponents() |
||||||
} |
} |
||||||
} |
} |
||||||
|
@ -1,134 +0,0 @@ |
|||||||
// https://github.com/alphagov/govuk-frontend/blob/add-pagination-prototype/src/govuk/components/pagination/_index.scss |
|
||||||
.app-pagination { |
|
||||||
border-top: 1px solid $govuk-border-colour; |
|
||||||
margin-top: govuk-spacing(2); |
|
||||||
padding-top: govuk-spacing(2); |
|
||||||
text-align: center; |
|
||||||
|
|
||||||
@include govuk-media-query($from: tablet) { |
|
||||||
// Hide whitespace between elements |
|
||||||
font-size: 0; |
|
||||||
|
|
||||||
// Trick to remove the need for floats |
|
||||||
text-align: justify; |
|
||||||
|
|
||||||
&:after { |
|
||||||
content: " "; |
|
||||||
display: inline-block; |
|
||||||
width: 100%; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
.app-pagination__list { |
|
||||||
margin: 0 govuk-spacing(-3); |
|
||||||
padding: 0; |
|
||||||
list-style: none; |
|
||||||
|
|
||||||
@include govuk-media-query($from: tablet) { |
|
||||||
display: inline-block; |
|
||||||
margin-bottom: 0; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
.app-pagination__results { |
|
||||||
@include govuk-font(19); |
|
||||||
margin-top: 0; |
|
||||||
margin-bottom: govuk-spacing(4); |
|
||||||
padding: govuk-spacing(1) 0; |
|
||||||
|
|
||||||
@include govuk-media-query($from: tablet) { |
|
||||||
display: inline-block; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
.app-pagination__item { |
|
||||||
@include govuk-font(19); |
|
||||||
display: inline-block; |
|
||||||
margin-bottom: govuk-spacing(4); |
|
||||||
|
|
||||||
// Hide items on small screens |
|
||||||
@include govuk-media-query($until: tablet) { |
|
||||||
display: none; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Only show previous, next, first, last and current items on mobile |
|
||||||
.app-pagination__item--current, |
|
||||||
.app-pagination__item--divider, |
|
||||||
.app-pagination__item--prev, |
|
||||||
.app-pagination__item--next, |
|
||||||
.app-pagination__item:nth-child(2), |
|
||||||
.app-pagination__item:nth-last-child(2) { |
|
||||||
@include govuk-media-query($until: tablet) { |
|
||||||
display: inline-block; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
.app-pagination__item--current, |
|
||||||
.app-pagination__item--divider { |
|
||||||
box-sizing: border-box; |
|
||||||
font-weight: bold; |
|
||||||
min-width: govuk-spacing(8); |
|
||||||
min-height: govuk-spacing(4); |
|
||||||
padding: govuk-spacing(2); |
|
||||||
text-align: center; |
|
||||||
} |
|
||||||
|
|
||||||
.app-pagination__item--divider { |
|
||||||
margin: 0 govuk-spacing(-4); |
|
||||||
padding-right: 0; |
|
||||||
padding-left: 0; |
|
||||||
color: $govuk-secondary-text-colour; |
|
||||||
pointer-events: none; |
|
||||||
} |
|
||||||
|
|
||||||
.app-pagination__link { |
|
||||||
@include govuk-link-common; |
|
||||||
@include govuk-link-style-no-underline; |
|
||||||
box-sizing: border-box; |
|
||||||
color: $govuk-link-colour; |
|
||||||
display: block; |
|
||||||
min-width: govuk-spacing(8); |
|
||||||
min-height: govuk-spacing(4); |
|
||||||
padding: govuk-spacing(2); |
|
||||||
text-align: center; |
|
||||||
|
|
||||||
.app-pagination__link-label { |
|
||||||
@include govuk-font($size: 16, $weight: "regular"); |
|
||||||
display: block; |
|
||||||
padding-left: 32px; |
|
||||||
text-decoration: underline; |
|
||||||
} |
|
||||||
|
|
||||||
&:hover .app-pagination__link-label { |
|
||||||
@include govuk-link-hover-decoration; |
|
||||||
} |
|
||||||
|
|
||||||
&:focus { |
|
||||||
box-shadow: 0 0 $govuk-focus-colour, 0 4px $govuk-focus-text-colour; |
|
||||||
text-decoration: underline; |
|
||||||
} |
|
||||||
|
|
||||||
&:hover:not(:focus) { |
|
||||||
background-color: govuk-colour("light-grey", $legacy: "grey-4"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
.app-pagination__icon { |
|
||||||
fill: currentcolor; |
|
||||||
} |
|
||||||
|
|
||||||
.app-pagination__item--prev .app-pagination__link, |
|
||||||
.app-pagination__item--next .app-pagination__link { |
|
||||||
padding: govuk-spacing(2) govuk-spacing(3); |
|
||||||
font-weight: bold; |
|
||||||
} |
|
||||||
|
|
||||||
.app-pagination__item--prev .app-pagination__icon { |
|
||||||
margin-right: govuk-spacing(2); |
|
||||||
} |
|
||||||
|
|
||||||
.app-pagination__item--next .app-pagination__icon { |
|
||||||
margin-left: govuk-spacing(2); |
|
||||||
} |
|
@ -1,109 +1,145 @@ |
|||||||
class Scheme < ApplicationRecord |
class Scheme < ApplicationRecord |
||||||
belongs_to :organisation |
belongs_to :organisation |
||||||
|
belongs_to :stock_owning_organisation, optional: true, class_name: "Organisation" |
||||||
has_many :locations |
has_many :locations |
||||||
has_many :case_logs |
has_many :case_logs |
||||||
|
|
||||||
scope :search_by_code, ->(code) { where("code ILIKE ?", "%#{code}%") } |
scope :filter_by_id, ->(id) { where(id: (id.start_with?("S") ? id[1..] : id)) } |
||||||
scope :search_by_service_name, ->(name) { where("service_name ILIKE ?", "%#{name}%") } |
scope :search_by_service_name, ->(name) { where("service_name ILIKE ?", "%#{name}%") } |
||||||
scope :search_by, ->(param) { search_by_code(param).or(search_by_service_name(param)) } |
scope :search_by_postcode, ->(postcode) { joins(:locations).where("locations.postcode ILIKE ?", "%#{postcode.delete(' ')}%") } |
||||||
|
scope :search_by, ->(param) { search_by_postcode(param).or(search_by_service_name(param)).or(filter_by_id(param)).distinct } |
||||||
|
|
||||||
SCHEME_TYPE = { |
SENSITIVE = { |
||||||
0 => "Missings", |
No: 0, |
||||||
4 => "Foyer", |
Yes: 1, |
||||||
5 => "Direct Access Hostel", |
|
||||||
6 => "Other Supported Housing", |
|
||||||
7 => "Housing for older people", |
|
||||||
}.freeze |
}.freeze |
||||||
|
|
||||||
PRIMARY_CLIENT_GROUP = { |
enum sensitive: SENSITIVE, _suffix: true |
||||||
"O" => "Homeless families with support needs", |
|
||||||
"H" => "Offenders & people at risk of offending", |
REGISTERED_UNDER_CARE_ACT = { |
||||||
"M" => "Older people with support needs", |
"No": 0, |
||||||
"L" => "People at risk of domestic violence", |
"Yes – registered care home providing nursing care": 1, |
||||||
"A" => "People with a physical or sensory disability", |
"Yes – registered care home providing personal care": 2, |
||||||
"G" => "People with alcohol problems", |
"Yes – part registered as a care home": 3, |
||||||
"F" => "People with drug problems", |
|
||||||
"B" => "People with HIV or AIDS", |
|
||||||
"D" => "People with learning disabilities", |
|
||||||
"E" => "People with mental health problems", |
|
||||||
"I" => "Refugees (permanent)", |
|
||||||
"S" => "Rough sleepers", |
|
||||||
"N" => "Single homeless people with support needs", |
|
||||||
"R" => "Teenage parents", |
|
||||||
"Q" => "Young people at risk", |
|
||||||
"P" => "Young people leaving care", |
|
||||||
"X" => "Missing", |
|
||||||
}.freeze |
}.freeze |
||||||
|
|
||||||
|
enum registered_under_care_act: REGISTERED_UNDER_CARE_ACT |
||||||
|
|
||||||
|
SCHEME_TYPE = { |
||||||
|
"Missing": 0, |
||||||
|
"Foyer": 4, |
||||||
|
"Direct Access Hostel": 5, |
||||||
|
"Other Supported Housing": 6, |
||||||
|
"Housing for older people": 7, |
||||||
|
}.freeze |
||||||
|
|
||||||
|
enum scheme_type: SCHEME_TYPE, _suffix: true |
||||||
|
|
||||||
SUPPORT_TYPE = { |
SUPPORT_TYPE = { |
||||||
0 => "Missing", |
"Missing": 0, |
||||||
1 => "Resettlement Support", |
"Resettlement support": 1, |
||||||
2 => "Low levels of support", |
"Low levels of support": 2, |
||||||
3 => "Medium levels of support", |
"Medium levels of support": 3, |
||||||
4 => "High levels of care and support", |
"High levels of care and support": 4, |
||||||
5 => "Nursing care services to a care home", |
"Nursing care services to a care home": 5, |
||||||
6 => "Floating Support", |
"Floating Support": 6, |
||||||
}.freeze |
}.freeze |
||||||
|
|
||||||
INTENDED_STAY = { |
enum support_type: SUPPORT_TYPE, _suffix: true |
||||||
"M" => "Medium stay", |
|
||||||
"P" => "Permanent", |
PRIMARY_CLIENT_GROUP = { |
||||||
"S" => "Short Stay", |
"Homeless families with support needs": "O", |
||||||
"V" => "Very short stay", |
"Offenders & people at risk of offending": "H", |
||||||
"X" => "Missing", |
"Older people with support needs": "M", |
||||||
|
"People at risk of domestic violence": "L", |
||||||
|
"People with a physical or sensory disability": "A", |
||||||
|
"People with alcohol problems": "G", |
||||||
|
"People with drug problems": "F", |
||||||
|
"People with HIV or AIDS": "B", |
||||||
|
"People with learning disabilities": "D", |
||||||
|
"People with mental health problems": "E", |
||||||
|
"Refugees (permanent)": "I", |
||||||
|
"Rough sleepers": "S", |
||||||
|
"Single homeless people with support needs": "N", |
||||||
|
"Teenage parents": "R", |
||||||
|
"Young people at risk": "Q", |
||||||
|
"Young people leaving care": "P", |
||||||
|
"Missing": "X", |
||||||
}.freeze |
}.freeze |
||||||
|
|
||||||
REGISTERED_UNDER_CARE_ACT = { |
enum primary_client_group: PRIMARY_CLIENT_GROUP, _suffix: true |
||||||
0 => "No", |
enum secondary_client_group: PRIMARY_CLIENT_GROUP, _suffix: true |
||||||
1 => "Yes – part registered as a care home", |
|
||||||
|
INTENDED_STAY = { |
||||||
|
"Medium stay": "M", |
||||||
|
"Permanent": "P", |
||||||
|
"Short stay": "S", |
||||||
|
"Very short stay": "V", |
||||||
|
"Missing": "X", |
||||||
}.freeze |
}.freeze |
||||||
|
|
||||||
SENSITIVE = { |
HAS_OTHER_CLIENT_GROUP = { |
||||||
0 => "No", |
No: 0, |
||||||
1 => "Yes", |
Yes: 1, |
||||||
}.freeze |
}.freeze |
||||||
|
|
||||||
def display_attributes |
enum intended_stay: INTENDED_STAY, _suffix: true |
||||||
|
enum has_other_client_group: HAS_OTHER_CLIENT_GROUP, _suffix: true |
||||||
|
|
||||||
|
def id_to_display |
||||||
|
"S#{id}" |
||||||
|
end |
||||||
|
|
||||||
|
def check_details_attributes |
||||||
[ |
[ |
||||||
{ name: "Service code", value: code }, |
{ name: "Service code", value: id_to_display }, |
||||||
{ name: "Name", value: service_name }, |
{ name: "Name", value: service_name }, |
||||||
{ name: "Confidential information", value: sensitive_display }, |
{ name: "Confidential information", value: sensitive }, |
||||||
|
{ name: "Housing stock owned by", value: stock_owning_organisation&.name }, |
||||||
{ name: "Managed by", value: organisation.name }, |
{ name: "Managed by", value: organisation.name }, |
||||||
{ name: "Type of scheme", value: scheme_type_display }, |
{ name: "Type of scheme", value: scheme_type }, |
||||||
{ name: "Registered under Care Standards Act 2000", value: registered_under_care_act_display }, |
{ name: "Registered under Care Standards Act 2000", value: registered_under_care_act }, |
||||||
{ name: "Total number of units", value: total_units }, |
|
||||||
{ name: "Primary client group", value: primary_client_group_display }, |
|
||||||
{ name: "Secondary client group", value: secondary_client_group_display }, |
|
||||||
{ name: "Level of support given", value: support_type_display }, |
|
||||||
{ name: "Intended length of stay", value: intended_stay_display }, |
|
||||||
] |
] |
||||||
end |
end |
||||||
|
|
||||||
def scheme_type_display |
def check_primary_client_attributes |
||||||
SCHEME_TYPE[scheme_type] |
[ |
||||||
end |
{ name: "Primary client group", value: primary_client_group }, |
||||||
|
] |
||||||
def sensitive_display |
|
||||||
SENSITIVE[sensitive] |
|
||||||
end |
|
||||||
|
|
||||||
def registered_under_care_act_display |
|
||||||
REGISTERED_UNDER_CARE_ACT[registered_under_care_act] |
|
||||||
end |
end |
||||||
|
|
||||||
def primary_client_group_display |
def check_secondary_client_confirmation_attributes |
||||||
PRIMARY_CLIENT_GROUP[primary_client_group] |
[ |
||||||
|
{ name: "Has another client group", value: has_other_client_group }, |
||||||
|
] |
||||||
end |
end |
||||||
|
|
||||||
def secondary_client_group_display |
def check_secondary_client_attributes |
||||||
PRIMARY_CLIENT_GROUP[secondary_client_group] |
[ |
||||||
|
{ name: "Secondary client group", value: secondary_client_group }, |
||||||
|
] |
||||||
end |
end |
||||||
|
|
||||||
def support_type_display |
def check_support_attributes |
||||||
SUPPORT_TYPE[support_type] |
[ |
||||||
|
{ name: "Level of support given", value: support_type }, |
||||||
|
{ name: "Intended length of stay", value: intended_stay }, |
||||||
|
] |
||||||
end |
end |
||||||
|
|
||||||
def intended_stay_display |
def display_attributes |
||||||
INTENDED_STAY[intended_stay] |
[ |
||||||
|
{ name: "Service code", value: id_to_display }, |
||||||
|
{ name: "Name", value: service_name }, |
||||||
|
{ name: "Confidential information", value: sensitive }, |
||||||
|
{ name: "Housing stock owned by", value: stock_owning_organisation&.name }, |
||||||
|
{ name: "Managed by", value: organisation.name }, |
||||||
|
{ name: "Type of scheme", value: scheme_type }, |
||||||
|
{ name: "Registered under Care Standards Act 2000", value: registered_under_care_act }, |
||||||
|
{ name: "Primary client group", value: primary_client_group }, |
||||||
|
{ name: "Secondary client group", value: secondary_client_group }, |
||||||
|
{ name: "Level of support given", value: support_type }, |
||||||
|
{ name: "Intended length of stay", value: intended_stay }, |
||||||
|
] |
||||||
end |
end |
||||||
end |
end |
||||||
|
@ -1,41 +1,6 @@ |
|||||||
<% link = pagy_link_proc(pagy) -%> |
<%= govuk_pagination(pagy:) %> |
||||||
<% if pagy.pages > 1 %> |
<% if pagy.pages > 1 %> |
||||||
<nav class="app-pagination" id="pagination-label" aria-label="results navigation"> |
<p class="govuk-body"> |
||||||
<ul class="app-pagination__list"> |
Showing <b><%= pagy.from %></b> to <b><%= pagy.to %></b> of <b><%= pagy.count %></b> <%= item_name %> |
||||||
<li class="app-pagination__item app-pagination__item--prev"> |
</p> |
||||||
<% if pagy.prev %> |
|
||||||
<a class="app-pagination__link" href="<%= "#{request.path}?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"> |
|
||||||
<path d="m6.5938-0.0078125-6.7266 6.7266 6.7441 6.4062 1.377-1.449-4.1856-3.9768h12.896v-2h-12.984l4.2931-4.293-1.414-1.414z"></path> |
|
||||||
</svg>Previous |
|
||||||
<span class="govuk-visually-hidden">page</span> |
|
||||||
</span></a> |
|
||||||
</li> |
|
||||||
<% pagy.series.each do |item| %> |
|
||||||
<% if item == :gap %> |
|
||||||
<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="<%= "#{request.path}?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="<%= "#{request.path}?page=#{pagy.next}" %>"> |
|
||||||
<% end %> |
|
||||||
Next <span class="govuk-visually-hidden">page</span> |
|
||||||
<span class="app-pagination__link-title"> |
|
||||||
<svg class="app-pagination__icon" xmlns="http://www.w3.org/2000/svg" height="13" width="17"> |
|
||||||
<path d="m10.107-0.0078125-1.4136 1.414 4.2926 4.293h-12.986v2h12.896l-4.1855 3.9766 1.377 1.4492 6.7441-6.4062-6.7246-6.7266z"></path> |
|
||||||
</svg> |
|
||||||
</span></a> |
|
||||||
</li> |
|
||||||
</ul> |
|
||||||
<p class="app_pagination__results"> |
|
||||||
Showing <b><%= pagy.from %></b> to <b><%= pagy.to %></b> of <b><%= pagy.count %></b> <%= item_name %> |
|
||||||
</p> |
|
||||||
</nav> |
|
||||||
<% end %> |
<% end %> |
||||||
|
@ -0,0 +1,65 @@ |
|||||||
|
<% content_for :title, "Check your answers before creating this scheme" %> |
||||||
|
|
||||||
|
<%= render partial: "organisations/headings", locals: { main: "Check your changes before updating this scheme", sub: @scheme.service_name } %> |
||||||
|
|
||||||
|
<%= govuk_tabs(title: "Check your answers before creating this scheme") do |component| %> |
||||||
|
<% component.tab(label: "Scheme") do %> |
||||||
|
<%= govuk_summary_list do |summary_list| %> |
||||||
|
<% @scheme.check_details_attributes.each do |attr| %> |
||||||
|
<% next if current_user.data_coordinator? && attr[:name] == ("Managed by") %> |
||||||
|
<%= summary_list.row do |row| %> |
||||||
|
<% row.key { attr[:name].to_s } %> |
||||||
|
<% row.value { details_html(attr) } %> |
||||||
|
<% row.action( |
||||||
|
text: "Change", |
||||||
|
href: scheme_details_path(scheme_id: @scheme.id, check_answers: true), |
||||||
|
) %> |
||||||
|
<% end %> |
||||||
|
<% end %> |
||||||
|
<% @scheme.check_primary_client_attributes.each do |attr| %> |
||||||
|
<%= summary_list.row do |row| %> |
||||||
|
<% row.key { attr[:name].to_s } %> |
||||||
|
<% row.value { details_html(attr) } %> |
||||||
|
<% row.action( |
||||||
|
text: "Change", |
||||||
|
href: scheme_primary_client_group_path(scheme_id: @scheme.id, check_answers: true), |
||||||
|
) %> |
||||||
|
<% end %> |
||||||
|
<% end %> |
||||||
|
<% @scheme.check_secondary_client_confirmation_attributes.each do |attr| %> |
||||||
|
<%= summary_list.row do |row| %> |
||||||
|
<% row.key { attr[:name].to_s } %> |
||||||
|
<% row.value { details_html(attr) } %> |
||||||
|
<% row.action( |
||||||
|
text: "Change", |
||||||
|
href: scheme_confirm_secondary_client_group_path(scheme_id: @scheme.id, check_answers: true), |
||||||
|
) %> |
||||||
|
<% end %> |
||||||
|
<% end %> |
||||||
|
<% if @scheme.has_other_client_group == "Yes" %> |
||||||
|
<% @scheme.check_secondary_client_attributes.each do |attr| %> |
||||||
|
<%= summary_list.row do |row| %> |
||||||
|
<% row.key { attr[:name].to_s } %> |
||||||
|
<% row.value { details_html(attr) } %> |
||||||
|
<% row.action( |
||||||
|
text: "Change", |
||||||
|
href: scheme_secondary_client_group_path(scheme_id: @scheme.id, check_answers: true), |
||||||
|
) %> |
||||||
|
<% end %> |
||||||
|
<% end %> |
||||||
|
<% end %> |
||||||
|
<% @scheme.check_support_attributes.each do |attr| %> |
||||||
|
<%= summary_list.row do |row| %> |
||||||
|
<% row.key { attr[:name].to_s } %> |
||||||
|
<% row.value { details_html(attr) } %> |
||||||
|
<% row.action( |
||||||
|
text: "Change", |
||||||
|
href: scheme_support_path(scheme_id: @scheme.id, check_answers: true), |
||||||
|
) %> |
||||||
|
<% end %> |
||||||
|
<% end %> |
||||||
|
<% end %> |
||||||
|
<% end %> |
||||||
|
<% end %> |
||||||
|
|
||||||
|
<%= govuk_button_link_to "Create scheme", schemes_path(scheme_id: @scheme.id), html: { method: :get } %> |
@ -0,0 +1,32 @@ |
|||||||
|
<% content_for :title, "Does this scheme provide for another client group?" %> |
||||||
|
|
||||||
|
<% content_for :before_content do %> |
||||||
|
<%= govuk_back_link( |
||||||
|
text: "Back", |
||||||
|
href: request.query_parameters["check_answers"] ? "/schemes/#{@scheme.id}/check-answers" : "/schemes/#{@scheme.id}/primary-client-group", |
||||||
|
) %> |
||||||
|
<% end %> |
||||||
|
|
||||||
|
<%= render partial: "organisations/headings", locals: { main: "Does this scheme provide for another client group?", sub: @scheme.service_name } %> |
||||||
|
|
||||||
|
<%= form_for(@scheme, method: :patch) do |f| %> |
||||||
|
<div class="govuk-grid-row"> |
||||||
|
<div class="govuk-grid-column-two-thirds"> |
||||||
|
|
||||||
|
<% selection = [OpenStruct.new(id: "Yes", name: "Yes"), OpenStruct.new(id: "No", name: "No")] %> |
||||||
|
|
||||||
|
<%= f.govuk_collection_radio_buttons :has_other_client_group, |
||||||
|
selection, |
||||||
|
:id, |
||||||
|
:name, |
||||||
|
legend: nil %> |
||||||
|
|
||||||
|
<%= f.hidden_field :page, value: "confirm-secondary" %> |
||||||
|
<% if request.query_parameters["check_answers"] == "true" %> |
||||||
|
<%= f.hidden_field :check_answers, value: "true" %> |
||||||
|
<% end %> |
||||||
|
|
||||||
|
<%= f.govuk_submit "Save and continue" %> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<% end %> |
@ -0,0 +1,76 @@ |
|||||||
|
<% content_for :title, "Create a new supported housing scheme" %> |
||||||
|
|
||||||
|
<% content_for :before_content do %> |
||||||
|
<%= govuk_back_link( |
||||||
|
text: "Back", |
||||||
|
href: :back, |
||||||
|
) %> |
||||||
|
<% end %> |
||||||
|
|
||||||
|
<%= render partial: "organisations/headings", locals: { main: "Create a new supported housing scheme", sub: nil } %> |
||||||
|
|
||||||
|
<%= form_for(@scheme, method: :patch) do |f| %> |
||||||
|
<div class="govuk-grid-row"> |
||||||
|
<div class="govuk-grid-column-two-thirds"> |
||||||
|
<%= f.govuk_error_summary %> |
||||||
|
|
||||||
|
<%= f.govuk_text_field :service_name, |
||||||
|
label: { text: "Scheme name", size: "m" }, |
||||||
|
hint: { text: "This is how you’ll refer to this supported housing scheme within your organisation. For example, the name could relate to the address or location. You’ll be able to see the client group when selecting it." } %> |
||||||
|
|
||||||
|
<%= f.govuk_check_box :sensitive, |
||||||
|
1, |
||||||
|
0, |
||||||
|
checked: @scheme.sensitive?, |
||||||
|
multiple: false, |
||||||
|
label: { text: "This scheme contains confidential information" } %> |
||||||
|
|
||||||
|
<% null_option = [OpenStruct.new(id: "", name: "Select an option")] %> |
||||||
|
<% organisations = Organisation.all.map { |org| OpenStruct.new(id: org.id, name: org.name) } %> |
||||||
|
<% stock_org_answer_options = null_option + organisations %> |
||||||
|
|
||||||
|
<%= f.govuk_collection_select :stock_owning_organisation_id, |
||||||
|
stock_org_answer_options, |
||||||
|
:id, |
||||||
|
:name, |
||||||
|
label: { text: "Which organisation owns the housing stock for this scheme?", size: "m" }, |
||||||
|
"data-controller": %w[accessible-autocomplete conditional-filter] %> |
||||||
|
|
||||||
|
<% if current_user.support? %> |
||||||
|
<%= f.govuk_collection_select :organisation_id, |
||||||
|
organisations, |
||||||
|
:id, |
||||||
|
:name, |
||||||
|
label: { text: "Which organisation manages this scheme?", size: "m" }, |
||||||
|
options: { required: true }, |
||||||
|
"data-controller": %w[accessible-autocomplete conditional-filter] %> |
||||||
|
<% end %> |
||||||
|
|
||||||
|
<% if current_user.data_coordinator? %> |
||||||
|
<%= f.hidden_field :organisation_id, value: current_user.organisation.id %> |
||||||
|
<% end %> |
||||||
|
|
||||||
|
<% scheme_types_selection = Scheme.scheme_types.keys.excluding("Missing").map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %> |
||||||
|
|
||||||
|
<%= f.govuk_collection_radio_buttons :scheme_type, |
||||||
|
scheme_types_selection, |
||||||
|
:id, |
||||||
|
:name, |
||||||
|
legend: { text: "What is this type of scheme?", size: "m" } %> |
||||||
|
|
||||||
|
<% care_acts_selection = Scheme.registered_under_care_acts.keys.reverse.map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %> |
||||||
|
|
||||||
|
<%= f.govuk_collection_radio_buttons :registered_under_care_act, |
||||||
|
care_acts_selection, |
||||||
|
:id, |
||||||
|
:name, |
||||||
|
legend: { text: "Is this scheme registered under the Care Standards Act 2000?", size: "m" } %> |
||||||
|
|
||||||
|
<%= f.hidden_field :page, value: "details" %> |
||||||
|
<% if request.query_parameters["check_answers"] %> |
||||||
|
<%= f.hidden_field :check_answers, value: "true" %> |
||||||
|
<% end %> |
||||||
|
<%= f.govuk_submit "Save and continue" %> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<% end %> |
@ -0,0 +1,72 @@ |
|||||||
|
<% content_for :title, "Create a new supported housing scheme" %> |
||||||
|
|
||||||
|
<% content_for :before_content do %> |
||||||
|
<%= govuk_back_link( |
||||||
|
text: "Back", |
||||||
|
href: "javascript:history.go(-1);", |
||||||
|
) %> |
||||||
|
<% end %> |
||||||
|
|
||||||
|
<%= form_for(@scheme, as: :scheme, method: :post) do |f| %> |
||||||
|
<div class="govuk-grid-row"> |
||||||
|
<div class="govuk-grid-column-two-thirds"> |
||||||
|
<%= f.govuk_error_summary %> |
||||||
|
|
||||||
|
<h1 class="govuk-heading-l"> |
||||||
|
<%= content_for(:title) %> |
||||||
|
</h1> |
||||||
|
|
||||||
|
<%= f.govuk_text_field :service_name, |
||||||
|
label: { text: "Scheme name", size: "m" }, |
||||||
|
hint: { text: "This is how you refer to this supported housing scheme within your organisation. For example, the name could relate to the address or location. You’ll be able to see the client group when selecting it." } %> |
||||||
|
|
||||||
|
<%= f.govuk_check_box :sensitive, |
||||||
|
"Yes", |
||||||
|
checked: @scheme.sensitive?, |
||||||
|
multiple: false, |
||||||
|
label: { text: "This scheme contains confidential information" } %> |
||||||
|
|
||||||
|
<% null_option = [OpenStruct.new(id: "", name: "Select an option")] %> |
||||||
|
<% organisations = Organisation.all.map { |org| OpenStruct.new(id: org.id, name: org.name) } %> |
||||||
|
<% answer_options = null_option + organisations %> |
||||||
|
|
||||||
|
<%= f.govuk_collection_select :stock_owning_organisation_id, |
||||||
|
answer_options, |
||||||
|
:id, |
||||||
|
:name, |
||||||
|
label: { text: "Which organisation owns the housing stock for this scheme?", size: "m" }, |
||||||
|
"data-controller": %w[accessible-autocomplete conditional-filter] %> |
||||||
|
|
||||||
|
<% if current_user.support? %> |
||||||
|
<%= f.govuk_collection_select :organisation_id, |
||||||
|
answer_options, |
||||||
|
:id, |
||||||
|
:name, |
||||||
|
label: { text: "Which organisation manages this scheme?", size: "m" }, |
||||||
|
"data-controller": %w[accessible-autocomplete conditional-filter] %> |
||||||
|
<% end %> |
||||||
|
|
||||||
|
<% if current_user.data_coordinator? %> |
||||||
|
<%= f.hidden_field :organisation_id, value: current_user.organisation.id %> |
||||||
|
<% end %> |
||||||
|
|
||||||
|
<% scheme_types_selection = Scheme.scheme_types.keys.excluding("Missing").map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %> |
||||||
|
|
||||||
|
<%= f.govuk_collection_radio_buttons :scheme_type, |
||||||
|
scheme_types_selection, |
||||||
|
:id, |
||||||
|
:name, |
||||||
|
legend: { text: "What is this type of scheme?", size: "m" } %> |
||||||
|
|
||||||
|
<% care_acts_selection = Scheme.registered_under_care_acts.keys.reverse.map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %> |
||||||
|
|
||||||
|
<%= f.govuk_collection_radio_buttons :registered_under_care_act, |
||||||
|
care_acts_selection, |
||||||
|
:id, |
||||||
|
:name, |
||||||
|
legend: { text: "Is this scheme registered under the Care Standards Act 2000?", size: "m" } %> |
||||||
|
|
||||||
|
<%= f.govuk_submit "Save and continue" %> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<% end %> |
@ -0,0 +1,35 @@ |
|||||||
|
<% content_for :title, "What client group is this scheme intended for?" %> |
||||||
|
|
||||||
|
<% content_for :before_content do %> |
||||||
|
<%= govuk_back_link( |
||||||
|
text: "Back", |
||||||
|
href: request.query_parameters["check_answers"] ? "/schemes/#{@scheme.id}/check-answers" : "/schemes/#{@scheme.id}/details", |
||||||
|
) %> |
||||||
|
<% end %> |
||||||
|
|
||||||
|
<%= render partial: "organisations/headings", locals: { main: "What client group is this scheme intended for?", sub: @scheme.service_name } %> |
||||||
|
|
||||||
|
<%= form_for(@scheme, method: :patch) do |f| %> |
||||||
|
<div class="govuk-grid-row"> |
||||||
|
<div class="govuk-grid-column-two-thirds"> |
||||||
|
<%= f.govuk_error_summary %> |
||||||
|
|
||||||
|
<legend class="govuk-fieldset__legend"> |
||||||
|
</legend> |
||||||
|
|
||||||
|
<% primary_client_group_selection = Scheme.primary_client_groups.keys.excluding("Missing").map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %> |
||||||
|
<%= f.govuk_collection_radio_buttons :primary_client_group, |
||||||
|
primary_client_group_selection, |
||||||
|
:id, |
||||||
|
:name, |
||||||
|
legend: nil %> |
||||||
|
|
||||||
|
<%= f.hidden_field :page, value: "primary-client-group" %> |
||||||
|
<% if request.query_parameters["check_answers"] == "true" %> |
||||||
|
<%= f.hidden_field :check_answers, value: "true" %> |
||||||
|
<% end %> |
||||||
|
|
||||||
|
<%= f.govuk_submit "Save and continue" %> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<% end %> |
@ -0,0 +1,35 @@ |
|||||||
|
<% content_for :title, "What is the other client group?" %> |
||||||
|
|
||||||
|
<% content_for :before_content do %> |
||||||
|
<%= govuk_back_link( |
||||||
|
text: "Back", |
||||||
|
href: request.query_parameters["check_answers"] ? "/schemes/#{@scheme.id}/check-answers" : "/schemes/#{@scheme.id}/confirm-secondary-client-group", |
||||||
|
) %> |
||||||
|
<% end %> |
||||||
|
|
||||||
|
<%= render partial: "organisations/headings", locals: { main: "What is the other client group?", sub: @scheme.service_name } %> |
||||||
|
|
||||||
|
<%= form_for(@scheme, method: :patch) do |f| %> |
||||||
|
<div class="govuk-grid-row"> |
||||||
|
<div class="govuk-grid-column-two-thirds"> |
||||||
|
<%= f.govuk_error_summary %> |
||||||
|
|
||||||
|
<legend class="govuk-fieldset__legend"> |
||||||
|
</legend> |
||||||
|
|
||||||
|
<% secondary_client_group_selection = Scheme.secondary_client_groups.keys.excluding("Missing").map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %> |
||||||
|
<%= f.govuk_collection_radio_buttons :secondary_client_group, |
||||||
|
secondary_client_group_selection, |
||||||
|
:id, |
||||||
|
:name, |
||||||
|
legend: nil %> |
||||||
|
|
||||||
|
<%= f.hidden_field :page, value: "secondary-client-group" %> |
||||||
|
<% if request.query_parameters["check_answers"] == "true" %> |
||||||
|
<%= f.hidden_field :check_answers, value: "true" %> |
||||||
|
<% end %> |
||||||
|
|
||||||
|
<%= f.govuk_submit "Save and continue" %> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<% end %> |
@ -0,0 +1,39 @@ |
|||||||
|
<% content_for :title, "What support does this scheme provide?" %> |
||||||
|
|
||||||
|
<% content_for :before_content do %> |
||||||
|
<%= govuk_back_link( |
||||||
|
text: "Back", |
||||||
|
href: request.query_parameters["check_answers"] ? "/schemes/#{@scheme.id}/check-answers" : "/schemes/#{@scheme.id}/secondary-client-group", |
||||||
|
) %> |
||||||
|
<% end %> |
||||||
|
|
||||||
|
<%= render partial: "organisations/headings", locals: { main: "What support does this scheme provide?", sub: @scheme.service_name } %> |
||||||
|
|
||||||
|
<%= form_for(@scheme, method: :patch) do |f| %> |
||||||
|
<div class="govuk-grid-row"> |
||||||
|
<div class="govuk-grid-column-two-thirds"> |
||||||
|
<%= f.govuk_error_summary %> |
||||||
|
|
||||||
|
<legend class="govuk-fieldset__legend"> |
||||||
|
</legend> |
||||||
|
|
||||||
|
<% support_type_selection = Scheme.support_types.keys.excluding("Missing").map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %> |
||||||
|
<%= f.govuk_collection_radio_buttons :support_type, |
||||||
|
support_type_selection, |
||||||
|
:id, |
||||||
|
:name, |
||||||
|
legend: { text: "Level of support given", size: "m" } %> |
||||||
|
|
||||||
|
<% intended_stay_selection = Scheme.intended_stays.keys.excluding("Missing").map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %> |
||||||
|
<%= f.govuk_collection_radio_buttons :intended_stay, |
||||||
|
intended_stay_selection, |
||||||
|
:id, |
||||||
|
:name, |
||||||
|
legend: { text: "Intended length of stay", size: "m" } %> |
||||||
|
|
||||||
|
<%= f.hidden_field :page, value: "support" %> |
||||||
|
|
||||||
|
<%= f.govuk_submit "Save and continue" %> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<% end %> |
@ -1,5 +1,5 @@ |
|||||||
class AddReferenceToCaseLog < ActiveRecord::Migration[7.0] |
class AddReferenceToCaseLog < ActiveRecord::Migration[7.0] |
||||||
def change |
def change |
||||||
add_reference :case_logs, :scheme, foreign_key: true |
add_reference :case_logs, :scheme, foreign_key: true, null: true |
||||||
end |
end |
||||||
end |
end |
||||||
|
@ -0,0 +1,7 @@ |
|||||||
|
class AddHasOtherClientGroupField < ActiveRecord::Migration[7.0] |
||||||
|
def change |
||||||
|
change_table :schemes, bulk: true do |t| |
||||||
|
t.column :has_other_client_group, :integer |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,5 @@ |
|||||||
|
class RemoveCodeFromSchemes < ActiveRecord::Migration[7.0] |
||||||
|
def change |
||||||
|
remove_column :schemes, :code, :string |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,5 @@ |
|||||||
|
class AddStockOwningOrganisationToSchemes < ActiveRecord::Migration[7.0] |
||||||
|
def change |
||||||
|
add_reference :schemes, :stock_owning_organisation, foreign_key: { to_table: :organisations } |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,195 @@ |
|||||||
|
# Developing locally on host machine |
||||||
|
|
||||||
|
The most common way to run a development version of the application is run with local dependencies. |
||||||
|
|
||||||
|
Dependencies: |
||||||
|
|
||||||
|
- [Ruby](https://www.ruby-lang.org/en/) |
||||||
|
- [Rails](https://rubyonrails.org/) |
||||||
|
- [PostgreSQL](https://www.postgresql.org/) |
||||||
|
- [NodeJS](https://nodejs.org/en/) |
||||||
|
- [Gecko driver](https://github.com/mozilla/geckodriver/releases) [for running Selenium tests] |
||||||
|
|
||||||
|
We recommend using [RBenv](https://github.com/rbenv/rbenv) to manage Ruby versions. |
||||||
|
|
||||||
|
1. Install PostgreSQL |
||||||
|
|
||||||
|
macOS: |
||||||
|
|
||||||
|
```bash |
||||||
|
brew install postgresql |
||||||
|
brew services start postgresql |
||||||
|
``` |
||||||
|
|
||||||
|
Linux (Debian): |
||||||
|
|
||||||
|
```bash |
||||||
|
sudo apt install -y postgresql postgresql-contrib libpq-dev |
||||||
|
sudo systemctl start postgresql |
||||||
|
``` |
||||||
|
|
||||||
|
2. Create a Postgres user |
||||||
|
|
||||||
|
```bash |
||||||
|
sudo su - postgres -c "createuser <username> -s -P" |
||||||
|
``` |
||||||
|
|
||||||
|
3. Install RBenv & Ruby-build |
||||||
|
|
||||||
|
macOS: |
||||||
|
|
||||||
|
```bash |
||||||
|
brew install rbenv |
||||||
|
rbenv init |
||||||
|
mkdir -p ~/.rbenv/plugins |
||||||
|
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build |
||||||
|
``` |
||||||
|
|
||||||
|
Linux (Debian): |
||||||
|
|
||||||
|
```bash |
||||||
|
sudo apt install -y rbenv git |
||||||
|
rbenv init |
||||||
|
echo 'eval "$(rbenv init -)"' >> ~/.bashrc |
||||||
|
mkdir -p ~/.rbenv/plugins |
||||||
|
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build |
||||||
|
``` |
||||||
|
|
||||||
|
4. Install Ruby and Bundler |
||||||
|
|
||||||
|
```bash |
||||||
|
rbenv install 3.1.2 |
||||||
|
rbenv global 3.1.2 |
||||||
|
source ~/.bashrc |
||||||
|
gem install bundler |
||||||
|
``` |
||||||
|
|
||||||
|
5. Install JavaScript dependencies |
||||||
|
|
||||||
|
macOS: |
||||||
|
|
||||||
|
```bash |
||||||
|
brew install node |
||||||
|
brew install yarn |
||||||
|
``` |
||||||
|
|
||||||
|
Linux (Debian): |
||||||
|
|
||||||
|
```bash |
||||||
|
curl -sL https://deb.nodesource.com/setup_16.x | sudo bash - |
||||||
|
sudo apt -y install nodejs |
||||||
|
mkdir -p ~/.npm-packages |
||||||
|
npm config set prefix ~/.npm-packages |
||||||
|
echo 'NPM_PACKAGES="~/.npm-packages"' >> ~/.bashrc |
||||||
|
echo 'export PATH="$PATH:$NPM_PACKAGES/bin"' >> ~/.bashrc |
||||||
|
source ~/.bashrc |
||||||
|
npm install --location=global yarn |
||||||
|
``` |
||||||
|
|
||||||
|
6. Clone the repo |
||||||
|
|
||||||
|
```bash |
||||||
|
git clone https://github.com/communitiesuk/submit-social-housing-lettings-and-sales-data.git |
||||||
|
``` |
||||||
|
|
||||||
|
## Application setup |
||||||
|
|
||||||
|
1. Copy the `.env.example` to `.env` and replace the database credentials with your local postgres user credentials. |
||||||
|
|
||||||
|
2. Install the dependencies: |
||||||
|
|
||||||
|
```bash |
||||||
|
bundle install && yarn install |
||||||
|
``` |
||||||
|
|
||||||
|
3. Create the database & run migrations: |
||||||
|
|
||||||
|
```bash |
||||||
|
bundle exec rake db:create db:migrate |
||||||
|
``` |
||||||
|
|
||||||
|
4. Seed the database if required: |
||||||
|
|
||||||
|
```bash |
||||||
|
bundle exec rake db:seed |
||||||
|
``` |
||||||
|
|
||||||
|
5. Start the dev servers |
||||||
|
|
||||||
|
a. Using Foreman: |
||||||
|
|
||||||
|
```bash |
||||||
|
./bin/dev |
||||||
|
``` |
||||||
|
|
||||||
|
b. Individually: |
||||||
|
|
||||||
|
Rails: |
||||||
|
|
||||||
|
```bash |
||||||
|
bundle exec rails s |
||||||
|
``` |
||||||
|
|
||||||
|
JavaScript (for hot reloading): |
||||||
|
|
||||||
|
```bash |
||||||
|
yarn build --mode=development --watch |
||||||
|
``` |
||||||
|
|
||||||
|
If you’re not modifying front end assets you can bundle them as a one off task: |
||||||
|
|
||||||
|
```bash |
||||||
|
yarn build --mode=development |
||||||
|
``` |
||||||
|
|
||||||
|
Development mode will target the latest versions of Chrome, Firefox and Safari for transpilation while production mode will target older browsers. |
||||||
|
|
||||||
|
The Rails server will start on <http://localhost:3000>. |
||||||
|
|
||||||
|
6. Install Gecko Driver |
||||||
|
|
||||||
|
Linux (Debian): |
||||||
|
|
||||||
|
```bash |
||||||
|
wget https://github.com/mozilla/geckodriver/releases/download/v0.31.0/geckodriver-v0.31.0-linux64.tar.gz |
||||||
|
tar -xvzf geckodriver-v0.31.0-linux64.tar.gz |
||||||
|
rm geckodriver-v0.31.0-linux64.tar.gz |
||||||
|
chmod +x geckodriver |
||||||
|
sudo mv geckodriver /usr/local/bin/ |
||||||
|
``` |
||||||
|
|
||||||
|
Running the test suite (front end assets need to be built or server needs to be running): |
||||||
|
|
||||||
|
```bash |
||||||
|
bundle exec rspec |
||||||
|
``` |
||||||
|
|
||||||
|
## Using Docker |
||||||
|
|
||||||
|
1. Build the image: |
||||||
|
|
||||||
|
```bash |
||||||
|
docker-compose build |
||||||
|
``` |
||||||
|
|
||||||
|
2. Run the database migrations: |
||||||
|
|
||||||
|
```bash |
||||||
|
docker-compose run --rm app /bin/bash -c 'rake db:migrate' |
||||||
|
``` |
||||||
|
|
||||||
|
3. Seed the database if required: |
||||||
|
|
||||||
|
```bash |
||||||
|
docker-compose run --rm app /bin/bash -c 'rake db:seed' |
||||||
|
``` |
||||||
|
|
||||||
|
4. To be able to debug with Pry run the app using: |
||||||
|
|
||||||
|
```bash |
||||||
|
docker-compose run --service-ports app |
||||||
|
``` |
||||||
|
|
||||||
|
If this is not needed you can run `docker-compose up` as normal |
||||||
|
|
||||||
|
The Rails server will start on <http://localhost:8080>. |
@ -0,0 +1,17 @@ |
|||||||
|
# Exporting to CDS |
||||||
|
|
||||||
|
All data collected by the application needs to be exported to the Consolidated Data Store (CDS) which is a data warehouse based on MS SQL running in the DAP (Data Analytics Platform). |
||||||
|
|
||||||
|
This is done via XML exports saved in an S3 bucket located in the DAP VPC using dedicated credentials shared out of band. The data mapping for this export can be found in `app/services/exports/case_log_export_service.rb` |
||||||
|
|
||||||
|
Initially the application database field names and field types were chosen to match the existing CDS data as closely as possible to minimise the amount of transformation needed. This has led to a less than optimal data model though and increasingly we should look to transform at the mapping layer where beneficial for our application. |
||||||
|
|
||||||
|
The export service is triggered nightly using [Gov PaaS tasks](https://docs.cloudfoundry.org/devguide/using-tasks.html). These tasks are triggered from a Github action, as Gov PaaS does not currently support the Cloud Foundry Task Scheduler. |
||||||
|
|
||||||
|
The S3 bucket is located in the DAP VPC rather than the application VPC as DAP runs in an AWS account directly so access to the S3 bucket can be restricted to only the IPs used by the application. This is not possible the other way around as [Gov PaaS does not support restricting S3 access by IP](https://github.com/alphagov/paas-roadmap/issues/107). |
||||||
|
|
||||||
|
## Other options previously considered |
||||||
|
|
||||||
|
- CDC replication using a managed service such as [AWS DMS](https://aws.amazon.com/dms/) |
||||||
|
- Would require VPC peering which [Gov PaaS does not currently support](https://github.com/alphagov/paas-roadmap/issues/105) |
||||||
|
- Would require CDS to make changes to their ingestion model |
@ -0,0 +1,21 @@ |
|||||||
|
# Form Runner |
||||||
|
|
||||||
|
The Form Runner is composed of: |
||||||
|
|
||||||
|
Ruby Classes: |
||||||
|
|
||||||
|
- A singleton form handler that instantiates an instances of each form definition (config file we have) combined with the setup section that is common to all forms. This is created at rails boot time. (`app/models/form_handler.rb`) |
||||||
|
- A `Form` class that is the entry point for parsing a form definition and handles most of the associated logic (`app/models/form.rb`) |
||||||
|
- `Section`, `Subsection`, `Page` and `Question` classes (`app/models/form/`) |
||||||
|
- Setup subsection specific instances (subclasses) of `Section`, `Subsection`, `Pages` and `Questions` (`app/form/setup/`) |
||||||
|
|
||||||
|
ERB templates: |
||||||
|
|
||||||
|
- The page view which is the main view for each form page (`app/views/form/page.html.erb`) |
||||||
|
- Partials for each question type (radio, checkbox, select, text, numeric, date) (`app/views/form/`) |
||||||
|
- Partials for specific question guidance (`app/views/form/guidance`) |
||||||
|
- The check answers page which is the view for the answer summary page of each section (`app/views/form/check_answers.html.erb`) |
||||||
|
|
||||||
|
Routes for each form page are generated by looping over each Page instance in each Form instance held by the form handler and defining a `GET` path. The corresponding controller method is also auto-generated with meta-programming via the same looping in `app/controllers/form_controller.rb` |
||||||
|
|
||||||
|
All form pages submit to the same controller method (`app/controllers/form_controller.rb#submit_form`) which validates and persists the data, and then redirects to the next form page that identifies as `routed_to` given the current case log state. |
After Width: | Height: | Size: 1.1 MiB |
After Width: | Height: | Size: 88 KiB |
After Width: | Height: | Size: 404 KiB |
After Width: | Height: | Size: 118 KiB |
@ -0,0 +1,148 @@ |
|||||||
|
# Infrastructure |
||||||
|
|
||||||
|
## Deployment |
||||||
|
|
||||||
|
This application is running on [GOV.UK PaaS](https://www.cloud.service.gov.uk/). To deploy you need to: |
||||||
|
|
||||||
|
1. Contact your organisation manager to get an account in `dluhc-core` organization and in the relevant spaces (staging/production). |
||||||
|
|
||||||
|
2. [Install the Cloud Foundry CLI](https://docs.cloudfoundry.org/cf-cli/install-go-cli.html) |
||||||
|
|
||||||
|
3. Login: |
||||||
|
|
||||||
|
```bash |
||||||
|
cf login -a api.london.cloud.service.gov.uk -u <your_username> |
||||||
|
``` |
||||||
|
|
||||||
|
4. Set your deployment target (staging/production): |
||||||
|
|
||||||
|
```bash |
||||||
|
cf target -o dluhc-core -s <deploy_environment> |
||||||
|
``` |
||||||
|
|
||||||
|
5. Deploy: |
||||||
|
|
||||||
|
```bash |
||||||
|
cf push dluhc-core --strategy rolling |
||||||
|
``` |
||||||
|
|
||||||
|
This will use the [manifest file](staging_manifest.yml) |
||||||
|
|
||||||
|
Once the app is deployed: |
||||||
|
|
||||||
|
1. Get a Rails console: |
||||||
|
|
||||||
|
```bash |
||||||
|
cf ssh dluhc-core-staging -t -c "/tmp/lifecycle/launcher /home/vcap/app 'rails console' ''" |
||||||
|
``` |
||||||
|
|
||||||
|
2. Check logs: |
||||||
|
|
||||||
|
```bash |
||||||
|
cf logs dluhc-core-staging --recent |
||||||
|
``` |
||||||
|
|
||||||
|
### Troubleshooting deployments |
||||||
|
|
||||||
|
A failed Github deployment action will occasionally leave a Cloud Foundry deployment in a broken state. As a result all subsequent Github deployment actions will also fail with the message `Cannot update this process while a deployment is in flight`. |
||||||
|
|
||||||
|
```bash |
||||||
|
cf cancel-deployment dluhc-core |
||||||
|
``` |
||||||
|
|
||||||
|
You would then need to check the logs and fix the issue that caused the initial deployment to fail. |
||||||
|
|
||||||
|
## CI/CD |
||||||
|
|
||||||
|
When a commit is made to `main` the following GitHub action jobs are triggered: |
||||||
|
|
||||||
|
1. **Test**: RSpec runs our test suite |
||||||
|
2. **Deploy**: If the Test stage passes, this job will deploy the app to our GOV.UK PaaS account using the Cloud Foundry CLI |
||||||
|
|
||||||
|
When a pull request is opened to `main` only the Test stage runs. |
||||||
|
|
||||||
|
## Setting up Infrastructure for a new environment |
||||||
|
|
||||||
|
### Staging |
||||||
|
|
||||||
|
1. Login: |
||||||
|
|
||||||
|
```bash |
||||||
|
cf login -a api.london.cloud.service.gov.uk -u <your_username> |
||||||
|
``` |
||||||
|
|
||||||
|
2. Set your deployment target (staging): |
||||||
|
|
||||||
|
```bash |
||||||
|
cf target -o dluhc-core -s staging |
||||||
|
``` |
||||||
|
|
||||||
|
3. Create required Postgres and S3 bucket backing services (this will take ~15 mins to finish creating): |
||||||
|
|
||||||
|
```bash |
||||||
|
cf create-service postgres tiny-unencrypted-13 dluhc-core-staging-postgres |
||||||
|
cf create-service aws-s3-bucket default dluhc-core-staging-import-bucket |
||||||
|
cf create-service aws-s3-bucket default dluhc-core-staging-export-bucket |
||||||
|
``` |
||||||
|
|
||||||
|
4. Deploy manifest: |
||||||
|
|
||||||
|
```bash |
||||||
|
cf push dluhc-core-staging --strategy rolling |
||||||
|
``` |
||||||
|
|
||||||
|
5. Bind S3 services to app: |
||||||
|
|
||||||
|
```bash |
||||||
|
cf bind-service dluhc-core-staging dluhc-core-staging-import-bucket -c '{"permissions": "read-only"}' |
||||||
|
cf bind-service dluhc-core-staging dluhc-core-staging-export-bucket -c '{"permissions": "read-write"}' |
||||||
|
``` |
||||||
|
|
||||||
|
6. Create a service keys for accessing the S3 bucket from outside Gov PaaS: |
||||||
|
|
||||||
|
```bash |
||||||
|
cf create-service-key dluhc-core-staging-import-bucket data-import -c '{"allow_external_access": true}' |
||||||
|
cf create-service-key dluhc-core-staging-export-bucket data-export -c '{"allow_external_access": true, "permissions": "read-only"}' |
||||||
|
``` |
||||||
|
|
||||||
|
### Production |
||||||
|
|
||||||
|
1. Login: |
||||||
|
|
||||||
|
```bash |
||||||
|
cf login -a api.london.cloud.service.gov.uk -u <your_username> |
||||||
|
``` |
||||||
|
|
||||||
|
2. Set your deployment target (production): |
||||||
|
|
||||||
|
```bash |
||||||
|
cf target -o dluhc-core -s production |
||||||
|
``` |
||||||
|
|
||||||
|
3. Create required Postgres and S3 bucket backing services (this will take ~15 mins to finish creating): |
||||||
|
|
||||||
|
```bash |
||||||
|
cf create-service postgres small-ha-13 dluhc-core-production-postgres |
||||||
|
cf create-service aws-s3-bucket default dluhc-core-production-import-bucket |
||||||
|
cf create-service aws-s3-bucket default dluhc-core-production-export-bucket |
||||||
|
``` |
||||||
|
|
||||||
|
4. Deploy manifest: |
||||||
|
|
||||||
|
```bash |
||||||
|
cf push dluhc-core-production --strategy rolling |
||||||
|
``` |
||||||
|
|
||||||
|
5. Bind S3 services to app: |
||||||
|
|
||||||
|
```bash |
||||||
|
cf bind-service dluhc-core-production dluhc-core-production-import-bucket -c '{"permissions": "read-only"}' |
||||||
|
cf bind-service dluhc-core-production dluhc-core-production-export-bucket -c '{"permissions": "read-write"}' |
||||||
|
``` |
||||||
|
|
||||||
|
6. Create a service keys for accessing the S3 bucket from outside Gov PaaS: |
||||||
|
|
||||||
|
```bash |
||||||
|
cf create-service-key dluhc-core-production-import-bucket data-import -c '{"allow_external_access": true}' |
||||||
|
cf create-service-key dluhc-core-production-export-bucket data-export -c '{"allow_external_access": true, "permissions": "read-only"}' |
||||||
|
``` |
@ -0,0 +1,17 @@ |
|||||||
|
# Monitoring |
||||||
|
|
||||||
|
We use self-hosted Prometheus and Grafana for monitoring infrastructure metrics. These are run in a dedicated Gov PaaS space called "monitoring" and are deployed as Docker images using Github action pipelines. The repository for these and more information is here: [dluhc-data-collection-monitoring](https://github.com/communitiesuk/dluhc-data-collection-monitoring). |
||||||
|
|
||||||
|
## Performance monitoring and alerting |
||||||
|
|
||||||
|
For application error and performance monitoring we use managed [Sentry](https://sentry.io/organizations/dluhc-core). You will need to be added to the DLUHC account to access this. It triggers slack notifications to the #team-data-collection-alerts channel for all application errors in staging and production and for any controller endpoints that have a P95 transaction duration > 250ms over a 24 hour period. |
||||||
|
|
||||||
|
## Logs |
||||||
|
|
||||||
|
For log persistence we use a managed ELK (Elasticsearch, Logstash, Kibana) stack provided by [Logit](https://logit.io/). You will need to be added to the DLUHC account to access this. Longs are retained for 14 days with a daily limit of 2GB. |
||||||
|
|
||||||
|
Logs are also available from Gov PaaS directly via CLI: |
||||||
|
|
||||||
|
```bash |
||||||
|
cf logs <gov-paas-space-name> --recent |
||||||
|
``` |
@ -0,0 +1,25 @@ |
|||||||
|
# Organisational relationships |
||||||
|
|
||||||
|
## Definitions |
||||||
|
|
||||||
|
- **Stock owning organisation**: An organisation that owns housing stock. It may manage the allocation of people in and out of their accommodation, or it may contract this out to managing agents. |
||||||
|
|
||||||
|
- **Managing agent**: In scenarios where one organisation owns stock and another organisation is contracted to manage the stock and tenants, the latter organisation is often called a ‘managing agent’. Managing agents are responsible for the allocation of people in and out of the accommodation, and/or responsible for the services provided to support those people in the accommodation (in the case of supported housing). |
||||||
|
|
||||||
|
## Permissions |
||||||
|
|
||||||
|
Organisations that own stock can contract out the management of that stock to another organisation. This relationship is often referred to as a parent/child relationship. This is a useful analogy as a parent can have multiple children, and a child can have many parents. A child organisation can also be a parent, and a parent organisation can also be a child organisation: |
||||||
|
|
||||||
|
 |
||||||
|
|
||||||
|
The case logs that a user can see depends on their role: |
||||||
|
|
||||||
|
- Customer support users can access any case log |
||||||
|
|
||||||
|
- Data coordinators can access any case log for which the organisation they work for is ultimately responsible for, meaning they can see logs managed by a child organisation |
||||||
|
|
||||||
|
- Data providers can only access case logs for which their organisation manages (or directly owns) |
||||||
|
|
||||||
|
Taking the relationships from the above diagram, and looking at which logs each user can access: |
||||||
|
|
||||||
|
 |
@ -0,0 +1,9 @@ |
|||||||
|
# Supported housing schemes |
||||||
|
|
||||||
|
## Schemes |
||||||
|
|
||||||
|
Groups of similar properties in the same location, intended for similar tenants with the same type of support needs, managed in the same way. As some of the information we need about a new tenancy is the same for all new tenancies in the ‘scheme’, users can set up a ‘scheme’ in the CORE system by completing the information once. In Supported Housing forms, the user just supplies the appropriate scheme. This means providers do not have to complete identical information multiple times in each CORE form. Effectively we model these as templates or predefined answer sets. |
||||||
|
|
||||||
|
## Management groups |
||||||
|
|
||||||
|
Schemes are often managed together as part of a ‘management group’. An organisation may have multiple management groups, and each management group may have multiple schemes. For Supported Housing logs, users must select the management group first, then select scheme. |
@ -0,0 +1,5 @@ |
|||||||
|
# Service overview |
||||||
|
|
||||||
|
All lettings and and sales of social housing in England need to be logged with the Department for levelling up, housing and communities (DLUHC). This is done by Local Authorities and Housing Associations, who are the primary users of this service. Data is collected via a form that runs on an annual data collection window basis. Form changes are made annually to add new questions, remove any that are no longer needed, or adjust wording or answer options etc. Each data collection window runs from 1st April to 1st April + an extra 3 months to allow for any late submissions, meaning that between April and July, two collection windows are open simultaneously and logs can be submitted for either. |
||||||
|
|
||||||
|
ADD (Analytics & Data Directorate) statisticians are the other primary users of the service. The data collected is transferred to DLUHCs data warehouse (CDS - consolidated data store), via nightly exports to XML which are transferred to S3 and ingested from there. CDS ingests and transforms the data, ultimately storing it in a MS SQL database and exposing it to analysts and statisticians via Amazon Workspaces. |
@ -0,0 +1,13 @@ |
|||||||
|
# Testing strategy |
||||||
|
|
||||||
|
- We use [RSpec](https://rspec.info/) and [Capybara](https://teamcapybara.github.io/capybara/) |
||||||
|
|
||||||
|
- Capybara is used for our feature tests. These use the Rack driver by default (faster) or the Gecko driver (installation required) when the `js: true` option is passed for a test. |
||||||
|
|
||||||
|
- Capybara is configured to run in headless mode but this can be toggled by commenting out `app/spec/rails_helper.rb#L14` |
||||||
|
|
||||||
|
- Capybara is configured to use Gecko driver for JavaScript tests as Chrome is more commonly used and so naturally more likely to be better tested but this can be switched to Chrome driver by changing `app/spec/rails_helper.rb#L13` |
||||||
|
|
||||||
|
- Feature specs are generally written sparingly as they’re also the slowest, where possible a request spec is preferred as this still tests a large surface area (route, controller, model, view) without the performance impact. They are not suitable for tests that need to run JavaScript or test that a specific set of interaction events that trigger a specific set of requests (with high confidence). |
||||||
|
|
||||||
|
- Test data is created with [FactoryBot](https://github.com/thoughtbot/factory_bot) where ever possible |
@ -0,0 +1,15 @@ |
|||||||
|
# User roles |
||||||
|
|
||||||
|
## External users |
||||||
|
|
||||||
|
The primary users of the system are external data providing organisations: Local Authorities and Private Registered Providers (Housing Associations). These have 2 main user types: |
||||||
|
|
||||||
|
- Data coordinators – administrators for their own organisation, can also complete logs |
||||||
|
- Data providers – complete the logs |
||||||
|
|
||||||
|
Additionally there are Data Protection Officers (DPO), which for some organisations is a separate role, but in our codebase is modelled as an attribute of the user (i.e. a data coordinator or provider can additionally be a DPO). They are responsible for ensuring the organisation has signed the data sharing agreement. |
||||||
|
|
||||||
|
## Internal users |
||||||
|
|
||||||
|
- Customer support (help desk) – can administrate all organisations |
||||||
|
- ADD statisticians – primary consumers of the data collected via CDS/DAP |
@ -1,56 +0,0 @@ |
|||||||
# Staging |
|
||||||
|
|
||||||
1. Login:\ |
|
||||||
`cf login -a api.london.cloud.service.gov.uk -u <your_username>` |
|
||||||
|
|
||||||
2. Set your deployment target (staging):\ |
|
||||||
`cf target -o dluhc-core -s staging` |
|
||||||
|
|
||||||
3. Create required Postgres and S3 bucket backing services (this will take ~15 mins to finish creating):\ |
|
||||||
`cf create-service postgres tiny-unencrypted-13 dluhc-core-staging-postgres` |
|
||||||
|
|
||||||
`cf create-service aws-s3-bucket default dluhc-core-staging-import-bucket` |
|
||||||
|
|
||||||
`cf create-service aws-s3-bucket default dluhc-core-staging-export-bucket` |
|
||||||
|
|
||||||
4. Deploy manifest:\ |
|
||||||
`cf push dluhc-core-staging --strategy rolling` |
|
||||||
|
|
||||||
5. Bind S3 services to app:\ |
|
||||||
`cf bind-service dluhc-core-staging dluhc-core-staging-import-bucket -c '{"permissions": "read-only"}'` |
|
||||||
|
|
||||||
`cf bind-service dluhc-core-staging dluhc-core-staging-export-bucket -c '{"permissions": "read-write"}'` |
|
||||||
|
|
||||||
6. Create a service keys for accessing the S3 bucket from outside Gov PaaS:\ |
|
||||||
`cf create-service-key dluhc-core-staging-import-bucket data-import -c '{"allow_external_access": true}'` |
|
||||||
|
|
||||||
`cf create-service-key dluhc-core-staging-export-bucket data-export -c '{"allow_external_access": true, "permissions": "read-only"}'` |
|
||||||
|
|
||||||
|
|
||||||
# Production |
|
||||||
|
|
||||||
1. Login:\ |
|
||||||
`cf login -a api.london.cloud.service.gov.uk -u <your_username>` |
|
||||||
|
|
||||||
2. Set your deployment target (production):\ |
|
||||||
`cf target -o dluhc-core -s production` |
|
||||||
|
|
||||||
3. Create required Postgres and S3 bucket backing services (this will take ~15 mins to finish creating):\ |
|
||||||
`cf create-service postgres small-ha-13 dluhc-core-production-postgres` |
|
||||||
|
|
||||||
`cf create-service aws-s3-bucket default dluhc-core-production-import-bucket` |
|
||||||
|
|
||||||
`cf create-service aws-s3-bucket default dluhc-core-production-export-bucket` |
|
||||||
|
|
||||||
4. Deploy manifest:\ |
|
||||||
`cf push dluhc-core-production --strategy rolling` |
|
||||||
|
|
||||||
5. Bind S3 services to app:\ |
|
||||||
`cf bind-service dluhc-core-production dluhc-core-production-import-bucket -c '{"permissions": "read-only"}'` |
|
||||||
|
|
||||||
`cf bind-service dluhc-core-production dluhc-core-production-export-bucket -c '{"permissions": "read-write"}'` |
|
||||||
|
|
||||||
6. Create a service keys for accessing the S3 bucket from outside Gov PaaS:\ |
|
||||||
`cf create-service-key dluhc-core-production-import-bucket data-import -c '{"allow_external_access": true}'` |
|
||||||
|
|
||||||
`cf create-service-key dluhc-core-production-export-bucket data-export -c '{"allow_external_access": true, "permissions": "read-only"}'` |
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue