Browse Source

merge: with main

pull/847/head
natdeanlewissoftwire 3 years ago
parent
commit
fa08bcaf7c
  1. 8
      app/components/check_answers_summary_list_card_component.html.erb
  2. 10
      app/components/check_answers_summary_list_card_component.rb
  3. 2
      app/components/log_summary_component.html.erb
  4. 2
      app/components/search_component.rb
  5. 2
      app/controllers/auth/sessions_controller.rb
  6. 8
      app/controllers/bulk_upload_controller.rb
  7. 74
      app/controllers/form_controller.rb
  8. 58
      app/controllers/lettings_logs_controller.rb
  9. 14
      app/controllers/modules/lettings_logs_filter.rb
  10. 10
      app/controllers/organisations_controller.rb
  11. 2
      app/controllers/start_controller.rb
  12. 4
      app/frontend/controllers/conditional_question_controller.js
  13. 4
      app/frontend/controllers/numeric_question_controller.js
  14. 30
      app/helpers/check_answers_helper.rb
  15. 10
      app/helpers/filters_helper.rb
  16. 6
      app/helpers/form_page_error_helper.rb
  17. 12
      app/helpers/interruption_screen_helper.rb
  18. 6
      app/helpers/navigation_items_helper.rb
  19. 2
      app/helpers/question_attribute_helper.rb
  20. 32
      app/helpers/tasklist_helper.rb
  21. 8
      app/models/bulk_upload.rb
  22. 25
      app/models/derived_variables/lettings_log_variables.rb
  23. 82
      app/models/form.rb
  24. 4
      app/models/form/page.rb
  25. 98
      app/models/form/question.rb
  26. 2
      app/models/form/setup/pages/created_by.rb
  27. 2
      app/models/form/setup/pages/organisation.rb
  28. 10
      app/models/form/setup/questions/created_by_id.rb
  29. 20
      app/models/form/setup/questions/location_id.rb
  30. 6
      app/models/form/setup/questions/owning_organisation_id.rb
  31. 18
      app/models/form/setup/questions/scheme_id.rb
  32. 4
      app/models/form/setup/subsections/setup.rb
  33. 28
      app/models/form/subsection.rb
  34. 63
      app/models/lettings_log.rb
  35. 16
      app/models/organisation.rb
  36. 2
      app/models/scheme.rb
  37. 20
      app/models/user.rb
  38. 10
      app/models/validations/household_validations.rb
  39. 17
      app/services/csv/lettings_log_csv_service.rb
  40. 2
      app/services/exports/lettings_log_export_constants.rb
  41. 104
      app/services/exports/lettings_log_export_service.rb
  42. 22
      app/services/imports/lettings_logs_field_import_service.rb
  43. 73
      app/services/imports/lettings_logs_import_service.rb
  44. 12
      app/views/form/_check_answers_summary_list.html.erb
  45. 4
      app/views/form/_checkbox_question.html.erb
  46. 4
      app/views/form/_interruption_screen_question.html.erb
  47. 12
      app/views/form/_numeric_output_question.html.erb
  48. 2
      app/views/form/_numeric_question.html.erb
  49. 2
      app/views/form/_radio_question.html.erb
  50. 6
      app/views/form/_select_question.html.erb
  51. 22
      app/views/form/check_answers.html.erb
  52. 12
      app/views/form/page.html.erb
  53. 8
      app/views/form/review.html.erb
  54. 2
      app/views/layouts/application.html.erb
  55. 0
      app/views/lettings_logs/_log_filters.erb
  56. 2
      app/views/lettings_logs/_log_list.html.erb
  57. 8
      app/views/lettings_logs/_tasklist.html.erb
  58. 4
      app/views/lettings_logs/bulk_upload.html.erb
  59. 16
      app/views/lettings_logs/edit.html.erb
  60. 6
      app/views/lettings_logs/index.html.erb
  61. 6
      app/views/organisations/logs.html.erb
  62. 2
      app/views/start/index.html.erb
  63. 82
      config/forms/2021_2022.json
  64. 82
      config/forms/2022_2023.json
  65. 4
      config/locales/en.yml
  66. 2
      config/routes.rb
  67. 8
      db/migrate/20220809100723_add_accessibility_requirements_fields.rb
  68. 5
      db/migrate/20220823083657_change_case_log_to_lettings_log.rb
  69. 38
      db/schema.rb
  70. 6
      docs/adr/adr-003-form-submission-flow.md
  71. 2
      docs/adr/adr-005-form-definition.md
  72. 8
      docs/adr/adr-007-data-validations.md
  73. 4
      docs/adr/adr-011-form-oop-refactor.md
  74. 54
      docs/api/v1.json
  75. 2
      docs/exports.md
  76. 6
      docs/form/builder.md
  77. 2
      docs/form/question.md
  78. 2
      docs/form/runner.md
  79. 2
      docs/form/section.md
  80. 2
      docs/form/subsection.md
  81. 8
      docs/index.md
  82. 43
      docs/infrastructure.md
  83. 6
      lib/tasks/data_export.rake
  84. 4
      lib/tasks/data_import.rake
  85. 2
      lib/tasks/data_import_field.rake
  86. 2
      lib/tasks/full_import.rake
  87. 14
      spec/components/check_answers_summary_list_card_component_spec.rb
  88. 2
      spec/components/log_summary_component_spec.rb
  89. 5
      spec/factories/lettings_log.rb
  90. 46
      spec/features/form/accessible_autocomplete_spec.rb
  91. 94
      spec/features/form/check_answers_page_spec.rb
  92. 38
      spec/features/form/checkboxes_spec.rb
  93. 22
      spec/features/form/conditional_questions_spec.rb
  94. 40
      spec/features/form/form_navigation_spec.rb
  95. 18
      spec/features/form/helpers.rb
  96. 66
      spec/features/form/page_routing_spec.rb
  97. 44
      spec/features/form/progressive_total_field_spec.rb
  98. 46
      spec/features/form/saving_data_spec.rb
  99. 26
      spec/features/form/tasklist_page_spec.rb
  100. 60
      spec/features/form/validations_spec.rb
  101. Some files were not shown because too many files have changed in this diff Show More

8
app/components/check_answers_summary_list_card_component.html.erb

@ -16,18 +16,18 @@
<% row.key { question.check_answer_label.to_s.presence || question.header.to_s } %>
<% row.value do %>
<span class="govuk-!-margin-right-4"><%= get_answer_label(question) %></span>
<% extra_value = question.get_extra_check_answer_value(case_log) %>
<% extra_value = question.get_extra_check_answer_value(lettings_log) %>
<% if extra_value %>
<span class="govuk-!-font-weight-regular app-!-colour-muted"><%= extra_value %></span>
<% end %>
<br>
<% question.get_inferred_answers(case_log).each do |inferred_answer| %>
<% question.get_inferred_answers(lettings_log).each do |inferred_answer| %>
<span class="govuk-!-font-weight-regular app-!-colour-muted"><%= inferred_answer %></span>
<% end %>
<% end %>
<% row.action(
text: question.action_text(case_log),
href: question.action_href(case_log, question.page.id),
text: question.action_text(lettings_log),
href: question.action_href(lettings_log, question.page.id),
visually_hidden_text: question.check_answer_label.to_s.downcase,
) %>
<% end %>

10
app/components/check_answers_summary_list_card_component.rb

@ -1,18 +1,18 @@
class CheckAnswersSummaryListCardComponent < ViewComponent::Base
attr_reader :questions, :case_log, :user
attr_reader :questions, :lettings_log, :user
def initialize(questions:, case_log:, user:)
def initialize(questions:, lettings_log:, user:)
@questions = questions
@case_log = case_log
@lettings_log = lettings_log
@user = user
super
end
def applicable_questions
questions.reject { |q| q.hidden_in_check_answers?(case_log, user) }
questions.reject { |q| q.hidden_in_check_answers?(lettings_log, user) }
end
def get_answer_label(question)
question.answer_label(case_log).presence || "<span class=\"app-!-colour-muted\">You didn’t answer this question</span>".html_safe
question.answer_label(lettings_log).presence || "<span class=\"app-!-colour-muted\">You didn’t answer this question</span>".html_safe
end
end

2
app/components/log_summary_component.html.erb

@ -3,7 +3,7 @@
<div class="govuk-grid-column-one-half">
<header class="app-log-summary__header">
<h2 class="app-log-summary__title">
<%= govuk_link_to case_log_path(log) do %>
<%= govuk_link_to lettings_log_path(log) do %>
<span class="govuk-visually-hidden">Log </span><%= log.id %>
<% end %>
</h2>

2
app/components/search_component.rb

@ -20,7 +20,7 @@ class SearchComponent < ViewComponent::Base
elsif request.path.include?("organisations")
organisations_path
elsif request.path.include?("logs")
case_logs_path
lettings_logs_path
end
end

2
app/controllers/auth/sessions_controller.rb

@ -24,7 +24,7 @@ private
if resource.need_two_factor_authentication?(request)
user_two_factor_authentication_path
else
params.dig("user", "start").present? ? case_logs_path : super
params.dig("user", "start").present? ? lettings_logs_path : super
end
end
end

8
app/controllers/bulk_upload_controller.rb

@ -3,7 +3,7 @@ class BulkUploadController < ApplicationController
def show
@bulk_upload = BulkUpload.new(nil, nil)
render "case_logs/bulk_upload"
render "lettings_logs/bulk_upload"
end
def bulk_upload
@ -12,15 +12,15 @@ class BulkUploadController < ApplicationController
@bulk_upload = BulkUpload.new(file, content_type)
@bulk_upload.process(current_user)
if @bulk_upload.errors.present?
render "case_logs/bulk_upload", status: :unprocessable_entity
render "lettings_logs/bulk_upload", status: :unprocessable_entity
else
redirect_to(case_logs_path)
redirect_to(lettings_logs_path)
end
end
private
def upload_params
params.require("bulk_upload")["case_log_bulk_upload"]
params.require("bulk_upload")["lettings_log_bulk_upload"]
end
end

74
app/controllers/form_controller.rb

@ -4,22 +4,22 @@ class FormController < ApplicationController
before_action :find_resource_by_named_id, except: %i[submit_form review]
def submit_form
if @case_log
@page = @case_log.form.get_page(params[:case_log][:page])
if @lettings_log
@page = @lettings_log.form.get_page(params[:lettings_log][:page])
responses_for_page = responses_for_page(@page)
mandatory_questions_with_no_response = mandatory_questions_with_no_response(responses_for_page)
if mandatory_questions_with_no_response.empty? && @case_log.update(responses_for_page)
if mandatory_questions_with_no_response.empty? && @lettings_log.update(responses_for_page)
session[:errors] = session[:fields] = nil
redirect_to(successful_redirect_path)
else
redirect_path = "case_log_#{@page.id}_path"
redirect_path = "lettings_log_#{@page.id}_path"
mandatory_questions_with_no_response.map do |question|
@case_log.errors.add question.id.to_sym, question.unanswered_error_message
@lettings_log.errors.add question.id.to_sym, question.unanswered_error_message
end
session[:errors] = @case_log.errors.to_json
Rails.logger.info "User triggered validation(s) on: #{@case_log.errors.map(&:attribute).join(', ')}"
redirect_to(send(redirect_path, @case_log))
session[:errors] = @lettings_log.errors.to_json
Rails.logger.info "User triggered validation(s) on: #{@lettings_log.errors.map(&:attribute).join(', ')}"
redirect_to(send(redirect_path, @lettings_log))
end
else
render_not_found
@ -27,9 +27,9 @@ class FormController < ApplicationController
end
def check_answers
if @case_log
if @lettings_log
current_url = request.env["PATH_INFO"]
subsection = @case_log.form.get_subsection(current_url.split("/")[-2])
subsection = @lettings_log.form.get_subsection(current_url.split("/")[-2])
render "form/check_answers", locals: { subsection:, current_user: }
else
render_not_found
@ -37,7 +37,7 @@ class FormController < ApplicationController
end
def review
if @case_log
if @lettings_log
render "form/review"
else
render_not_found
@ -47,14 +47,14 @@ class FormController < ApplicationController
FormHandler.instance.forms.each do |_key, form|
form.pages.map do |page|
define_method(page.id) do |_errors = {}|
if @case_log
if @lettings_log
restore_error_field_values
@subsection = @case_log.form.subsection_for_page(page)
@page = @case_log.form.get_page(page.id)
if @page.routed_to?(@case_log, current_user)
@subsection = @lettings_log.form.subsection_for_page(page)
@page = @lettings_log.form.get_page(page.id)
if @page.routed_to?(@lettings_log, current_user)
render "form/page"
else
redirect_to case_log_path(@case_log)
redirect_to lettings_log_path(@lettings_log)
end
else
render_not_found
@ -68,13 +68,13 @@ private
def restore_error_field_values
if session["errors"]
JSON(session["errors"]).each do |field, messages|
messages.each { |message| @case_log.errors.add field.to_sym, message }
messages.each { |message| @lettings_log.errors.add field.to_sym, message }
end
end
if session["fields"]
session["fields"].each do |field, value|
unless @case_log.form.get_question(field, @case_log)&.type == "date"
@case_log[field] = value
unless @lettings_log.form.get_question(field, @lettings_log)&.type == "date"
@lettings_log[field] = value
end
end
end
@ -82,11 +82,11 @@ private
def responses_for_page(page)
page.questions.each_with_object({}) do |question, result|
question_params = params["case_log"][question.id]
question_params = params["lettings_log"][question.id]
if question.type == "date"
day = params["case_log"]["#{question.id}(3i)"]
month = params["case_log"]["#{question.id}(2i)"]
year = params["case_log"]["#{question.id}(1i)"]
day = params["lettings_log"]["#{question.id}(3i)"]
month = params["lettings_log"]["#{question.id}(2i)"]
year = params["lettings_log"]["#{question.id}(1i)"]
next unless [day, month, year].any?(&:present?)
result[question.id] = if Date.valid_date?(year.to_i, month.to_i, day.to_i) && year.to_i.between?(2000, 2200)
@ -109,11 +109,11 @@ private
end
def find_resource
@case_log = current_user.case_logs.find_by(id: params[:id])
@lettings_log = current_user.lettings_logs.find_by(id: params[:id])
end
def find_resource_by_named_id
@case_log = current_user.case_logs.find_by(id: params[:case_log_id])
@lettings_log = current_user.lettings_logs.find_by(id: params[:lettings_log_id])
end
def is_referrer_check_answers?
@ -123,18 +123,18 @@ private
def successful_redirect_path
if is_referrer_check_answers?
page_ids = @case_log.form.subsection_for_page(@page).pages.map(&:id)
page_ids = @lettings_log.form.subsection_for_page(@page).pages.map(&:id)
page_index = page_ids.index(@page.id)
next_page = @case_log.form.next_page(@page, @case_log, current_user)
previous_page = @case_log.form.previous_page(page_ids, page_index, @case_log, current_user)
next_page = @lettings_log.form.next_page(@page, @lettings_log, current_user)
previous_page = @lettings_log.form.previous_page(page_ids, page_index, @lettings_log, current_user)
if next_page.to_s.include?("value_check") || next_page == previous_page
return "/logs/#{@case_log.id}/#{next_page.dasherize}?referrer=check_answers"
return "/logs/#{@lettings_log.id}/#{next_page.dasherize}?referrer=check_answers"
else
return send("case_log_#{@case_log.form.subsection_for_page(@page).id}_check_answers_path", @case_log)
return send("lettings_log_#{@lettings_log.form.subsection_for_page(@page).id}_check_answers_path", @lettings_log)
end
end
redirect_path = @case_log.form.next_page_redirect_path(@page, @case_log, current_user)
send(redirect_path, @case_log)
redirect_path = @lettings_log.form.next_page_redirect_path(@page, @lettings_log, current_user)
send(redirect_path, @lettings_log)
end
def mandatory_questions_with_no_response(responses_for_page)
@ -148,12 +148,12 @@ private
end
def question_is_required?(question)
CaseLog::OPTIONAL_FIELDS.exclude?(question.id) && required_questions.include?(question.id)
LettingsLog::OPTIONAL_FIELDS.exclude?(question.id) && required_questions.include?(question.id)
end
def required_questions
@required_questions ||= begin
log = @case_log
log = @lettings_log
log.assign_attributes(responses_for_page(@page))
@page.subsection.applicable_questions(log).select { |q| q.enabled?(log) }.map(&:id)
end
@ -162,12 +162,12 @@ private
def question_missing_response?(responses_for_page, question)
if %w[checkbox validation_override].include?(question.type)
answered = question.answer_options.keys.reject { |x| x.match(/divider/) }.map do |option|
session["fields"][option] = @case_log[option] = params["case_log"][question.id].include?(option) ? 1 : 0
params["case_log"][question.id].exclude?(option)
session["fields"][option] = @lettings_log[option] = params["lettings_log"][question.id].include?(option) ? 1 : 0
params["lettings_log"][question.id].exclude?(option)
end
answered.all?
else
session["fields"][question.id] = @case_log[question.id] = responses_for_page[question.id]
session["fields"][question.id] = @lettings_log[question.id] = responses_for_page[question.id]
responses_for_page[question.id].nil? || responses_for_page[question.id].blank?
end
end

58
app/controllers/case_logs_controller.rb → app/controllers/lettings_logs_controller.rb

@ -1,6 +1,6 @@
class CaseLogsController < ApplicationController
class LettingsLogsController < ApplicationController
include Pagy::Backend
include Modules::CaseLogsFilter
include Modules::LettingsLogsFilter
include Modules::SearchFilter
skip_before_action :verify_authenticity_token, if: :json_api_request?
@ -11,12 +11,12 @@ class CaseLogsController < ApplicationController
def index
set_session_filters
all_logs = current_user.case_logs
unpaginated_filtered_logs = filtered_case_logs(filtered_collection(all_logs, search_term))
all_logs = current_user.lettings_logs
unpaginated_filtered_logs = filtered_lettings_logs(filtered_collection(all_logs, search_term))
respond_to do |format|
format.html do
@pagy, @case_logs = pagy(unpaginated_filtered_logs)
@pagy, @lettings_logs = pagy(unpaginated_filtered_logs)
@searched = search_term.presence
@total_count = all_logs.size
end
@ -28,28 +28,28 @@ class CaseLogsController < ApplicationController
end
def create
case_log = CaseLog.new(case_log_params)
lettings_log = LettingsLog.new(lettings_log_params)
respond_to do |format|
format.html do
case_log.save!
redirect_to case_log
lettings_log.save!
redirect_to lettings_log_url(lettings_log)
end
format.json do
if case_log.save
render json: case_log, status: :created
if lettings_log.save
render json: lettings_log, status: :created
else
render json: { errors: case_log.errors.messages }, status: :unprocessable_entity
render json: { errors: lettings_log.errors.messages }, status: :unprocessable_entity
end
end
end
end
def update
if @case_log
if @case_log.update(api_case_log_params)
render json: @case_log, status: :ok
if @lettings_log
if @lettings_log.update(api_lettings_log_params)
render json: @lettings_log, status: :ok
else
render json: { errors: @case_log.errors.messages }, status: :unprocessable_entity
render json: { errors: @lettings_log.errors.messages }, status: :unprocessable_entity
end
else
render_not_found_json("Log", params[:id])
@ -61,8 +61,8 @@ class CaseLogsController < ApplicationController
# We don't have a dedicated non-editable show view
format.html { edit }
format.json do
if @case_log
render json: @case_log, status: :ok
if @lettings_log
render json: @lettings_log, status: :ok
else
render_not_found_json("Log", params[:id])
end
@ -71,8 +71,8 @@ class CaseLogsController < ApplicationController
end
def edit
@case_log = current_user.case_logs.find_by(id: params[:id])
if @case_log
@lettings_log = current_user.lettings_logs.find_by(id: params[:id])
if @lettings_log
render :edit, locals: { current_user: }
else
render_not_found
@ -80,11 +80,11 @@ class CaseLogsController < ApplicationController
end
def destroy
if @case_log
if @case_log.delete
if @lettings_log
if @lettings_log.delete
head :no_content
else
render json: { errors: @case_log.errors.messages }, status: :unprocessable_entity
render json: { errors: @lettings_log.errors.messages }, status: :unprocessable_entity
end
else
render_not_found_json("Log", params[:id])
@ -107,11 +107,11 @@ private
http_basic_authenticate_or_request_with name: ENV["API_USER"], password: ENV["API_KEY"]
end
def case_log_params
def lettings_log_params
if current_user && !current_user.support?
org_params.merge(api_case_log_params)
org_params.merge(api_lettings_log_params)
else
api_case_log_params
api_lettings_log_params
end
end
@ -123,16 +123,16 @@ private
}
end
def api_case_log_params
return {} unless params[:case_log]
def api_lettings_log_params
return {} unless params[:lettings_log]
permitted = params.require(:case_log).permit(CaseLog.editable_fields)
permitted = params.require(:lettings_log).permit(LettingsLog.editable_fields)
owning_id = permitted["owning_organisation_id"]
permitted["owning_organisation"] = Organisation.find(owning_id) if owning_id
permitted
end
def find_resource
@case_log = CaseLog.find_by(id: params[:id])
@lettings_log = LettingsLog.find_by(id: params[:id])
end
end

14
app/controllers/modules/case_logs_filter.rb → app/controllers/modules/lettings_logs_filter.rb

@ -1,7 +1,7 @@
module Modules::CaseLogsFilter
def filtered_case_logs(logs)
if session[:case_logs_filters].present?
filters = JSON.parse(session[:case_logs_filters])
module Modules::LettingsLogsFilter
def filtered_lettings_logs(logs)
if session[:lettings_logs_filters].present?
filters = JSON.parse(session[:lettings_logs_filters])
filters.each do |category, values|
next if Array(values).reject(&:empty?).blank?
next if category == "organisation" && params["organisation_select"] == "all"
@ -14,10 +14,10 @@ module Modules::CaseLogsFilter
end
def set_session_filters(specific_org: false)
new_filters = session[:case_logs_filters].present? ? JSON.parse(session[:case_logs_filters]) : {}
current_user.case_logs_filters(specific_org:).each { |filter| new_filters[filter] = params[filter] if params[filter].present? }
new_filters = session[:lettings_logs_filters].present? ? JSON.parse(session[:lettings_logs_filters]) : {}
current_user.lettings_logs_filters(specific_org:).each { |filter| new_filters[filter] = params[filter] if params[filter].present? }
new_filters = new_filters.except("organisation") if params["organisation_select"] == "all"
session[:case_logs_filters] = new_filters.to_json
session[:lettings_logs_filters] = new_filters.to_json
end
end

10
app/controllers/organisations_controller.rb

@ -1,6 +1,6 @@
class OrganisationsController < ApplicationController
include Pagy::Backend
include Modules::CaseLogsFilter
include Modules::LettingsLogsFilter
include Modules::SearchFilter
before_action :authenticate_user!
@ -91,12 +91,12 @@ class OrganisationsController < ApplicationController
if current_user.support?
set_session_filters(specific_org: true)
organisation_logs = CaseLog.all.where(owning_organisation_id: @organisation.id)
unpaginated_filtered_logs = filtered_case_logs(filtered_collection(organisation_logs, search_term))
organisation_logs = LettingsLog.all.where(owning_organisation_id: @organisation.id)
unpaginated_filtered_logs = filtered_lettings_logs(filtered_collection(organisation_logs, search_term))
respond_to do |format|
format.html do
@pagy, @case_logs = pagy(unpaginated_filtered_logs)
@pagy, @lettings_logs = pagy(unpaginated_filtered_logs)
@searched = search_term.presence
@total_count = organisation_logs.size
render "logs", layout: "application"
@ -107,7 +107,7 @@ class OrganisationsController < ApplicationController
end
end
else
redirect_to(case_logs_path)
redirect_to(lettings_logs_path)
end
end

2
app/controllers/start_controller.rb

@ -1,7 +1,7 @@
class StartController < ApplicationController
def index
if current_user
redirect_to(case_logs_path)
redirect_to(lettings_logs_path)
end
end
end

4
app/frontend/controllers/conditional_question_controller.js

@ -12,10 +12,10 @@ export default class extends Controller {
Object.entries(conditionalFor).forEach(([targetQuestion, conditions]) => {
if (!conditions.map(String).includes(String(selectedValue))) {
const textNumericInput = document.getElementById(`case-log-${targetQuestion.replaceAll('_', '-')}-field`)
const textNumericInput = document.getElementById(`lettings-log-${targetQuestion.replaceAll('_', '-')}-field`)
if (textNumericInput == null) {
const dateInputs = [1, 2, 3].map((idx) => {
return document.getElementById(`case_log_${targetQuestion}_${idx}i`)
return document.getElementById(`lettings_log_${targetQuestion}_${idx}i`)
})
this.clearDateInputs(dateInputs)
} else {

4
app/frontend/controllers/numeric_question_controller.js

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

30
app/helpers/check_answers_helper.rb

@ -1,9 +1,9 @@
module CheckAnswersHelper
include GovukLinkHelper
def display_answered_questions_summary(subsection, case_log, current_user)
total = total_count(subsection, case_log, current_user)
answered = answered_questions_count(subsection, case_log, current_user)
def display_answered_questions_summary(subsection, lettings_log, current_user)
total = total_count(subsection, lettings_log, current_user)
answered = answered_questions_count(subsection, lettings_log, current_user)
if total == answered
'<p class="govuk-body">You answered all the questions.</p>'.html_safe
else
@ -24,29 +24,29 @@ module CheckAnswersHelper
end
end
def any_questions_have_summary_card_number?(subsection, case_log)
subsection.applicable_questions(case_log).map(&:check_answers_card_number).compact.length.positive?
def any_questions_have_summary_card_number?(subsection, lettings_log)
subsection.applicable_questions(lettings_log).map(&:check_answers_card_number).compact.length.positive?
end
private
def answered_questions_count(subsection, case_log, current_user)
answered_questions(subsection, case_log, current_user).count
def answered_questions_count(subsection, lettings_log, current_user)
answered_questions(subsection, lettings_log, current_user).count
end
def answered_questions(subsection, case_log, current_user)
total_applicable_questions(subsection, case_log, current_user).select { |q| q.completed?(case_log) }
def answered_questions(subsection, lettings_log, current_user)
total_applicable_questions(subsection, lettings_log, current_user).select { |q| q.completed?(lettings_log) }
end
def total_count(subsection, case_log, current_user)
total_applicable_questions(subsection, case_log, current_user).count
def total_count(subsection, lettings_log, current_user)
total_applicable_questions(subsection, lettings_log, current_user).count
end
def total_applicable_questions(subsection, case_log, current_user)
subsection.applicable_questions(case_log).reject { |q| q.hidden_in_check_answers?(case_log, current_user) }
def total_applicable_questions(subsection, lettings_log, current_user)
subsection.applicable_questions(lettings_log).reject { |q| q.hidden_in_check_answers?(lettings_log, current_user) }
end
def get_answer_label(question, case_log)
question.answer_label(case_log).presence || "<span class=\"app-!-colour-muted\">You didn’t answer this question</span>".html_safe
def get_answer_label(question, lettings_log)
question.answer_label(lettings_log).presence || "<span class=\"app-!-colour-muted\">You didn’t answer this question</span>".html_safe
end
end

10
app/helpers/filters_helper.rb

@ -1,8 +1,8 @@
module FiltersHelper
def filter_selected?(filter, value)
return false unless session[:case_logs_filters]
return false unless session[:lettings_logs_filters]
selected_filters = JSON.parse(session[:case_logs_filters])
selected_filters = JSON.parse(session[:lettings_logs_filters])
return true if selected_filters.blank? && filter == "user" && value == :all
return true if !selected_filters.key?("organisation") && filter == "organisation_select" && value == :all
return true if selected_filters["organisation"].present? && filter == "organisation_select" && value == :specific_org
@ -13,13 +13,13 @@ module FiltersHelper
def status_filters
statuses = {}
CaseLog.statuses.keys.map { |status| statuses[status] = status.humanize }
LettingsLog.statuses.keys.map { |status| statuses[status] = status.humanize }
statuses
end
def selected_option(filter)
return false unless session[:case_logs_filters]
return false unless session[:lettings_logs_filters]
JSON.parse(session[:case_logs_filters])[filter] || ""
JSON.parse(session[:lettings_logs_filters])[filter] || ""
end
end

6
app/helpers/form_page_error_helper.rb

@ -1,6 +1,6 @@
module FormPageErrorHelper
def remove_other_page_errors(case_log, page)
other_page_error_ids = case_log.errors.map(&:attribute) - page.questions.map { |q| q.id.to_sym }.concat([:base])
other_page_error_ids.each { |id| case_log.errors.delete(id) }
def remove_other_page_errors(lettings_log, page)
other_page_error_ids = lettings_log.errors.map(&:attribute) - page.questions.map { |q| q.id.to_sym }.concat([:base])
other_page_error_ids.each { |id| lettings_log.errors.delete(id) }
end
end

12
app/helpers/interruption_screen_helper.rb

@ -1,13 +1,13 @@
module InterruptionScreenHelper
def display_informative_text(informative_text, case_log)
def display_informative_text(informative_text, lettings_log)
return "" unless informative_text["arguments"]
translation_params = {}
informative_text["arguments"].each do |argument|
value = if argument["label"]
case_log.form.get_question(argument["key"], case_log).answer_label(case_log).downcase
lettings_log.form.get_question(argument["key"], lettings_log).answer_label(lettings_log).downcase
else
case_log.public_send(argument["key"])
lettings_log.public_send(argument["key"])
end
translation_params[argument["i18n_template"].to_sym] = value
end
@ -21,16 +21,16 @@ module InterruptionScreenHelper
end
end
def display_title_text(title_text, case_log)
def display_title_text(title_text, lettings_log)
return "" if title_text.nil?
translation_params = {}
arguments = title_text["arguments"] || {}
arguments.each do |argument|
value = if argument["label"]
case_log.form.get_question(argument["key"], case_log).answer_label(case_log).downcase
lettings_log.form.get_question(argument["key"], lettings_log).answer_label(lettings_log).downcase
else
case_log.public_send(argument["key"])
lettings_log.public_send(argument["key"])
end
translation_params[argument["i18n_template"].to_sym] = value
end

6
app/helpers/navigation_items_helper.rb

@ -6,19 +6,19 @@ module NavigationItemsHelper
[
NavigationItem.new("Organisations", organisations_path, organisations_current?(path)),
NavigationItem.new("Users", "/users", users_current?(path)),
NavigationItem.new("Logs", case_logs_path, logs_current?(path)),
NavigationItem.new("Logs", lettings_logs_path, logs_current?(path)),
NavigationItem.new("Schemes", "/schemes", supported_housing_schemes_current?(path)),
]
elsif current_user.data_coordinator? && current_user.organisation.holds_own_stock?
[
NavigationItem.new("Logs", case_logs_path, logs_current?(path)),
NavigationItem.new("Logs", lettings_logs_path, logs_current?(path)),
NavigationItem.new("Schemes", "/schemes", subnav_supported_housing_schemes_path?(path)),
NavigationItem.new("Users", users_organisation_path(current_user.organisation), subnav_users_path?(path)),
NavigationItem.new("About your organisation", "/organisations/#{current_user.organisation.id}", subnav_details_path?(path)),
]
else
[
NavigationItem.new("Logs", case_logs_path, logs_current?(path)),
NavigationItem.new("Logs", lettings_logs_path, logs_current?(path)),
NavigationItem.new("Users", users_organisation_path(current_user.organisation), subnav_users_path?(path)),
NavigationItem.new("About your organisation", "/organisations/#{current_user.organisation.id}", subnav_details_path?(path)),
]

2
app/helpers/question_attribute_helper.rb

@ -16,7 +16,7 @@ private
{
"data-controller": "numeric-question",
"data-action": "input->numeric-question#calculateFields",
"data-target": "case-log-#{question.result_field.to_s.dasherize}-field",
"data-target": "lettings-log-#{question.result_field.to_s.dasherize}-field",
"data-calculated": question.fields_to_add.to_json,
}
end

32
app/helpers/tasklist_helper.rb

@ -1,36 +1,36 @@
module TasklistHelper
include GovukLinkHelper
def get_next_incomplete_section(case_log)
case_log.form.subsections.find { |subsection| subsection.is_incomplete?(case_log) }
def get_next_incomplete_section(lettings_log)
lettings_log.form.subsections.find { |subsection| subsection.is_incomplete?(lettings_log) }
end
def get_subsections_count(case_log, status = :all)
return case_log.form.subsections.count { |subsection| subsection.applicable_questions(case_log).count.positive? } if status == :all
def get_subsections_count(lettings_log, status = :all)
return lettings_log.form.subsections.count { |subsection| subsection.applicable_questions(lettings_log).count.positive? } if status == :all
case_log.form.subsections.count { |subsection| subsection.status(case_log) == status && subsection.applicable_questions(case_log).count.positive? }
lettings_log.form.subsections.count { |subsection| subsection.status(lettings_log) == status && subsection.applicable_questions(lettings_log).count.positive? }
end
def next_page_or_check_answers(subsection, case_log, current_user)
path = if subsection.is_started?(case_log)
"case_log_#{subsection.id}_check_answers_path"
def next_page_or_check_answers(subsection, lettings_log, current_user)
path = if subsection.is_started?(lettings_log)
"lettings_log_#{subsection.id}_check_answers_path"
else
"case_log_#{next_question_page(subsection, case_log, current_user)}_path"
"lettings_log_#{next_question_page(subsection, lettings_log, current_user)}_path"
end
send(path, case_log)
send(path, lettings_log)
end
def next_question_page(subsection, case_log, current_user)
if subsection.pages.first.routed_to?(case_log, current_user)
def next_question_page(subsection, lettings_log, current_user)
if subsection.pages.first.routed_to?(lettings_log, current_user)
subsection.pages.first.id
else
case_log.form.next_page(subsection.pages.first, case_log, current_user)
lettings_log.form.next_page(subsection.pages.first, lettings_log, current_user)
end
end
def subsection_link(subsection, case_log, current_user)
if subsection.status(case_log) != :cannot_start_yet
next_page_path = next_page_or_check_answers(subsection, case_log, current_user).to_s
def subsection_link(subsection, lettings_log, current_user)
if subsection.status(lettings_log) != :cannot_start_yet
next_page_path = next_page_or_check_answers(subsection, lettings_log, current_user).to_s
govuk_link_to(subsection.label, next_page_path.dasherize, aria: { describedby: subsection.id.dasherize })
else
subsection.label

8
app/models/bulk_upload.rb

@ -22,20 +22,20 @@ class BulkUpload
sheet = xlsx.sheet(0)
last_row = sheet.last_row
if last_row < FIRST_DATA_ROW
errors.add(:case_log_bulk_upload, "No data found")
errors.add(:lettings_log_bulk_upload, "No data found")
else
data_range = FIRST_DATA_ROW..last_row
data_range.map do |row_num|
row = sheet.row(row_num)
# owning_organisation = Organisation.find(row[111])
# managing_organisation = Organisation.find(row[113])
case_log = CaseLog.create!(
lettings_log = LettingsLog.create!(
owning_organisation: current_user.organisation,
managing_organisation: current_user.organisation,
created_by: current_user,
)
map_row(row).each do |attr_key, attr_val|
update = case_log.update(attr_key => attr_val)
update = lettings_log.update(attr_key => attr_val)
unless update
# TODO: determine what to do when a bulk upload contains field values that don't pass validations
end
@ -50,7 +50,7 @@ class BulkUpload
if SPREADSHEET_CONTENT_TYPES.include?(@content_type)
true
else
errors.add(:case_log_bulk_upload, "Invalid file type")
errors.add(:lettings_log_bulk_upload, "Invalid file type")
false
end
end

25
app/models/derived_variables/case_log_variables.rb → app/models/derived_variables/lettings_log_variables.rb

@ -1,4 +1,4 @@
module DerivedVariables::CaseLogVariables
module DerivedVariables::LettingsLogVariables
RENT_TYPE_MAPPING = { 0 => 1, 1 => 2, 2 => 2, 3 => 3, 4 => 3, 5 => 3 }.freeze
def scheme_has_multiple_locations?
@ -43,10 +43,8 @@ module DerivedVariables::CaseLogVariables
self.has_benefits = get_has_benefits
self.tshortfall_known = 0 if tshortfall
self.nocharge = household_charge&.zero? ? 1 : 0
self.housingneeds = get_housingneeds
if is_renewal?
self.underoccupation_benefitcap = 2 if collection_start_year == 2021
self.homeless = 1
self.referral = 0
self.waityear = 2
if is_general_needs?
@ -70,6 +68,8 @@ module DerivedVariables::CaseLogVariables
self.voiddate = startdate
end
end
set_housingneeds_fields if housingneeds?
end
private
@ -186,4 +186,23 @@ private
self.location = scheme.locations.first
end
end
def set_housingneeds_fields
self.housingneeds_a = fully_wheelchair_accessible? ? 1 : 0
self.housingneeds_b = essential_wheelchair_access? ? 1 : 0
self.housingneeds_c = level_access_housing? ? 1 : 0
self.housingneeds_f = other_housingneeds? ? 1 : 0
set_housingneeds_values_to_zero unless has_housingneeds?
self.housingneeds_g = no_housingneeds? ? 1 : 0
self.housingneeds_h = unknown_housingneeds? ? 1 : 0
end
def set_housingneeds_values_to_zero
self.housingneeds_a = 0
self.housingneeds_b = 0
self.housingneeds_c = 0
self.housingneeds_f = 0
self.housingneeds_g = 0
self.housingneeds_h = 0
end
end

82
app/models/form.rb

@ -29,9 +29,9 @@ class Form
pages.find { |p| p.id == id.to_s.underscore }
end
def get_question(id, case_log, current_user = nil)
def get_question(id, lettings_log, current_user = nil)
all_questions = questions.select { |q| q.id == id.to_s.underscore }
routed_question = all_questions.find { |q| q.page.routed_to?(case_log, current_user) } if case_log
routed_question = all_questions.find { |q| q.page.routed_to?(lettings_log, current_user) } if lettings_log
routed_question || all_questions[0]
end
@ -39,47 +39,47 @@ class Form
subsections.find { |s| s.pages.find { |p| p.id == page.id } }
end
def next_page(page, case_log, current_user)
def next_page(page, lettings_log, current_user)
page_ids = subsection_for_page(page).pages.map(&:id)
page_index = page_ids.index(page.id)
page_id = if page.id.include?("value_check") && case_log[page.questions[0].id] == 1 && page.routed_to?(case_log, current_user)
previous_page(page_ids, page_index, case_log, current_user)
page_id = if page.id.include?("value_check") && lettings_log[page.questions[0].id] == 1 && page.routed_to?(lettings_log, current_user)
previous_page(page_ids, page_index, lettings_log, current_user)
else
page_ids[page_index + 1]
end
nxt_page = get_page(page_id)
return :check_answers if nxt_page.nil?
return nxt_page.id if nxt_page.routed_to?(case_log, current_user)
return nxt_page.id if nxt_page.routed_to?(lettings_log, current_user)
next_page(nxt_page, case_log, current_user)
next_page(nxt_page, lettings_log, current_user)
end
def next_page_redirect_path(page, case_log, current_user)
nxt_page = next_page(page, case_log, current_user)
def next_page_redirect_path(page, lettings_log, current_user)
nxt_page = next_page(page, lettings_log, current_user)
if nxt_page == :check_answers
"case_log_#{subsection_for_page(page).id}_check_answers_path"
"lettings_log_#{subsection_for_page(page).id}_check_answers_path"
else
"case_log_#{nxt_page}_path"
"lettings_log_#{nxt_page}_path"
end
end
def next_incomplete_section_redirect_path(subsection, case_log)
def next_incomplete_section_redirect_path(subsection, lettings_log)
subsection_ids = subsections.map(&:id)
if case_log.status == "completed"
if lettings_log.status == "completed"
return first_question_in_last_subsection(subsection_ids)
end
next_subsection = next_subsection(subsection, case_log, subsection_ids)
next_subsection = next_subsection(subsection, lettings_log, subsection_ids)
case next_subsection.status(case_log)
case next_subsection.status(lettings_log)
when :completed
next_incomplete_section_redirect_path(next_subsection, case_log)
next_incomplete_section_redirect_path(next_subsection, lettings_log)
when :in_progress
"#{next_subsection.id}/check_answers".dasherize
when :not_started
first_question_in_subsection = next_subsection.pages.find { |page| page.routed_to?(case_log, nil) }.id
first_question_in_subsection = next_subsection.pages.find { |page| page.routed_to?(lettings_log, nil) }.id
first_question_in_subsection.to_s.dasherize
else
"error"
@ -92,21 +92,21 @@ class Form
first_question_in_subsection.to_s.dasherize
end
def next_subsection(subsection, case_log, subsection_ids)
def next_subsection(subsection, lettings_log, subsection_ids)
next_subsection_id_index = subsection_ids.index(subsection.id) + 1
next_subsection = get_subsection(subsection_ids[next_subsection_id_index])
if subsection_ids[subsection_ids.length - 1] == subsection.id && case_log.status != "completed"
if subsection_ids[subsection_ids.length - 1] == subsection.id && lettings_log.status != "completed"
next_subsection = get_subsection(subsection_ids[0])
end
next_subsection
end
def all_subsections_except_declaration_completed?(case_log)
def all_subsections_except_declaration_completed?(lettings_log)
subsection_ids = subsections.map(&:id)
subsection_ids.delete_at(subsection_ids.length - 1)
return true if subsection_ids.all? { |subsection_id| get_subsection(subsection_id).status(case_log) == :completed }
return true if subsection_ids.all? { |subsection_id| get_subsection(subsection_id).status(lettings_log) == :completed }
false
end
@ -118,26 +118,26 @@ class Form
}.flatten
end
def invalidated_pages(case_log, current_user = nil)
pages.reject { |p| p.routed_to?(case_log, current_user) }
def invalidated_pages(lettings_log, current_user = nil)
pages.reject { |p| p.routed_to?(lettings_log, current_user) }
end
def invalidated_questions(case_log)
invalidated_page_questions(case_log) + invalidated_conditional_questions(case_log)
def invalidated_questions(lettings_log)
invalidated_page_questions(lettings_log) + invalidated_conditional_questions(lettings_log)
end
def invalidated_page_questions(case_log, current_user = nil)
# we're already treating these fields as a special case and reset their values upon saving a case_log
def invalidated_page_questions(lettings_log, current_user = nil)
# we're already treating these fields as a special case and reset their values upon saving a lettings_log
callback_questions = %w[postcode_known la ppcodenk previous_la_known prevloc postcode_full ppostcode_full location_id]
questions.reject { |q| q.page.routed_to?(case_log, current_user) || q.derived? || callback_questions.include?(q.id) } || []
questions.reject { |q| q.page.routed_to?(lettings_log, current_user) || q.derived? || callback_questions.include?(q.id) } || []
end
def enabled_page_questions(case_log)
questions - invalidated_page_questions(case_log)
def enabled_page_questions(lettings_log)
questions - invalidated_page_questions(lettings_log)
end
def invalidated_conditional_questions(case_log)
questions.reject { |q| q.enabled?(case_log) } || []
def invalidated_conditional_questions(lettings_log)
questions.reject { |q| q.enabled?(lettings_log) } || []
end
def readonly_questions
@ -148,18 +148,18 @@ class Form
questions.select { |q| q.type == "numeric" }
end
def previous_page(page_ids, page_index, case_log, current_user)
def previous_page(page_ids, page_index, lettings_log, current_user)
prev_page = get_page(page_ids[page_index - 1])
return prev_page.id if prev_page.routed_to?(case_log, current_user)
return prev_page.id if prev_page.routed_to?(lettings_log, current_user)
previous_page(page_ids, page_index - 1, case_log, current_user)
previous_page(page_ids, page_index - 1, lettings_log, current_user)
end
def send_chain(arr, case_log)
Array(arr).inject(case_log) { |o, a| o.public_send(*a) }
def send_chain(arr, lettings_log)
Array(arr).inject(lettings_log) { |o, a| o.public_send(*a) }
end
def depends_on_met(depends_on, case_log)
def depends_on_met(depends_on, lettings_log)
return true unless depends_on
depends_on.any? do |conditions_set|
@ -169,12 +169,12 @@ class Form
if value.is_a?(Hash) && value.key?("operator")
operator = value["operator"]
operand = value["operand"]
case_log[question]&.send(operator, operand)
lettings_log[question]&.send(operator, operand)
else
parts = question.split(".")
case_log_value = send_chain(parts, case_log)
lettings_log_value = send_chain(parts, lettings_log)
value.nil? ? case_log_value == value : !case_log_value.nil? && case_log_value == value
value.nil? ? lettings_log_value == value : !lettings_log_value.nil? && lettings_log_value == value
end
end
end

4
app/models/form/page.rb

@ -18,10 +18,10 @@ class Form::Page
delegate :form, to: :subsection
def routed_to?(case_log, _current_user)
def routed_to?(lettings_log, _current_user)
return true unless depends_on || subsection.depends_on
subsection.enabled?(case_log) && form.depends_on_met(depends_on, case_log)
subsection.enabled?(lettings_log) && form.depends_on_met(depends_on, lettings_log)
end
def non_conditional_questions

98
app/models/form/question.rb

@ -44,31 +44,31 @@ class Form::Question
delegate :subsection, to: :page
delegate :form, to: :subsection
def answer_label(case_log)
return checkbox_answer_label(case_log) if type == "checkbox"
return case_log[id]&.to_formatted_s(:govuk_date).to_s if type == "date"
def answer_label(lettings_log)
return checkbox_answer_label(lettings_log) if type == "checkbox"
return lettings_log[id]&.to_formatted_s(:govuk_date).to_s if type == "date"
answer = label_from_value(case_log[id]) if case_log[id].present?
answer_label = [prefix, format_value(answer), suffix_label(case_log)].join("") if answer
answer = label_from_value(lettings_log[id]) if lettings_log[id].present?
answer_label = [prefix, format_value(answer), suffix_label(lettings_log)].join("") if answer
return answer_label if answer_label
has_inferred_check_answers_value?(case_log) ? inferred_check_answers_value["value"] : ""
has_inferred_check_answers_value?(lettings_log) ? inferred_check_answers_value["value"] : ""
end
def get_inferred_answers(case_log)
def get_inferred_answers(lettings_log)
return [] unless inferred_answers
enabled_inferred_answers(inferred_answers, case_log).keys.map do |question_id|
question = form.get_question(question_id, case_log)
enabled_inferred_answers(inferred_answers, lettings_log).keys.map do |question_id|
question = form.get_question(question_id, lettings_log)
if question.present?
question.label_from_value(case_log[question_id])
question.label_from_value(lettings_log[question_id])
else
Array(question_id.to_s.split(".")).inject(case_log) { |log, method| log.present? ? log.public_send(*method) : "" }
Array(question_id.to_s.split(".")).inject(lettings_log) { |log, method| log.present? ? log.public_send(*method) : "" }
end
end
end
def get_extra_check_answer_value(_case_log)
def get_extra_check_answer_value(_lettings_log)
nil
end
@ -76,59 +76,59 @@ class Form::Question
!!readonly
end
def enabled?(case_log)
def enabled?(lettings_log)
return true if conditional_on.blank?
conditional_on.all? { |condition| evaluate_condition(condition, case_log) }
conditional_on.all? { |condition| evaluate_condition(condition, lettings_log) }
end
def hidden_in_check_answers?(case_log, _current_user = nil)
def hidden_in_check_answers?(lettings_log, _current_user = nil)
if hidden_in_check_answers.is_a?(Hash)
form.depends_on_met(hidden_in_check_answers["depends_on"], case_log)
form.depends_on_met(hidden_in_check_answers["depends_on"], lettings_log)
else
hidden_in_check_answers
end
end
def displayed_to_user?(case_log)
page.routed_to?(case_log, nil) && enabled?(case_log)
def displayed_to_user?(lettings_log)
page.routed_to?(lettings_log, nil) && enabled?(lettings_log)
end
def derived?
!!derived
end
def has_inferred_check_answers_value?(case_log)
return true if selected_answer_option_is_derived?(case_log)
return inferred_check_answers_value["condition"].values[0] == case_log[inferred_check_answers_value["condition"].keys[0]] if inferred_check_answers_value.present?
def has_inferred_check_answers_value?(lettings_log)
return true if selected_answer_option_is_derived?(lettings_log)
return inferred_check_answers_value["condition"].values[0] == lettings_log[inferred_check_answers_value["condition"].keys[0]] if inferred_check_answers_value.present?
false
end
def displayed_answer_options(case_log)
def displayed_answer_options(lettings_log)
answer_options.select do |_key, val|
!val.is_a?(Hash) || !val["depends_on"] || form.depends_on_met(val["depends_on"], case_log)
!val.is_a?(Hash) || !val["depends_on"] || form.depends_on_met(val["depends_on"], lettings_log)
end
end
def action_text(case_log)
if has_inferred_check_answers_value?(case_log)
def action_text(lettings_log)
if has_inferred_check_answers_value?(lettings_log)
"Change"
elsif type == "checkbox"
answer_options.keys.any? { |key| value_is_yes?(case_log[key]) } ? "Change" : "Answer"
answer_options.keys.any? { |key| value_is_yes?(lettings_log[key]) } ? "Change" : "Answer"
else
case_log[id].blank? ? "Answer" : "Change"
lettings_log[id].blank? ? "Answer" : "Change"
end
end
def action_href(case_log, page_id)
"/logs/#{case_log.id}/#{page_id.to_s.dasherize}?referrer=check_answers"
def action_href(lettings_log, page_id)
"/logs/#{lettings_log.id}/#{page_id.to_s.dasherize}?referrer=check_answers"
end
def completed?(case_log)
return answer_options.keys.any? { |key| value_is_yes?(case_log[key]) } if type == "checkbox"
def completed?(lettings_log)
return answer_options.keys.any? { |key| value_is_yes?(lettings_log[key]) } if type == "checkbox"
case_log[id].present? || !case_log.respond_to?(id.to_sym) || has_inferred_display_value?(case_log)
lettings_log[id].present? || !lettings_log.respond_to?(id.to_sym) || has_inferred_display_value?(lettings_log)
end
def value_from_label(label)
@ -203,7 +203,7 @@ class Form::Question
I18n.t("validations.not_answered", question: display_label.downcase)
end
def suffix_label(case_log)
def suffix_label(lettings_log)
return "" unless suffix
return suffix if suffix.is_a?(String)
@ -213,7 +213,7 @@ class Form::Question
condition = s["depends_on"]
next unless condition
answer = case_log.send(condition.keys.first)
answer = lettings_log.send(condition.keys.first)
if answer == condition.values.first
label = s["label"]
end
@ -239,10 +239,10 @@ class Form::Question
resource.hint
end
def answer_selected?(case_log, answer)
def answer_selected?(lettings_log, answer)
return false unless type == "select"
case_log[id].to_s == answer.id.to_s
lettings_log[id].to_s == answer.id.to_s
end
def top_guidance?
@ -255,20 +255,20 @@ class Form::Question
private
def selected_answer_option_is_derived?(case_log)
selected_option = answer_options&.dig(case_log[id].to_s.presence)
selected_option.is_a?(Hash) && selected_option["depends_on"] && form.depends_on_met(selected_option["depends_on"], case_log)
def selected_answer_option_is_derived?(lettings_log)
selected_option = answer_options&.dig(lettings_log[id].to_s.presence)
selected_option.is_a?(Hash) && selected_option["depends_on"] && form.depends_on_met(selected_option["depends_on"], lettings_log)
end
def has_inferred_display_value?(case_log)
inferred_check_answers_value.present? && case_log[inferred_check_answers_value["condition"].keys.first] == inferred_check_answers_value["condition"].values.first
def has_inferred_display_value?(lettings_log)
inferred_check_answers_value.present? && lettings_log[inferred_check_answers_value["condition"].keys.first] == inferred_check_answers_value["condition"].values.first
end
def checkbox_answer_label(case_log)
def checkbox_answer_label(lettings_log)
answer = []
return "Yes" if id == "declaration" && value_is_yes?(case_log["declaration"])
return "Yes" if id == "declaration" && value_is_yes?(lettings_log["declaration"])
answer_options.each { |key, options| value_is_yes?(case_log[key]) ? answer << options["value"] : nil }
answer_options.each { |key, options| value_is_yes?(lettings_log[key]) ? answer << options["value"] : nil }
answer.join(", ")
end
@ -282,21 +282,21 @@ private
end
end
def evaluate_condition(condition, case_log)
def evaluate_condition(condition, lettings_log)
case page.questions.find { |q| q.id == condition[:from] }.type
when "numeric"
operator = condition[:cond][/[<>=]+/].to_sym
operand = condition[:cond][/\d+/].to_i
case_log[condition[:from]].present? && case_log[condition[:from]].send(operator, operand)
lettings_log[condition[:from]].present? && lettings_log[condition[:from]].send(operator, operand)
when "text", "radio", "select"
case_log[condition[:from]].present? && condition[:cond].include?(case_log[condition[:from]])
lettings_log[condition[:from]].present? && condition[:cond].include?(lettings_log[condition[:from]])
else
raise "Not implemented yet"
end
end
def enabled_inferred_answers(inferred_answers, case_log)
inferred_answers.filter { |_key, value| value.all? { |condition_key, condition_value| case_log[condition_key] == condition_value } }
def enabled_inferred_answers(inferred_answers, lettings_log)
inferred_answers.filter { |_key, value| value.all? { |condition_key, condition_value| lettings_log[condition_key] == condition_value } }
end
RADIO_YES_VALUE = {

2
app/models/form/setup/pages/created_by.rb

@ -13,7 +13,7 @@ class Form::Setup::Pages::CreatedBy < ::Form::Page
]
end
def routed_to?(_case_log, current_user)
def routed_to?(_lettings_log, current_user)
!!current_user&.support?
end
end

2
app/models/form/setup/pages/organisation.rb

@ -13,7 +13,7 @@ class Form::Setup::Pages::Organisation < ::Form::Page
]
end
def routed_to?(_case_log, current_user)
def routed_to?(_lettings_log, current_user)
!!current_user&.support?
end
end

10
app/models/form/setup/questions/created_by_id.rb

@ -19,10 +19,10 @@ class Form::Setup::Questions::CreatedById < ::Form::Question
end
end
def displayed_answer_options(case_log)
return answer_options unless case_log.owning_organisation
def displayed_answer_options(lettings_log)
return answer_options unless lettings_log.owning_organisation
user_ids = case_log.owning_organisation.users.pluck(:id) + [""]
user_ids = lettings_log.owning_organisation.users.pluck(:id) + [""]
answer_options.select { |k, _v| user_ids.include?(k) }
end
@ -32,7 +32,7 @@ class Form::Setup::Questions::CreatedById < ::Form::Question
answer_options[value]
end
def hidden_in_check_answers?(_case_log, current_user)
def hidden_in_check_answers?(_lettings_log, current_user)
!current_user.support?
end
@ -42,7 +42,7 @@ class Form::Setup::Questions::CreatedById < ::Form::Question
private
def selected_answer_option_is_derived?(_case_log)
def selected_answer_option_is_derived?(_lettings_log)
false
end
end

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

@ -23,28 +23,28 @@ class Form::Setup::Questions::LocationId < ::Form::Question
end
end
def displayed_answer_options(case_log)
return {} unless case_log.scheme
def displayed_answer_options(lettings_log)
return {} unless lettings_log.scheme
scheme_location_ids = case_log.scheme.locations.pluck(:id)
scheme_location_ids = lettings_log.scheme.locations.pluck(:id)
answer_options.select { |k, _v| scheme_location_ids.include?(k.to_i) }
end
def hidden_in_check_answers?(case_log, _current_user = nil)
!supported_housing_selected?(case_log)
def hidden_in_check_answers?(lettings_log, _current_user = nil)
!supported_housing_selected?(lettings_log)
end
def get_extra_check_answer_value(case_log)
case_log.form.get_question("la", nil).label_from_value(case_log.la)
def get_extra_check_answer_value(lettings_log)
lettings_log.form.get_question("la", nil).label_from_value(lettings_log.la)
end
private
def supported_housing_selected?(case_log)
case_log.needstype == 2
def supported_housing_selected?(lettings_log)
lettings_log.needstype == 2
end
def selected_answer_option_is_derived?(_case_log)
def selected_answer_option_is_derived?(_lettings_log)
false
end
end

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

@ -19,7 +19,7 @@ class Form::Setup::Questions::OwningOrganisationId < ::Form::Question
end
end
def displayed_answer_options(_case_log)
def displayed_answer_options(_lettings_log)
answer_options
end
@ -29,7 +29,7 @@ class Form::Setup::Questions::OwningOrganisationId < ::Form::Question
answer_options[value]
end
def hidden_in_check_answers?(_case_log, current_user)
def hidden_in_check_answers?(_lettings_log, current_user)
!current_user.support?
end
@ -39,7 +39,7 @@ class Form::Setup::Questions::OwningOrganisationId < ::Form::Question
private
def selected_answer_option_is_derived?(_case_log)
def selected_answer_option_is_derived?(_lettings_log)
false
end
end

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

@ -20,8 +20,8 @@ class Form::Setup::Questions::SchemeId < ::Form::Question
end
end
def displayed_answer_options(case_log)
organisation = case_log.owning_organisation || case_log.created_by&.organisation
def displayed_answer_options(lettings_log)
organisation = lettings_log.owning_organisation || lettings_log.created_by&.organisation
schemes = organisation ? Scheme.select(:id).where(owning_organisation_id: organisation.id, confirmed: true) : Scheme.select(:id).where(confirmed: true)
filtered_scheme_ids = schemes.joins(:locations).merge(Location.where("startdate <= ? or startdate IS NULL", Time.zone.today)).map(&:id)
answer_options.select do |k, _v|
@ -29,21 +29,21 @@ class Form::Setup::Questions::SchemeId < ::Form::Question
end
end
def hidden_in_check_answers?(case_log, _current_user = nil)
!supported_housing_selected?(case_log)
def hidden_in_check_answers?(lettings_log, _current_user = nil)
!supported_housing_selected?(lettings_log)
end
def answer_selected?(case_log, answer)
case_log[id] == answer.name || case_log[id] == answer.resource
def answer_selected?(lettings_log, answer)
lettings_log[id] == answer.name || lettings_log[id] == answer.resource
end
private
def supported_housing_selected?(case_log)
case_log.needstype == 2
def supported_housing_selected?(lettings_log)
lettings_log.needstype == 2
end
def selected_answer_option_is_derived?(_case_log)
def selected_answer_option_is_derived?(_lettings_log)
false
end
end

4
app/models/form/setup/subsections/setup.rb

@ -21,11 +21,11 @@ class Form::Subsections::Setup < ::Form::Subsection
]
end
def applicable_questions(case_log)
def applicable_questions(lettings_log)
questions.select { |q| support_only_questions.include?(q.id) } + super
end
def enabled?(_case_log)
def enabled?(_lettings_log)
true
end

28
app/models/form/subsection.rb

@ -17,40 +17,40 @@ class Form::Subsection
@questions ||= pages.flat_map(&:questions)
end
def enabled?(case_log)
def enabled?(lettings_log)
return true unless depends_on
depends_on.any? do |conditions_set|
conditions_set.all? do |subsection_id, dependent_status|
form.get_subsection(subsection_id).status(case_log) == dependent_status.to_sym
form.get_subsection(subsection_id).status(lettings_log) == dependent_status.to_sym
end
end
end
def status(case_log)
unless enabled?(case_log)
def status(lettings_log)
unless enabled?(lettings_log)
return :cannot_start_yet
end
qs = applicable_questions(case_log)
qs_optional_removed = qs.reject { |q| case_log.optional_fields.include?(q.id) }
return :not_started if qs.count.positive? && qs.all? { |question| case_log[question.id].blank? || question.read_only? || question.derived? }
return :completed if qs_optional_removed.all? { |question| question.completed?(case_log) }
qs = applicable_questions(lettings_log)
qs_optional_removed = qs.reject { |q| lettings_log.optional_fields.include?(q.id) }
return :not_started if qs.count.positive? && qs.all? { |question| lettings_log[question.id].blank? || question.read_only? || question.derived? }
return :completed if qs_optional_removed.all? { |question| question.completed?(lettings_log) }
:in_progress
end
def is_incomplete?(case_log)
%i[not_started in_progress].include?(status(case_log))
def is_incomplete?(lettings_log)
%i[not_started in_progress].include?(status(lettings_log))
end
def is_started?(case_log)
%i[in_progress completed].include?(status(case_log))
def is_started?(lettings_log)
%i[in_progress completed].include?(status(lettings_log))
end
def applicable_questions(case_log)
def applicable_questions(lettings_log)
questions.select do |q|
(q.displayed_to_user?(case_log) && !q.derived?) || q.has_inferred_check_answers_value?(case_log)
(q.displayed_to_user?(lettings_log) && !q.derived?) || q.has_inferred_check_answers_value?(lettings_log)
end
end
end

63
app/models/case_log.rb → app/models/lettings_log.rb

@ -1,4 +1,4 @@
class CaseLogValidator < ActiveModel::Validator
class LettingsLogValidator < ActiveModel::Validator
# Validations methods need to be called 'validate_' to run on model save
# or form page submission
include Validations::SetupValidations
@ -14,13 +14,13 @@ class CaseLogValidator < ActiveModel::Validator
end
end
class CaseLog < ApplicationRecord
class LettingsLog < ApplicationRecord
include Validations::SoftValidations
include DerivedVariables::CaseLogVariables
include DerivedVariables::LettingsLogVariables
has_paper_trail
validates_with CaseLogValidator
validates_with LettingsLogValidator
before_validation :recalculate_start_year!, if: :startdate_changed?
before_validation :reset_scheme_location!, if: :scheme_changed?, unless: :location_changed?
before_validation :process_postcode_changes!, if: :postcode_full_changed?
@ -474,7 +474,7 @@ class CaseLog < ApplicationRecord
end
def self.to_csv(user = nil)
Csv::CaseLogCsvService.new(user).to_csv
Csv::LettingsLogCsvService.new(user).to_csv
end
def soft_min_for_period
@ -575,7 +575,6 @@ private
def reset_derived_questions
dependent_questions = { waityear: [{ key: :renewal, value: 0 }],
homeless: [{ key: :renewal, value: 0 }],
referral: [{ key: :renewal, value: 0 }],
underoccupation_benefitcap: [{ key: :renewal, value: 0 }],
wchair: [{ key: :needstype, value: 1 }],
@ -701,30 +700,6 @@ private
end
end
def get_housingneeds
return 1 if has_housingneeds?
return 2 if no_housingneeds?
return 3 if unknown_housingneeds?
end
def has_housingneeds?
if [housingneeds_a, housingneeds_b, housingneeds_c, housingneeds_f].any?(1)
1
end
end
def no_housingneeds?
if housingneeds_g == 1
1
end
end
def unknown_housingneeds?
if housingneeds_h == 1
1
end
end
def all_fields_completed?
subsection_statuses = form.subsections.map { |subsection| subsection.status(self) }.uniq
subsection_statuses == [:completed]
@ -762,4 +737,32 @@ private
def upcase_and_remove_whitespace(string)
string.present? ? string.upcase.gsub(/\s+/, "") : string
end
def fully_wheelchair_accessible?
housingneeds_type.present? && housingneeds_type.zero?
end
def essential_wheelchair_access?
housingneeds_type == 1
end
def level_access_housing?
housingneeds_type == 2
end
def other_housingneeds?
housingneeds_other == 1
end
def has_housingneeds?
housingneeds == 1
end
def no_housingneeds?
housingneeds == 2
end
def unknown_housingneeds?
housingneeds == 3
end
end

16
app/models/organisation.rb

@ -1,7 +1,7 @@
class Organisation < ApplicationRecord
has_many :users, dependent: :delete_all
has_many :owned_case_logs, class_name: "CaseLog", foreign_key: "owning_organisation_id", dependent: :delete_all
has_many :managed_case_logs, class_name: "CaseLog", foreign_key: "managing_organisation_id"
has_many :owned_lettings_logs, class_name: "LettingsLog", foreign_key: "owning_organisation_id", dependent: :delete_all
has_many :managed_lettings_logs, class_name: "LettingsLog", foreign_key: "managing_organisation_id"
has_many :data_protection_confirmations
has_many :organisation_rent_periods
has_many :owned_schemes, class_name: "Scheme", foreign_key: "owning_organisation_id", dependent: :delete_all
@ -28,16 +28,16 @@ class Organisation < ApplicationRecord
validates :name, presence: { message: I18n.t("validations.organisation.name_missing") }
validates :provider_type, presence: { message: I18n.t("validations.organisation.provider_type_missing") }
def case_logs
CaseLog.filter_by_organisation(self)
def lettings_logs
LettingsLog.filter_by_organisation(self)
end
def completed_case_logs
case_logs.completed
def completed_lettings_logs
lettings_logs.completed
end
def not_completed_case_logs
case_logs.not_completed
def not_completed_lettings_logs
lettings_logs.not_completed
end
def address_string

2
app/models/scheme.rb

@ -2,7 +2,7 @@ class Scheme < ApplicationRecord
belongs_to :owning_organisation, class_name: "Organisation"
belongs_to :managing_organisation, optional: true, class_name: "Organisation"
has_many :locations, dependent: :delete_all
has_many :case_logs, dependent: :delete_all
has_many :lettings_logs, class_name: "LettingsLog", dependent: :delete_all
has_paper_trail

20
app/models/user.rb

@ -7,8 +7,8 @@ class User < ApplicationRecord
# Marked as optional because we validate organisation_id below instead so that
# the error message is linked to the right field on the form
belongs_to :organisation, optional: true
has_many :owned_case_logs, through: :organisation, dependent: :delete_all
has_many :managed_case_logs, through: :organisation
has_many :owned_lettings_logs, through: :organisation, dependent: :delete_all
has_many :managed_lettings_logs, through: :organisation
validates :name, presence: true
validates :email, presence: true
@ -50,20 +50,20 @@ class User < ApplicationRecord
scope :search_by, ->(param) { search_by_name(param).or(search_by_email(param)) }
scope :sorted_by_organisation_and_role, -> { joins(:organisation).order("organisations.name", role: :desc, name: :asc) }
def case_logs
def lettings_logs
if support?
CaseLog.all
LettingsLog.all
else
CaseLog.filter_by_organisation(organisation)
LettingsLog.filter_by_organisation(organisation)
end
end
def completed_case_logs
case_logs.completed
def completed_lettings_logs
lettings_logs.completed
end
def not_completed_case_logs
case_logs.not_completed
def not_completed_lettings_logs
lettings_logs.not_completed
end
def is_key_contact?
@ -131,7 +131,7 @@ class User < ApplicationRecord
ROLES.except(:support)
end
def case_logs_filters(specific_org: false)
def lettings_logs_filters(specific_org: false)
if support? && !specific_org
%w[status years user organisation]
else

10
app/models/validations/household_validations.rb

@ -54,16 +54,6 @@ module Validations::HouseholdValidations
validate_person_age_matches_economic_status(record, 1)
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]
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 }
record.errors.add :accessibility_requirements, I18n.t("validations.household.housingneeds_a.one_or_two_choices")
end
end
end
def validate_condition_effects(record)
all_options = [record.illness_type_1, record.illness_type_2, record.illness_type_3, record.illness_type_4, record.illness_type_5, record.illness_type_6, record.illness_type_7, record.illness_type_8, record.illness_type_9, record.illness_type_10]
if all_options.count(1) >= 1 && household_no_illness?(record)

17
app/services/csv/case_log_csv_service.rb → app/services/csv/lettings_log_csv_service.rb

@ -1,6 +1,6 @@
module Csv
class CaseLogCsvService
CSV_FIELDS_TO_OMIT = %w[hhmemb net_income_value_check sale_or_letting first_time_property_let_as_social_housing renttype needstype postcode_known is_la_inferred totchild totelder totadult net_income_known is_carehome previous_la_known is_previous_la_inferred age1_known age2_known age3_known age4_known age5_known age6_known age7_known age8_known letting_allocation_unknown details_known_2 details_known_3 details_known_4 details_known_5 details_known_6 details_known_7 details_known_8 rent_type wrent wscharge wpschrge wsupchrg wtcharge wtshortfall rent_value_check old_form_id old_id retirement_value_check tshortfall_known pregnancy_value_check hhtype new_old vacdays scheme_id location_id la prevloc].freeze
class LettingsLogCsvService
CSV_FIELDS_TO_OMIT = %w[hhmemb net_income_value_check sale_or_letting first_time_property_let_as_social_housing renttype needstype postcode_known is_la_inferred totchild totelder totadult net_income_known is_carehome previous_la_known is_previous_la_inferred age1_known age2_known age3_known age4_known age5_known age6_known age7_known age8_known letting_allocation_unknown details_known_2 details_known_3 details_known_4 details_known_5 details_known_6 details_known_7 details_known_8 rent_type wrent wscharge wpschrge wsupchrg wtcharge wtshortfall rent_value_check old_form_id old_id retirement_value_check tshortfall_known pregnancy_value_check hhtype new_old vacdays].freeze
def initialize(user)
@user = user
@ -11,7 +11,7 @@ module Csv
CSV.generate(headers: true) do |csv|
csv << @attributes
CaseLog.all.find_each do |record|
LettingsLog.all.find_each do |record|
csv << @attributes.map do |att|
if %w[la prevloc].include? att
label_from_value(record.send(att))
@ -38,20 +38,19 @@ module Csv
metadata_fields = %w[id status created_at updated_at created_by_name is_dpo owning_organisation_name managing_organisation_name]
metadata_id_fields = %w[managing_organisation_id owning_organisation_id created_by_id]
scheme_and_location_ids = %w[scheme_id location_id]
intersecting_attributes = ordered_form_questions & CaseLog.attribute_names - scheme_and_location_ids
remaining_attributes = CaseLog.attribute_names - intersecting_attributes - scheme_and_location_ids
scheme_attributes = %w[scheme_code scheme_service_name scheme_sensitive scheme_type scheme_registered_under_care_act scheme_owning_organisation_name scheme_managing_organisation_name scheme_primary_client_group scheme_has_other_client_group scheme_secondary_client_group scheme_support_type scheme_intended_stay scheme_created_at]
location_attributes = %w[location_code location_postcode location_name location_units location_type_of_unit location_mobility_type location_admin_district location_startdate]
la_fields = %w[la_label prevloc_label]
intersecting_attributes = ordered_form_questions & LettingsLog.attribute_names - scheme_and_location_ids
remaining_attributes = LettingsLog.attribute_names - intersecting_attributes - scheme_and_location_ids
@attributes = (metadata_fields + intersecting_attributes + remaining_attributes - metadata_id_fields + %w[unittype_sh] + scheme_and_location_ids + scheme_attributes + location_attributes + la_fields).uniq
move_la_fields
@attributes = (metadata_fields + intersecting_attributes + remaining_attributes - metadata_id_fields + %w[unittype_sh] + scheme_attributes + location_attributes).uniq
move_is_inferred_fields
@attributes -= CSV_FIELDS_TO_OMIT if @user.present? && !@user.support?
end
def ordered_form_questions
downloaded_form_years = CaseLog.all.map(&:collection_start_year).uniq.compact
downloaded_form_years = LettingsLog.all.map(&:collection_start_year).uniq.compact
downloaded_form_fields = downloaded_form_years.count == 1 && downloaded_form_years[0].present? ? FormHandler.instance.get_form("#{downloaded_form_years[0]}_#{downloaded_form_years[0] + 1}").questions : FormHandler.instance.forms.first.second.questions
move_checkbox_answer_options(downloaded_form_fields)
end

2
app/services/exports/case_log_export_constants.rb → app/services/exports/lettings_log_export_constants.rb

@ -1,4 +1,4 @@
module Exports::CaseLogExportConstants
module Exports::LettingsLogExportConstants
MAX_XML_RECORDS = 10_000
LOG_ID_OFFSET = 300_000_000_000

104
app/services/exports/case_log_export_service.rb → app/services/exports/lettings_log_export_service.rb

@ -1,25 +1,25 @@
module Exports
class CaseLogExportService
include Exports::CaseLogExportConstants
class LettingsLogExportService
include Exports::LettingsLogExportConstants
def initialize(storage_service, logger = Rails.logger)
@storage_service = storage_service
@logger = logger
end
def export_csv_case_logs
def export_csv_lettings_logs
time_str = Time.zone.now.strftime("%F").underscore
case_logs = retrieve_case_logs(Time.zone.now, true)
csv_io = build_export_csv(case_logs)
lettings_logs = retrieve_lettings_logs(Time.zone.now, true)
csv_io = build_export_csv(lettings_logs)
@storage_service.write_file("export_#{time_str}.csv", csv_io)
end
def export_xml_case_logs(full_update: false)
def export_xml_lettings_logs(full_update: false)
start_time = Time.zone.now
case_logs = retrieve_case_logs(start_time, full_update)
lettings_logs = retrieve_lettings_logs(start_time, full_update)
export = build_export_run(start_time, full_update)
daily_run = get_daily_run_number
archive_datetimes = write_export_archive(export, case_logs)
archive_datetimes = write_export_archive(export, lettings_logs)
export.empty_export = archive_datetimes.empty?
write_master_manifest(daily_run, archive_datetimes)
export.save!
@ -62,41 +62,41 @@ module Exports
@storage_service.write_file(file_path, string_io)
end
def get_archive_name(case_log, base_number, increment)
return unless case_log.startdate
def get_archive_name(lettings_log, base_number, increment)
return unless lettings_log.startdate
collection_start = case_log.collection_start_year
month = case_log.startdate.month
collection_start = lettings_log.collection_start_year
month = lettings_log.startdate.month
quarter = QUARTERS[(month - 1) / 3]
base_number_str = "f#{base_number.to_s.rjust(4, '0')}"
increment_str = "inc#{increment.to_s.rjust(4, '0')}"
"core_#{collection_start}_#{collection_start + 1}_#{quarter}_#{base_number_str}_#{increment_str}"
end
def write_export_archive(export, case_logs)
# Order case logs per archive
case_logs_per_archive = {}
case_logs.each do |case_log|
archive = get_archive_name(case_log, export.base_number, export.increment_number)
def write_export_archive(export, lettings_logs)
# Order lettings logs per archive
lettings_logs_per_archive = {}
lettings_logs.each do |lettings_log|
archive = get_archive_name(lettings_log, export.base_number, export.increment_number)
next unless archive
if case_logs_per_archive.key?(archive)
case_logs_per_archive[archive] << case_log
if lettings_logs_per_archive.key?(archive)
lettings_logs_per_archive[archive] << lettings_log
else
case_logs_per_archive[archive] = [case_log]
lettings_logs_per_archive[archive] = [lettings_log]
end
end
# Write all archives
archive_datetimes = {}
case_logs_per_archive.each do |archive, case_logs_to_export|
manifest_xml = build_manifest_xml(case_logs_to_export.count)
lettings_logs_per_archive.each do |archive, lettings_logs_to_export|
manifest_xml = build_manifest_xml(lettings_logs_to_export.count)
zip_file = Zip::File.open_buffer(StringIO.new)
zip_file.add("manifest.xml", manifest_xml)
part_number = 1
case_logs_to_export.each_slice(MAX_XML_RECORDS) do |case_logs_slice|
data_xml = build_export_xml(case_logs_slice)
lettings_logs_to_export.each_slice(MAX_XML_RECORDS) do |lettings_logs_slice|
data_xml = build_export_xml(lettings_logs_slice)
part_number_str = "pt#{part_number.to_s.rjust(3, '0')}"
zip_file.add("#{archive}_#{part_number_str}.xml", data_xml)
part_number += 1
@ -112,14 +112,14 @@ module Exports
archive_datetimes
end
def retrieve_case_logs(start_time, full_update)
def retrieve_lettings_logs(start_time, full_update)
recent_export = LogsExport.order("started_at").last
if !full_update && recent_export
params = { from: recent_export.started_at, to: start_time }
CaseLog.where("updated_at >= :from and updated_at <= :to", params)
LettingsLog.where("updated_at >= :from and updated_at <= :to", params)
else
params = { to: start_time }
CaseLog.where("updated_at <= :to", params)
LettingsLog.where("updated_at <= :to", params)
end
end
@ -150,13 +150,13 @@ module Exports
xml_doc_to_temp_file(doc)
end
def apply_cds_transformation(case_log, export_mode)
attribute_hash = case_log.attributes_before_type_cast
def apply_cds_transformation(lettings_log, export_mode)
attribute_hash = lettings_log.attributes_before_type_cast
attribute_hash["form"] = attribute_hash["old_form_id"] || (attribute_hash["id"] + LOG_ID_OFFSET)
# We can't have a variable number of columns in CSV
unless export_mode == EXPORT_MODE[:csv]
case case_log.collection_start_year
case lettings_log.collection_start_year
when 2021
attribute_hash.delete("joint")
when 2022
@ -165,15 +165,15 @@ module Exports
end
# Organisation fields
if case_log.owning_organisation
attribute_hash["owningorgid"] = case_log.owning_organisation.old_visible_id || (case_log.owning_organisation.id + LOG_ID_OFFSET)
attribute_hash["owningorgname"] = case_log.owning_organisation.name
attribute_hash["hcnum"] = case_log.owning_organisation.housing_registration_no
if lettings_log.owning_organisation
attribute_hash["owningorgid"] = lettings_log.owning_organisation.old_visible_id || (lettings_log.owning_organisation.id + LOG_ID_OFFSET)
attribute_hash["owningorgname"] = lettings_log.owning_organisation.name
attribute_hash["hcnum"] = lettings_log.owning_organisation.housing_registration_no
end
if case_log.managing_organisation
attribute_hash["maningorgid"] = case_log.managing_organisation.old_visible_id || (case_log.managing_organisation.id + LOG_ID_OFFSET)
attribute_hash["maningorgname"] = case_log.managing_organisation.name
attribute_hash["manhcnum"] = case_log.managing_organisation.housing_registration_no
if lettings_log.managing_organisation
attribute_hash["maningorgid"] = lettings_log.managing_organisation.old_visible_id || (lettings_log.managing_organisation.id + LOG_ID_OFFSET)
attribute_hash["maningorgname"] = lettings_log.managing_organisation.name
attribute_hash["manhcnum"] = lettings_log.managing_organisation.housing_registration_no
end
# Mapping which would require a change in our data model
@ -190,13 +190,13 @@ module Exports
end
# Supported housing fields
if case_log.is_supported_housing?
attribute_hash["unittype_sh"] = case_log.unittype_sh
attribute_hash["sheltered"] = case_log.sheltered
attribute_hash["nocharge"] = case_log.household_charge == 1 ? 1 : nil
attribute_hash["chcharge"] = case_log.chcharge
add_scheme_fields!(case_log.scheme, attribute_hash)
add_location_fields!(case_log.location, attribute_hash)
if lettings_log.is_supported_housing?
attribute_hash["unittype_sh"] = lettings_log.unittype_sh
attribute_hash["sheltered"] = lettings_log.sheltered
attribute_hash["nocharge"] = lettings_log.household_charge == 1 ? 1 : nil
attribute_hash["chcharge"] = lettings_log.chcharge
add_scheme_fields!(lettings_log.scheme, attribute_hash)
add_location_fields!(lettings_log.location, attribute_hash)
attribute_hash.delete("unittype_gn")
end
attribute_hash
@ -232,11 +232,11 @@ module Exports
!EXPORT_FIELDS.include?(field_name)
end
def build_export_csv(case_logs)
def build_export_csv(lettings_logs)
csv_io = CSV.generate do |csv|
attribute_keys = nil
case_logs.each do |case_log|
attribute_hash = apply_cds_transformation(case_log, EXPORT_MODE[:csv])
lettings_logs.each do |lettings_log|
attribute_hash = apply_cds_transformation(lettings_log, EXPORT_MODE[:csv])
if attribute_keys.nil?
attribute_keys = attribute_hash.keys
filter_keys!(attribute_keys)
@ -249,11 +249,11 @@ module Exports
StringIO.new(csv_io)
end
def build_export_xml(case_logs)
def build_export_xml(lettings_logs)
doc = Nokogiri::XML("<forms/>")
case_logs.each do |case_log|
attribute_hash = apply_cds_transformation(case_log, EXPORT_MODE[:xml])
lettings_logs.each do |lettings_log|
attribute_hash = apply_cds_transformation(lettings_log, EXPORT_MODE[:xml])
form = doc.create_element("form")
doc.at("forms") << form
attribute_hash.each do |key, value|
@ -263,7 +263,7 @@ module Exports
form << doc.create_element(key, value)
end
end
form << doc.create_element("providertype", case_log.owning_organisation.read_attribute_before_type_cast(:provider_type))
form << doc.create_element("providertype", lettings_log.owning_organisation.read_attribute_before_type_cast(:provider_type))
end
xml_doc_to_temp_file(doc)

22
app/services/imports/case_logs_field_import_service.rb → app/services/imports/lettings_logs_field_import_service.rb

@ -1,5 +1,5 @@
module Imports
class CaseLogsFieldImportService < ImportService
class LettingsLogsFieldImportService < ImportService
def update_field(field, folder)
case field
when "tenancycode"
@ -18,7 +18,7 @@ module Imports
def update_lettings_allocation(xml_doc)
old_id = field_value(xml_doc, "meta", "document-id")
previous_status = field_value(xml_doc, "meta", "status")
record = CaseLog.find_by(old_id:)
record = LettingsLog.find_by(old_id:)
if record.present? && previous_status.include?("submitted")
cbl = unsafe_string_as_integer(xml_doc, "Q15CBL")
@ -26,19 +26,19 @@ module Imports
cap = unsafe_string_as_integer(xml_doc, "Q15CAP")
if cbl == 2 && record.cbl == 1
record.update!(cbl: 0)
@logger.info("Case Log #{record.id}'s cbl value has been updated'")
@logger.info("lettings log #{record.id}'s cbl value has been updated'")
end
if chr == 2 && record.chr == 1
record.update!(chr: 0)
@logger.info("Case Log #{record.id}'s chr value has been updated'")
@logger.info("lettings log #{record.id}'s chr value has been updated'")
end
if cap == 2 && record.cap == 1
record.update!(cap: 0)
@logger.info("Case Log #{record.id}'s cap value has been updated'")
@logger.info("lettings log #{record.id}'s cap value has been updated'")
end
if cbl == 2 && chr == 2 && cap == 2 && record.letting_allocation_unknown.nil?
record.update!(letting_allocation_unknown: 1)
@logger.info("Case Log #{record.id}'s letting_allocation_unknown value has been updated'")
@logger.info("lettings log #{record.id}'s letting_allocation_unknown value has been updated'")
end
else
@logger.warn("Could not find record matching legacy ID #{old_id}")
@ -47,7 +47,7 @@ module Imports
def update_major_repairs(xml_doc)
old_id = field_value(xml_doc, "meta", "document-id")
record = CaseLog.find_by(old_id:)
record = LettingsLog.find_by(old_id:)
if record.present?
previous_status = field_value(xml_doc, "meta", "status")
@ -59,9 +59,9 @@ module Imports
end
if major_repairs.present? && record.majorrepairs.nil? && record.status != "completed"
record.update!(mrcdate: major_repairs_date, majorrepairs: major_repairs)
@logger.info("Case Log #{record.id}'s major repair value has been updated'")
@logger.info("lettings log #{record.id}'s major repair value has been updated'")
elsif record.majorrepairs.present?
@logger.info("Case Log #{record.id} has a value for major repairs, skipping update")
@logger.info("lettings log #{record.id} has a value for major repairs, skipping update")
end
else
@logger.warn("Could not find record matching legacy ID #{old_id}")
@ -70,14 +70,14 @@ module Imports
def update_tenant_code(xml_doc)
old_id = field_value(xml_doc, "meta", "document-id")
record = CaseLog.find_by(old_id:)
record = LettingsLog.find_by(old_id:)
if record.present?
tenant_code = string_or_nil(xml_doc, "_2bTenCode")
if tenant_code.present? && record.tenancycode.blank?
record.update!(tenancycode: tenant_code)
else
@logger.info("Case Log #{record.id} has a value for tenancycode, skipping update")
@logger.info("lettings log #{record.id} has a value for tenancycode, skipping update")
end
else
@logger.warn("Could not find record matching legacy ID #{old_id}")

73
app/services/imports/case_logs_import_service.rb → app/services/imports/lettings_logs_import_service.rb

@ -1,5 +1,5 @@
module Imports
class CaseLogsImportService < ImportService
class LettingsLogsImportService < ImportService
def initialize(storage_service, logger = Rails.logger)
@logs_with_discrepancies = Set.new
@logs_overridden = Set.new
@ -9,7 +9,7 @@ module Imports
def create_logs(folder)
import_from(folder, :create_log)
if @logs_with_discrepancies.count.positive?
@logger.warn("The following case logs had status discrepancies: [#{@logs_with_discrepancies.join(', ')}]")
@logger.warn("The following lettings logs had status discrepancies: [#{@logs_with_discrepancies.join(', ')}]")
end
end
@ -107,6 +107,13 @@ module Imports
%w[a b c f g h].each do |letter|
attributes["housingneeds_#{letter}"] = housing_needs(xml_doc, letter)
end
attributes["housingneeds"] = 1 if [attributes["housingneeds_a"], attributes["housingneeds_b"], attributes["housingneeds_c"], attributes["housingneeds_f"]].any? { |housingneed| housingneed == 1 }
attributes["housingneeds"] = 2 if attributes["housingneeds_g"] == 1
attributes["housingneeds"] = 3 if attributes["housingneeds_h"] == 1
attributes["housingneeds_type"] = 0 if attributes["housingneeds_a"] == 1
attributes["housingneeds_type"] = 1 if attributes["housingneeds_b"] == 1
attributes["housingneeds_type"] = 2 if attributes["housingneeds_c"] == 1
attributes["housingneeds_other"] = attributes["housingneeds_f"] == 1 ? 1 : 0
attributes["illness"] = unsafe_string_as_integer(xml_doc, "Q10ia")
(1..10).each do |index|
@ -224,65 +231,65 @@ module Imports
apply_date_consistency!(attributes)
apply_household_consistency!(attributes)
case_log = save_case_log(attributes)
compute_differences(case_log, attributes)
check_status_completed(case_log, previous_status) unless @logs_overridden.include?(case_log.old_id)
lettings_log = save_lettings_log(attributes)
compute_differences(lettings_log, attributes)
check_status_completed(lettings_log, previous_status) unless @logs_overridden.include?(lettings_log.old_id)
end
def save_case_log(attributes)
case_log = CaseLog.new(attributes)
def save_lettings_log(attributes)
lettings_log = LettingsLog.new(attributes)
begin
case_log.save!
case_log
lettings_log.save!
lettings_log
rescue ActiveRecord::RecordNotUnique
legacy_id = attributes["old_id"]
record = CaseLog.find_by(old_id: legacy_id)
@logger.info "Updating case log #{record.id} with legacy ID #{legacy_id}"
record = LettingsLog.find_by(old_id: legacy_id)
@logger.info "Updating lettings log #{record.id} with legacy ID #{legacy_id}"
record.update!(attributes)
record
rescue ActiveRecord::RecordInvalid => e
rescue_validation_or_raise(case_log, attributes, e)
rescue_validation_or_raise(lettings_log, attributes, e)
end
end
def rescue_validation_or_raise(case_log, attributes, exception)
if case_log.errors.of_kind?(:referral, :internal_transfer_non_social_housing)
@logger.warn("Log #{case_log.old_id}: Removing internal transfer referral since previous tenancy is a non social housing")
@logs_overridden << case_log.old_id
def rescue_validation_or_raise(lettings_log, attributes, exception)
if lettings_log.errors.of_kind?(:referral, :internal_transfer_non_social_housing)
@logger.warn("Log #{lettings_log.old_id}: Removing internal transfer referral since previous tenancy is a non social housing")
@logs_overridden << lettings_log.old_id
attributes.delete("referral")
save_case_log(attributes)
elsif case_log.errors.of_kind?(:referral, :internal_transfer_fixed_or_lifetime)
@logger.warn("Log #{case_log.old_id}: Removing internal transfer referral since previous tenancy is fixed terms or lifetime")
@logs_overridden << case_log.old_id
save_lettings_log(attributes)
elsif lettings_log.errors.of_kind?(:referral, :internal_transfer_fixed_or_lifetime)
@logger.warn("Log #{lettings_log.old_id}: Removing internal transfer referral since previous tenancy is fixed terms or lifetime")
@logs_overridden << lettings_log.old_id
attributes.delete("referral")
save_case_log(attributes)
save_lettings_log(attributes)
else
raise exception
end
end
def compute_differences(case_log, attributes)
def compute_differences(lettings_log, attributes)
differences = []
attributes.each do |key, value|
case_log_value = case_log.send(key.to_sym)
lettings_log_value = lettings_log.send(key.to_sym)
next if fields_not_present_in_softwire_data.include?(key)
if value != case_log_value
differences.push("#{key} #{value.inspect} #{case_log_value.inspect}")
if value != lettings_log_value
differences.push("#{key} #{value.inspect} #{lettings_log_value.inspect}")
end
end
@logger.warn "Differences found when saving log #{case_log.old_id}: #{differences}" unless differences.empty?
@logger.warn "Differences found when saving log #{lettings_log.old_id}: #{differences}" unless differences.empty?
end
def fields_not_present_in_softwire_data
%w[majorrepairs illness_type_0 tshortfall_known pregnancy_value_check retirement_value_check rent_value_check net_income_value_check major_repairs_date_value_check void_date_value_check]
%w[majorrepairs illness_type_0 tshortfall_known pregnancy_value_check retirement_value_check rent_value_check net_income_value_check major_repairs_date_value_check void_date_value_check housingneeds_type housingneeds_other]
end
def check_status_completed(case_log, previous_status)
if previous_status.include?("submitted") && case_log.status != "completed"
@logger.warn "Case log #{case_log.id} is not completed"
@logger.warn "Case log with old id:#{case_log.old_id} is incomplete but status should be complete"
@logs_with_discrepancies << case_log.old_id
def check_status_completed(lettings_log, previous_status)
if previous_status.include?("submitted") && lettings_log.status != "completed"
@logger.warn "lettings log #{lettings_log.id} is not completed"
@logger.warn "lettings log with old id:#{lettings_log.old_id} is incomplete but status should be complete"
@logs_with_discrepancies << lettings_log.old_id
end
end
@ -341,7 +348,7 @@ module Imports
end
end
# This does not match renttype (CDS) which is derived by case log logic
# This does not match renttype (CDS) which is derived by lettings log logic
def rent_type(xml_doc, lar, irproduct)
sr_ar_ir = get_form_name_component(xml_doc, FORM_NAME_INDEX[:rent_type])

12
app/views/form/_check_answers_summary_list.html.erb

@ -1,21 +1,21 @@
<%= govuk_summary_list do |summary_list| %>
<% total_applicable_questions(subsection, @case_log, current_user).each do |question| %>
<% total_applicable_questions(subsection, @lettings_log, current_user).each do |question| %>
<% summary_list.row do |row| %>
<% row.key { question.check_answer_label.to_s.presence || question.header.to_s } %>
<% row.value do %>
<span class="govuk-!-margin-right-4"><%= get_answer_label(question, @case_log) %></span>
<% extra_value = question.get_extra_check_answer_value(@case_log) %>
<span class="govuk-!-margin-right-4"><%= get_answer_label(question, @lettings_log) %></span>
<% extra_value = question.get_extra_check_answer_value(@lettings_log) %>
<% if extra_value %>
<span class="govuk-!-font-weight-regular app-!-colour-muted"><%= extra_value %></span>
<% end %>
<br>
<% question.get_inferred_answers(@case_log).each do |inferred_answer| %>
<% question.get_inferred_answers(@lettings_log).each do |inferred_answer| %>
<span class="govuk-!-font-weight-regular app-!-colour-muted"><%= inferred_answer %></span>
<% end %>
<% end %>
<% row.action(
text: question.action_text(@case_log),
href: question.action_href(@case_log, question.page.id),
text: question.action_text(@lettings_log),
href: question.action_href(@lettings_log, question.page.id),
visually_hidden_text: question.check_answer_label.to_s.downcase,
) %>
<% end %>

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

@ -6,7 +6,7 @@
hint: { text: question.hint_text&.html_safe } do %>
<% after_divider = false %>
<% question.displayed_answer_options(@case_log).map do |key, options| %>
<% question.displayed_answer_options(@lettings_log).map do |key, options| %>
<% if key.starts_with?("divider") %>
<% after_divider = true %>
<%= f.govuk_check_box_divider %>
@ -14,7 +14,7 @@
<%= f.govuk_check_box question.id, key,
label: { text: options["value"] },
hint: { text: options["hint"] },
checked: @case_log[key] == 1,
checked: @lettings_log[key] == 1,
exclusive: after_divider,
**stimulus_html_attributes(question) %>
<% end %>

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

@ -1,8 +1,8 @@
<%= govuk_panel(
title_text: display_title_text(title_text, case_log),
title_text: display_title_text(title_text, lettings_log),
classes: "app-panel--interruption",
) do %>
<p class="govuk-panel__body"><%= display_informative_text(informative_text, case_log) %></p>
<p class="govuk-panel__body"><%= display_informative_text(informative_text, lettings_log) %></p>
<%= f.govuk_radio_buttons_fieldset question.id.to_sym,
legend: { text: question.header },
hint: { text: question.hint_text&.html_safe } do %>

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

@ -1,7 +1,7 @@
<%= render partial: "form/guidance/#{question.guidance_partial}" if question.top_guidance? %>
<div class="govuk-form-group">
<label class="govuk-label govuk-label--<%= label_size(page_header, conditional) %>" for="case-log-<%= question.id %>-field">
<label class="govuk-label govuk-label--<%= label_size(page_header, conditional) %>" for="lettings-log-<%= question.id %>-field">
<%= question.header.html_safe %>
</label>
<div class="govuk-hint">
@ -9,15 +9,15 @@
</div>
<div class="govuk-input__wrapper">
<span class="govuk-input__prefix"><%= question.prefix %></span>
<output id="case-log-<%= question.id %>-field"
<output id="lettings-log-<%= question.id %>-field"
class="govuk-input govuk-input--width-<%= question.width %> app-input--output"
min="<%= question.min %>"
step="<%= question.step %>"
type="number"
name="case_log[tcharge]"
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_label(case_log) %></span>
name="lettings_log[tcharge]"
for="<%= question.fields_added.present? ? question.fields_added.map { |x| "lettings-log-#{x}-field" }.join(" ") : "" %>">
<%= lettings_log[question.id] %></output>
<span class="govuk-input__suffix"><%= question.suffix_label(lettings_log) %></span>
</div>
</div>

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

@ -8,7 +8,7 @@
width: question.width,
readonly: question.read_only?,
prefix_text: question.prefix.to_s,
suffix_text: question.suffix_label(@case_log),
suffix_text: question.suffix_label(@lettings_log),
**stimulus_html_attributes(question) %>
<%= render partial: "form/guidance/#{question.guidance_partial}" if question.bottom_guidance? %>

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

@ -5,7 +5,7 @@
legend: legend(question, page_header, conditional),
hint: { text: question.hint_text&.html_safe } do %>
<% question.displayed_answer_options(@case_log).map do |key, options| %>
<% question.displayed_answer_options(@lettings_log).map do |key, options| %>
<% if key.starts_with?("divider") %>
<%= f.govuk_radio_divider %>
<% else %>

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

@ -1,7 +1,7 @@
<%= render partial: "form/guidance/#{question.guidance_partial}" if question.top_guidance? %>
<% selected = @case_log.public_send(question.id) || "" %>
<% answers = question.displayed_answer_options(@case_log).map { |key, value| OpenStruct.new(id: key, name: value.respond_to?(:service_name) ? value.service_name : nil, resource: value) } %>
<% selected = @lettings_log.public_send(question.id) || "" %>
<% answers = question.displayed_answer_options(@lettings_log).map { |key, value| OpenStruct.new(id: key, name: value.respond_to?(:service_name) ? value.service_name : nil, resource: value) } %>
<%= f.govuk_select(question.id.to_sym,
label: legend(question, page_header, conditional),
"data-controller": "accessible-autocomplete",
@ -12,7 +12,7 @@
data-synonyms="<%= question.answer_option_synonyms(answer.resource) %>"
data-append="<%= question.answer_option_append(answer.resource) %>"
data-hint="<%= question.answer_option_hint(answer.resource) %>"
<%= question.answer_selected?(@case_log, answer) ? "selected" : "" %>
<%= question.answer_selected?(@lettings_log, answer) ? "selected" : "" %>
<%= answer.id == "" ? "disabled" : "" %>><%= answer.name || answer.resource %></option>
<% end %>
<% end %>

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

@ -1,7 +1,7 @@
<% content_for :title, "#{subsection.id.humanize} - Check your answers" %>
<% content_for :breadcrumbs, govuk_breadcrumbs(breadcrumbs: {
"Logs" => "/logs",
"Log #{@case_log.id}" => "/logs/#{@case_log.id}",
"Log #{@lettings_log.id}" => "/logs/#{@lettings_log.id}",
subsection.label => "",
}) %>
@ -12,27 +12,27 @@
Check your answers
</h1>
<% if subsection.id == "setup" && subsection.status(@case_log) == :completed %>
<% if subsection.id == "setup" && subsection.status(@lettings_log) == :completed %>
<%= govuk_inset_text(text: "Changing these answers might remove answers you’ve already given in other sections.") %>
<% end %>
<%= display_answered_questions_summary(subsection, @case_log, current_user) %>
<%= display_answered_questions_summary(subsection, @lettings_log, current_user) %>
<% if any_questions_have_summary_card_number?(subsection, @case_log) %>
<% subsection.applicable_questions(@case_log).group_by(&:check_answers_card_number).values.each do |question_group| %>
<%= render CheckAnswersSummaryListCardComponent.new(questions: question_group, case_log: @case_log, user: current_user) %>
<% if any_questions_have_summary_card_number?(subsection, @lettings_log) %>
<% subsection.applicable_questions(@lettings_log).group_by(&:check_answers_card_number).values.each do |question_group| %>
<%= render CheckAnswersSummaryListCardComponent.new(questions: question_group, lettings_log: @lettings_log, user: current_user) %>
<% end %>
<% else %>
<%= render partial: "form/check_answers_summary_list", locals: {
subsection:,
case_log: @case_log,
lettings_log: @lettings_log,
} %>
<% end %>
<%= form_with model: @case_log, method: "get" do |f| %>
<%= form_with model: @lettings_log, method: "get" do |f| %>
<%= f.govuk_submit "Save and return to log" do %>
<% next_incomplete_section_redirect_path = @case_log.form.next_incomplete_section_redirect_path(subsection, @case_log) %>
<% if @case_log.status == "in_progress" && next_incomplete_section_redirect_path != "error" %>
<%= govuk_button_link_to "Save and go to next incomplete section", "/logs/#{@case_log.id}/#{next_incomplete_section_redirect_path}", secondary: true %>
<% next_incomplete_section_redirect_path = @lettings_log.form.next_incomplete_section_redirect_path(subsection, @lettings_log) %>
<% if @lettings_log.status == "in_progress" && next_incomplete_section_redirect_path != "error" %>
<%= govuk_button_link_to "Save and go to next incomplete section", "/logs/#{@lettings_log.id}/#{next_incomplete_section_redirect_path}", secondary: true %>
<% end %>
<% end %>
<% end %>

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

@ -5,10 +5,10 @@
<% end %>
<div data-controller="govukfrontend"></div>
<%= form_with model: @case_log, url: form_case_log_path(@case_log), method: "post", local: true do |f| %>
<%= form_with model: @lettings_log, url: form_lettings_log_path(@lettings_log), method: "post", local: true do |f| %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-<%= @page.questions[0].type == "interruption_screen" ? "full-from-desktop" : "two-thirds-from-desktop" %>">
<% remove_other_page_errors(@case_log, @page) %>
<% remove_other_page_errors(@lettings_log, @page) %>
<%= f.govuk_error_summary %>
<% if @page.header.present? %>
@ -30,9 +30,9 @@
<%= govuk_section_break(visible: true, size: "m") %>
<% end %>
<% if question.type == "interruption_screen" %>
<%= 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 } %>
<%= render partial: "form/#{question.type}_question", locals: { question:, caption_text: @subsection.label, page_header: @page.header, lettings_log: @lettings_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:, caption_text: @subsection.label, page_header: @page.header, case_log: @case_log, f:, conditional: false } %>
<%= render partial: "form/#{question.type}_question", locals: { question:, caption_text: @subsection.label, page_header: @page.header, lettings_log: @lettings_log, f:, conditional: false } %>
<% end %>
</div>
<% end %>
@ -42,10 +42,10 @@
<div class="govuk-button-group">
<% if !@page.id.include?("value_check") && if request.query_parameters["referrer"] != "check_answers" %>
<%= f.govuk_submit "Save and continue" %>
<%= govuk_link_to "Skip for now", send(@case_log.form.next_page_redirect_path(@page, @case_log, current_user), @case_log) %>
<%= govuk_link_to "Skip for now", send(@lettings_log.form.next_page_redirect_path(@page, @lettings_log, current_user), @lettings_log) %>
<% else %>
<%= f.govuk_submit "Save changes" %>
<%= govuk_link_to "Cancel", "/logs/#{@case_log.id}/setup/check-answers" %>
<%= govuk_link_to "Cancel", "/logs/#{@lettings_log.id}/setup/check-answers" %>
<% end %>
<% end %>
</div>

8
app/views/form/review.html.erb

@ -1,7 +1,7 @@
<% content_for :title, "Review lettings log" %>
<% content_for :breadcrumbs, govuk_breadcrumbs(breadcrumbs: {
"Logs" => "/logs",
"Log #{@case_log.id}" => "/logs/#{@case_log.id}",
"Log #{@lettings_log.id}" => "/logs/#{@lettings_log.id}",
"Review lettings log" => "",
}) %>
@ -11,9 +11,9 @@
<%= content_for(:title) %>
</h1>
<p class="govuk-body">
You can review and make changes to this log until 2nd June <%= @case_log.collection_start_year.present? ? @case_log.collection_start_year + 1 : "" %>.
You can review and make changes to this log until 2nd June <%= @lettings_log.collection_start_year.present? ? @lettings_log.collection_start_year + 1 : "" %>.
</p>
<% @case_log.form.sections.map do |section| %>
<% @lettings_log.form.sections.map do |section| %>
<h2 class="govuk-heading-m"><%= section.label %></h2>
<% section.subsections.map do |subsection| %>
<div class="x-govuk-summary-card govuk-!-margin-bottom-6">
@ -23,7 +23,7 @@
<div class="x-govuk-summary-card__body">
<%= render partial: "form/check_answers_summary_list", locals: {
subsection:,
case_log: @case_log,
lettings_log: @lettings_log,
} %>
</div>
</div>

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

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="en" class="govuk-template">
<head>
<title><%= browser_title(yield(:title), @pagy, @admin_user, @user, @organisation, @case_log, @resource) %></title>
<title><%= browser_title(yield(:title), @pagy, @admin_user, @user, @organisation, @lettings_log, @resource) %></title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= tag.meta name: "viewport", content: "width=device-width, initial-scale=1" %>

0
app/views/case_logs/_log_filters.erb → app/views/lettings_logs/_log_filters.erb

2
app/views/case_logs/_log_list.html.erb → app/views/lettings_logs/_log_list.html.erb

@ -3,6 +3,6 @@
<%= govuk_link_to "Download (CSV)", "#{request.path}.csv", type: "text/csv" %>
</h2>
<% case_logs.map do |log| %>
<% lettings_logs.map do |log| %>
<%= render(LogSummaryComponent.new(current_user:, log:)) %>
<% end %>

8
app/views/case_logs/_tasklist.html.erb → app/views/lettings_logs/_tasklist.html.erb

@ -1,5 +1,5 @@
<ol class="app-task-list govuk-!-margin-top-8">
<% @case_log.form.sections.map do |section| %>
<% @lettings_log.form.sections.map do |section| %>
<li>
<h2 class="app-task-list__section-heading">
<%= section.label %>
@ -9,11 +9,11 @@
<% end %>
<ul class="app-task-list__items">
<% section.subsections.map do |subsection| %>
<% if subsection.applicable_questions(@case_log).count > 0 || !subsection.enabled?(@case_log) %>
<% subsection_status = subsection.status(@case_log) %>
<% if subsection.applicable_questions(@lettings_log).count > 0 || !subsection.enabled?(@lettings_log) %>
<% subsection_status = subsection.status(@lettings_log) %>
<li class="app-task-list__item">
<span class="app-task-list__task-name" id="<%= subsection.id.dasherize %>">
<%= subsection_link(subsection, @case_log, current_user) %>
<%= subsection_link(subsection, @lettings_log, current_user) %>
</span>
<%= status_tag(subsection_status, "app-task-list__tag") %>
</li>

4
app/views/case_logs/bulk_upload.html.erb → app/views/lettings_logs/bulk_upload.html.erb

@ -1,9 +1,9 @@
<% content_for :title, "Bulk upload" %>
<%= form_for @bulk_upload, url: bulk_upload_case_logs_path, method: "post" do |f| %>
<%= form_for @bulk_upload, url: bulk_upload_lettings_logs_path, method: "post" do |f| %>
<%= f.govuk_error_summary %>
<%= f.govuk_file_field :case_log_bulk_upload,
<%= f.govuk_file_field :lettings_log_bulk_upload,
label: { text: content_for(:title), size: "l" },
hint: { text: "Upload a spreadsheet using the template" } %>

16
app/views/case_logs/edit.html.erb → app/views/lettings_logs/edit.html.erb

@ -1,4 +1,4 @@
<% content_for :title, "Log #{@case_log.id}" %>
<% content_for :title, "Log #{@lettings_log.id}" %>
<% content_for :breadcrumbs, govuk_breadcrumbs(breadcrumbs: {
"Logs" => "/logs",
content_for(:title) => "",
@ -10,10 +10,10 @@
<%= content_for(:title) %>
</h1>
<% if @case_log.status == "in_progress" %>
<p class="govuk-body govuk-!-margin-bottom-7"><%= get_subsections_count(@case_log, :completed) %> of <%= get_subsections_count(@case_log, :all) %> sections completed.</p>
<% if @lettings_log.status == "in_progress" %>
<p class="govuk-body govuk-!-margin-bottom-7"><%= get_subsections_count(@lettings_log, :completed) %> of <%= get_subsections_count(@lettings_log, :all) %> sections completed.</p>
<p class="govuk-body govuk-!-margin-bottom-2">
<% next_incomplete_section = get_next_incomplete_section(@case_log) %>
<% next_incomplete_section = get_next_incomplete_section(@lettings_log) %>
</p>
<p>
<% if next_incomplete_section.present? %>
@ -22,14 +22,14 @@
</a>
<% end %>
</p>
<% elsif @case_log.status == "not_started" %>
<% elsif @lettings_log.status == "not_started" %>
<p class="govuk-body">This log has not been started.</p>
<% elsif @case_log.status == "completed" %>
<% elsif @lettings_log.status == "completed" %>
<p class="govuk-body">
<%= status_tag(@case_log.status) %>
<%= status_tag(@lettings_log.status) %>
</p>
<p class="govuk-body">
You can <%= govuk_link_to "review and make changes to this log", "/logs/#{@case_log.id}/review" %> until 2nd June <%= @case_log.collection_start_year.present? ? @case_log.collection_start_year + 1 : "" %>.
You can <%= govuk_link_to "review and make changes to this log", "/logs/#{@lettings_log.id}/review" %> until 2nd June <%= @lettings_log.collection_start_year.present? ? @lettings_log.collection_start_year + 1 : "" %>.
</p>
<% end %>
<%= render "tasklist" %>

6
app/views/case_logs/index.html.erb → app/views/lettings_logs/index.html.erb

@ -7,15 +7,15 @@
<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 %>
<%= govuk_button_to "Create a new lettings log", lettings_logs_path %>
<%#= govuk_link_to "Upload logs", bulk_upload_lettings_logs_path %>
</div>
<%= render partial: "log_filters" %>
<div class="app-filter-layout__content">
<%= render SearchComponent.new(current_user:, search_label: "Search by log ID, tenant code, property reference or postcode", value: @searched) %>
<%= govuk_section_break(visible: true, size: "m") %>
<%= render partial: "log_list", locals: { case_logs: @case_logs, title: "Logs", pagy: @pagy, searched: @searched, item_label:, total_count: @total_count } %>
<%= render partial: "log_list", locals: { lettings_logs: @lettings_logs, title: "Logs", pagy: @pagy, searched: @searched, item_label:, total_count: @total_count } %>
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "logs" } %>
</div>
</div>

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

@ -14,14 +14,14 @@
<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 for this organisation", case_logs_path(case_log: { owning_organisation_id: @organisation.id }, method: :post) %>
<%= govuk_button_to "Create a new lettings log for this organisation", lettings_logs_path(lettings_log: { owning_organisation_id: @organisation.id }, method: :post) %>
</div>
<%= render partial: "case_logs/log_filters" %>
<%= render partial: "lettings_logs/log_filters" %>
<div class="app-filter-layout__content">
<%= render SearchComponent.new(current_user:, search_label: "Search by log ID, tenant code, property reference or postcode", value: @searched) %>
<%= govuk_section_break(visible: true, size: "m") %>
<%= render partial: "case_logs/log_list", locals: { case_logs: @case_logs, title: "Logs", pagy: @pagy, searched: @searched, item_label:, total_count: @total_count } %>
<%= render partial: "lettings_logs/log_list", locals: { lettings_logs: @lettings_logs, title: "Logs", pagy: @pagy, searched: @searched, item_label:, total_count: @total_count } %>
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "logs" } %>
</div>
</div>

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

@ -10,7 +10,7 @@
<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 ? lettings_logs_path : "#{user_session_path}?start=true" %>
<%= govuk_start_button(
text: "Start now",
href: start_path,

82
config/forms/2021_2022.json

@ -790,7 +790,7 @@
}
]
},
"header": "Are you sure the time between these dates is correct?",
"header": "Are you sure the property has been vacant for this long?",
"type": "interruption_screen",
"answer_options": {
"0": {
@ -919,7 +919,7 @@
}
]
},
"header": "Are you sure the time between these dates is correct?",
"header": "Are you sure the property has been vacant for this long?",
"type": "interruption_screen",
"answer_options": {
"0": {
@ -1242,7 +1242,7 @@
"description": "",
"questions": {
"age1_known": {
"check_answers_card_number": 1,
"check_answers_card_number": 1,
"header": "Do you know the lead tenant’s age?",
"hint_text": "The ’lead’ or ’main’ 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",
@ -5667,40 +5667,79 @@
}
}
},
"access_needs": {
"access_needs_exist": {
"header": "",
"description": "",
"questions": {
"accessibility_requirements": {
"housingneeds": {
"header": "Does anybody in the household have any disabled access needs?",
"hint_text": "",
"type": "checkbox",
"check_answer_label": "Anybody in household with disabled access needs",
"type": "radio",
"check_answer_label": "Anybody with disabled access needs",
"answer_options": {
"housingneeds_a": {
"1": {
"value": "Yes"
},
"2": {
"value": "No"
},
"divider": {
"value": true
},
"3": {
"value": "Don’t know"
}
}
}
}
},
"type_of_access_needs": {
"header": "Disabled access needs",
"description": "",
"questions": {
"housingneeds_type": {
"header": "What type of access need do they have?",
"hint_text": "",
"type": "radio",
"check_answer_label": "Disabled access needs",
"answer_options": {
"0": {
"value": "Fully wheelchair accessible housing"
},
"housingneeds_b": {
"1": {
"value": "Wheelchair access to essential rooms"
},
"housingneeds_c": {
"2": {
"value": "Level access housing"
},
"housingneeds_f": {
"value": "Other disabled access needs"
},
"divider": {
"value": true
},
"housingneeds_g": {
"value": "No disabled access needs"
"3": {
"value": "None of the above"
}
}
},
"housingneeds_other": {
"header": "Do they have any other access needs?",
"hint_text": "",
"type": "radio",
"check_answer_label": "Other disabled access needs",
"answer_options": {
"1": {
"value": "Yes"
},
"housingneeds_h": {
"value": "Don’t know"
"0": {
"value": "No"
}
}
}
}
},
"depends_on": [
{
"housingneeds": 1
}
]
},
"health_conditions": {
"header": "",
@ -6209,12 +6248,7 @@
}
}
}
},
"depends_on": [
{
"renewal": 0
}
]
}
},
"previous_postcode": {
"header": "",

82
config/forms/2022_2023.json

@ -790,7 +790,7 @@
}
]
},
"header": "Are you sure the time between these dates is correct?",
"header": "Are you sure the property has been vacant for this long?",
"type": "interruption_screen",
"answer_options": {
"0": {
@ -919,7 +919,7 @@
}
]
},
"header": "Are you sure the time between these dates is correct?",
"header": "Are you sure the property has been vacant for this long?",
"type": "interruption_screen",
"answer_options": {
"0": {
@ -1277,7 +1277,7 @@
"description": "",
"questions": {
"age1_known": {
"check_answers_card_number": 1,
"check_answers_card_number": 1,
"header": "Do you know the lead tenant’s age?",
"hint_text": "The ’lead’ or ’main’ 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",
@ -5669,40 +5669,79 @@
}
}
},
"access_needs": {
"access_needs_exist": {
"header": "",
"description": "",
"questions": {
"accessibility_requirements": {
"housingneeds": {
"header": "Does anybody in the household have any disabled access needs?",
"hint_text": "",
"type": "checkbox",
"check_answer_label": "Anybody in household with disabled access needs",
"type": "radio",
"check_answer_label": "Anybody with disabled access needs",
"answer_options": {
"housingneeds_a": {
"1": {
"value": "Yes"
},
"2": {
"value": "No"
},
"divider": {
"value": true
},
"3": {
"value": "Don’t know"
}
}
}
}
},
"type_of_access_needs": {
"header": "Disabled access needs",
"description": "",
"questions": {
"housingneeds_type": {
"header": "What type of access need do they have?",
"hint_text": "",
"type": "radio",
"check_answer_label": "Disabled access needs",
"answer_options": {
"0": {
"value": "Fully wheelchair accessible housing"
},
"housingneeds_b": {
"1": {
"value": "Wheelchair access to essential rooms"
},
"housingneeds_c": {
"2": {
"value": "Level access housing"
},
"housingneeds_f": {
"value": "Other disabled access needs"
},
"divider": {
"value": true
},
"housingneeds_g": {
"value": "No disabled access needs"
"3": {
"value": "None of the above"
}
}
},
"housingneeds_other": {
"header": "Do they have any other access needs?",
"hint_text": "",
"type": "radio",
"check_answer_label": "Other disabled access needs",
"answer_options": {
"1": {
"value": "Yes"
},
"housingneeds_h": {
"value": "Don’t know"
"0": {
"value": "No"
}
}
}
}
},
"depends_on": [
{
"housingneeds": 1
}
]
},
"health_conditions": {
"header": "",
@ -6168,12 +6207,7 @@
}
}
}
},
"depends_on": [
{
"renewal": 0
}
]
}
},
"previous_postcode": {
"header": "",

4
config/locales/en.yml

@ -124,8 +124,8 @@ en:
not_first_let: "Major repairs date must not be completed if the tenancy is a first let"
ten_years_before_tenancy_start: "Enter a major repairs completion date that is no more than 10 years before the tenancy start date"
void_date:
ten_years_before_tenancy_start: "Enter a void date must no more than 10 years before the tenancy start date"
before_tenancy_start: "Enter a void date must that is before the tenancy start date"
ten_years_before_tenancy_start: "Enter a void date no more than 10 years before the tenancy start date"
before_tenancy_start: "Enter a void date that is before the tenancy start date"
after_mrcdate: "Void date must be before the major repairs date if provided"
offered:
relet_number: "Enter a number between 0 and 20 for the amount of times the property has been re-let"

2
config/routes.rb

@ -73,7 +73,7 @@ Rails.application.routes.draw do
end
end
resources :case_logs, path: "/logs" do
resources :lettings_logs, path: "/logs" do
collection do
post "bulk-upload", to: "bulk_upload#bulk_upload"
get "bulk-upload", to: "bulk_upload#show"

8
db/migrate/20220809100723_add_accessibility_requirements_fields.rb

@ -0,0 +1,8 @@
class AddAccessibilityRequirementsFields < ActiveRecord::Migration[7.0]
def change
change_table :case_logs, bulk: true do |t|
t.column :housingneeds_type, :integer
t.column :housingneeds_other, :integer
end
end
end

5
db/migrate/20220823083657_change_case_log_to_lettings_log.rb

@ -0,0 +1,5 @@
class ChangeCaseLogToLettingsLog < ActiveRecord::Migration[7.0]
def change
rename_table :case_logs, :lettings_logs
end
end

38
db/schema.rb

@ -10,11 +10,39 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2022_08_10_152340) do
ActiveRecord::Schema[7.0].define(version: 2022_08_23_083657) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "case_logs", force: :cascade do |t|
create_table "data_protection_confirmations", force: :cascade do |t|
t.bigint "organisation_id"
t.bigint "data_protection_officer_id"
t.boolean "confirmed"
t.string "old_id"
t.string "old_org_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["data_protection_officer_id"], name: "dpo_user_id"
t.index ["organisation_id", "data_protection_officer_id", "confirmed"], name: "data_protection_confirmations_unique", unique: true
t.index ["organisation_id"], name: "index_data_protection_confirmations_on_organisation_id"
end
create_table "la_rent_ranges", force: :cascade do |t|
t.integer "ranges_rent_id"
t.integer "lettype"
t.string "la"
t.integer "beds"
t.decimal "soft_min", precision: 10, scale: 2
t.decimal "soft_max", precision: 10, scale: 2
t.decimal "hard_min", precision: 10, scale: 2
t.decimal "hard_max", precision: 10, scale: 2
t.integer "start_year"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["start_year", "lettype", "beds", "la"], name: "index_la_rent_ranges_on_start_year_and_lettype_and_beds_and_la", unique: true
end
create_table "lettings_logs", force: :cascade do |t|
t.integer "status", default: 0
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
@ -390,9 +418,9 @@ ActiveRecord::Schema[7.0].define(version: 2022_08_10_152340) do
t.index ["item_type", "item_id"], name: "index_versions_on_item_type_and_item_id"
end
add_foreign_key "case_logs", "locations"
add_foreign_key "case_logs", "organisations", column: "owning_organisation_id", on_delete: :cascade
add_foreign_key "case_logs", "schemes"
add_foreign_key "lettings_logs", "locations"
add_foreign_key "lettings_logs", "organisations", column: "owning_organisation_id", on_delete: :cascade
add_foreign_key "lettings_logs", "schemes"
add_foreign_key "locations", "schemes"
add_foreign_key "schemes", "organisations", column: "managing_organisation_id"
add_foreign_key "schemes", "organisations", column: "owning_organisation_id", on_delete: :cascade

6
docs/adr/adr-003-form-submission-flow.md

@ -8,14 +8,14 @@ parent: Architecture decisions
## Impact on performance
Using Turbo Frames allows us to swap out just the question part of the page without needing full page refreshes as you go through the form and provides a "Single Page Application like" user experience. Each question still gets a unique URL that can be navigated to directly with the Case Log ID and the overall user experience is that form navigation feels faster.
Using Turbo Frames allows us to swap out just the question part of the page without needing full page refreshes as you go through the form and provides a "Single Page Application like" user experience. Each question still gets a unique URL that can be navigated to directly with the lettings log ID and the overall user experience is that form navigation feels faster.
## Impact on interrupted sessions
We currently have a single Active Record model for Case Logs that contains all the question fields. Every time a question is submitted the answer will be saved in the Active Record model instance before the next frame is rendered. This model will need to be able to handle partial records and partial validation anyway since not all API users will have all the required data. Validation can occur based on the data already saved and/or once the form is finally submitted. Front end validation will still happen additionally as you go through the form to help make sure users don’t get a long list of errors at the end. Using session data here and updating the model only once the form is completed would not seem to have any advantages over this approach.
We currently have a single Active Record model for lettings logs that contains all the question fields. Every time a question is submitted the answer will be saved in the Active Record model instance before the next frame is rendered. This model will need to be able to handle partial records and partial validation anyway since not all API users will have all the required data. Validation can occur based on the data already saved and/or once the form is finally submitted. Front end validation will still happen additionally as you go through the form to help make sure users don’t get a long list of errors at the end. Using session data here and updating the model only once the form is completed would not seem to have any advantages over this approach.
This means that when a user navigates away from the form or closes the tab etc, they can use the URL to navigate directly back to where they left off, or follow the form flow through again, and in both cases their submitted answers will still be there.
## Impact on API
The API will still expect to take a JSON describing the case log, instantiate the model with the given fields, and run validations as if it had been submitted.
The API will still expect to take a JSON describing the lettings log, instantiate the model with the given fields, and run validations as if it had been submitted.

2
docs/adr/adr-005-form-definition.md

@ -65,5 +65,5 @@ Assumptions made by the format:
- All sections have at least 1 subsection
- All subsections have at least 1 page
- All pages have at least 1 question
- The ActiveRecord case log model has a field for each question name (must match)
- The ActiveRecord lettings log model has a field for each question name (must match)
- Text not required by a page/question such as a header or hint text should be passed as an empty string

8
docs/adr/adr-007-data-validations.md

@ -15,7 +15,7 @@ These are handled slightly differently:
These run for all submitted data. Every time a form page (in the UI) is submitted, the fields related to that form page will be checked to ensure that any responses given are valid. If they are not, an error message will be shown on screen, and it will not be possible to ‘Save and continue’ until the response is fixed or removed.
Similarly if an API request is made to create a case log with data that contains _invalid_ fields, that data will be rejected, and an error message will be returned.
Similarly if an API request is made to create a lettings log with data that contains _invalid_ fields, that data will be rejected, and an error message will be returned.
## Presence checks
@ -23,8 +23,8 @@ These are not strictly error checks since it’s possible to submit partial data
Similarly the API client (3rd party software system) may not have all the required data and may only be submitting a partial log. This is still a valid use case so we should not be enforcing presence checks and returning errors based on them for either submission type.
Instead we determine the _status_ of the case log based the presence checks. Every time data is submitted (via a form page, bulk upload or API), before saving the data, the system will check whether all fields have been completed _and_ pass validity checks. If so, the case log will be marked as _completed_, if not it will be marked as _in progress_.
Instead we determine the _status_ of the lettings log based the presence checks. Every time data is submitted (via a form page, bulk upload or API), before saving the data, the system will check whether all fields have been completed _and_ pass validity checks. If so, the lettings log will be marked as _completed_, if not it will be marked as _in progress_.
By default all fields that a Case Log has will be assumed to be required unless explicitly marked as not required (for example as a result of other answers rendering a question inapplicable).
By default all fields that a lettings log has will be assumed to be required unless explicitly marked as not required (for example as a result of other answers rendering a question inapplicable).
On the form UI this will work by not allowing you to submit the form, until all presence checks have been satisfied, but all other navigation is allowed. On the API this will work by returning a Case Log that is ‘in progress’ if you’ve submitted a partial log, or ‘completed’ if you’ve submitted a full log, or ‘errors’ if you’ve submitted an invalid log.
On the form UI this will work by not allowing you to submit the form, until all presence checks have been satisfied, but all other navigation is allowed. On the API this will work by returning a lettings log that is ‘in progress’ if you’ve submitted a partial log, or ‘completed’ if you’ve submitted a full log, or ‘errors’ if you’ve submitted an invalid log.

4
docs/adr/adr-011-form-oop-refactor.md

@ -11,6 +11,6 @@ Refactoring this into smaller form domain object classes has several benefits:
- It’s easier to compare the form definition JSON to the code classes and reason about what fields can be passed and what effect they’ll have
- It moves business logic out of the helpers and keeps them to just dealing with display logic
- It makes it easier to unit test form functionality, and group that into smaller chunks
- It allows for less passing of arguments. e.g. `page.routed_to?(case_log)` vs `form.was_page_routed_to?(page, case_log)`
- It allows for less passing of arguments. e.g. `page.routed_to?(lettings_log)` vs `form.was_page_routed_to?(page, lettings_log)`
This abstraction is likely still not the best (the form vs case log split) but this seems like an improvement that can be iterated on.
This abstraction is likely still not the best (the form vs lettings log split) but this seems like an improvement that can be iterated on.

54
docs/api/v1.json

@ -15,7 +15,7 @@
"/logs/:id": {
"parameters": [],
"get": {
"summary": "Get Case Log Info by Case Log ID",
"summary": "Get lettings log Info by lettings log ID",
"tags": [],
"responses": {
"200": {
@ -23,7 +23,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Case-Log"
"$ref": "#/components/schemas/lettings-log"
},
"examples": {}
}
@ -40,7 +40,7 @@
"examples": {
"Not found": {
"value": {
"error": "Case Log 67 not found"
"error": "lettings log 67 not found"
}
}
}
@ -48,8 +48,8 @@
}
}
},
"operationId": "get-case_logs-case_logs-:id",
"description": "Retrieve data for a specific case log",
"operationId": "get-lettings_logs-lettings_logs-:id",
"description": "Retrieve data for a specific lettings log",
"parameters": [
{
"schema": {
@ -65,15 +65,15 @@
]
},
"patch": {
"summary": "Update Case Log Information",
"operationId": "patch-case_logs-case_logs-:id",
"summary": "Update lettings log Information",
"operationId": "patch-lettings_logs-lettings_logs-:id",
"responses": {
"200": {
"description": "Case Log Updated",
"description": "lettings log Updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Case-Log"
"$ref": "#/components/schemas/lettings-log"
},
"examples": {}
}
@ -90,7 +90,7 @@
"examples": {
"Not found": {
"value": {
"error": "Case Log 67 not found"
"error": "lettings log 67 not found"
}
}
}
@ -120,17 +120,17 @@
}
}
},
"description": "Update the information of an existing case log",
"description": "Update the information of an existing lettings log",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Case-Log"
"$ref": "#/components/schemas/lettings-log"
},
"examples": {}
}
},
"description": "Patch case log properties to update."
"description": "Patch lettings log properties to update."
},
"parameters": [
{
@ -147,8 +147,8 @@
]
},
"delete": {
"summary": "Delete a Case Log by Case Log ID",
"operationId": "delete-case_logs-:id",
"summary": "Delete a lettings log by lettings log ID",
"operationId": "delete-lettings_logs-:id",
"responses": {
"204": {
"description": "No Content"
@ -164,7 +164,7 @@
"examples": {
"Not found": {
"value": {
"error": "Case Log 67 not found"
"error": "lettings log 67 not found"
}
}
}
@ -172,7 +172,7 @@
}
}
},
"description": "Delete a case log",
"description": "Delete a lettings log",
"parameters": [
{
"schema": {
@ -190,15 +190,15 @@
},
"/logs": {
"post": {
"summary": "Create New Case Log",
"operationId": "post-caselog",
"summary": "Create New lettings log",
"operationId": "post-LettingsLog",
"responses": {
"201": {
"description": "Case Log Created",
"description": "lettings log Created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Case-Log"
"$ref": "#/components/schemas/lettings-log"
},
"examples": {}
}
@ -234,14 +234,14 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Case-Log"
"$ref": "#/components/schemas/lettings-log"
},
"examples": {}
}
},
"description": "Post the necessary fields for the API to create a new case log."
"description": "Post the necessary fields for the API to create a new lettings log."
},
"description": "Create a new case log.",
"description": "Create a new lettings log.",
"parameters": [
{
"schema": {
@ -262,12 +262,12 @@
},
"components": {
"schemas": {
"Case-Log": {
"lettings-log": {
"description": "",
"type": "object",
"x-examples": {
"example-1": {
"case_log": {
"lettings_log": {
"tenant_code": "T657",
"age1": 35,
"sex1": "F",
@ -415,7 +415,7 @@
}
}
},
"title": "Case Log",
"title": "lettings log",
"x-internal": false,
"properties": {
"tenant_code": {

2
docs/exports.md

@ -6,7 +6,7 @@ nav_order: 7
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`
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/lettings_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.

6
docs/form/builder.md

@ -23,9 +23,9 @@ As a result it’s not modelled as part of the config but rather as code. It sti
- Conditional questions (`conditional_for`) – Radio and checkbox questions can support conditional text or numeric questions that show/hide on the same page when the triggering option is selected
- Routing (`depends_on`) – all pages can specify conditions (attributes of the case log) that determine whether or not they’re shown to the user
- Routing (`depends_on`) – all pages can specify conditions (attributes of the lettings log) that determine whether or not they’re shown to the user
- Methods can be chained (i.e. you can have conditions in the form `{ owning_organisation.provider_type: "local_authority"`) which will call `case_log.owning_organisation.provider_type` and compare the result to the provided value.
- Methods can be chained (i.e. you can have conditions in the form `{ owning_organisation.provider_type: "local_authority"`) which will call `lettings_log.owning_organisation.provider_type` and compare the result to the provided value.
- Numeric questions support math expression depends_on conditions such as `{ age2: ">16" }`
@ -108,7 +108,7 @@ Assumptions made by the format:
- All pages have at least 1 question
- The ActiveRecord case log model has a field for each question name (must match). In the case of checkbox questions it must have one field for every answer option (again names must match).
- The ActiveRecord lettings log model has a field for each question name (must match). In the case of checkbox questions it must have one field for every answer option (again names must match).
- Text not required by a page/question such as a header or hint text should be passed as an empty string

2
docs/form/question.md

@ -87,4 +87,4 @@ In the above example the width is an optional attribute and can be provided for
The above example links to the first example as both of these questions would be on the same page. The `inferred_check_answers_value` is what should be displayed on the check answers page for this question if we infer it. If the value of `postcode_known` was given as `0` (which is a no), as seen in the condition part of `inferred_check_answers_value` then we can infer that the data inputter does not know the postcode and so we would display the value of `Not known` on the check answers page for the postcode.
In the above example the `inferred_answers` refers to a question where we can infer the answer based on the answer of this question. In this case the `la` question can be inferred from the postcode value given by the data inputter as we are able to lookup the local authority based on the postcode given. We then set a property on the case log `is_la_inferred` to true to indicate that this is an answer we've inferred.
In the above example the `inferred_answers` refers to a question where we can infer the answer based on the answer of this question. In this case the `la` question can be inferred from the postcode value given by the data inputter as we are able to lookup the local authority based on the postcode given. We then set a property on the lettings log `is_la_inferred` to true to indicate that this is an answer we've inferred.

2
docs/form/runner.md

@ -23,7 +23,7 @@ ERB templates:
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.
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 lettings log state.
## Form models and definition

2
docs/form/section.md

@ -29,6 +29,6 @@ An example section might look something like this:
In the above example the section id would be `tenancy_and_property` and its subsections would be `property_information` and `tenancy_information`.
The label contains the text that users will see for that section in the task list page of a case log.
The label contains the text that users will see for that section in the task list page of a lettings log.
Sections can contain one or more [subsections](subsection).

2
docs/form/subsection.md

@ -31,7 +31,7 @@ An example subsection might look something like this:
In the above example the the subsection has the id `property_information`. The `depends_on` contains the set of conditions that must be met for the section to be accessible to a data provider, in this example subsection depends on the completion of the setup section/subsection (note that this is a common condition as the answers provided to questions in the setup subsection often have an impact on what questions are asked of the data provider in later subsections of the form).
The label contains the text that users will see for that subsection in the task list page of a case log.
The label contains the text that users will see for that subsection in the task list page of a lettings log.
The pages of the subsection in the example would be `property_postcode` and `property_local_authority`.

8
docs/index.md

@ -46,13 +46,13 @@ This is a useful analogy as a parent can have multiple children, and a child can
### User permissions within organisations
The case logs that a user can see depends on their role:
The lettings logs that a user can see depends on their role:
- Customer support users can access any case log
- Customer support users can access any lettings 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 coordinators can access any lettings 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)
- Data providers can only access lettings 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:

43
docs/infrastructure.md

@ -4,6 +4,49 @@ nav_order: 5
# Infrastructure
## Configuration
On [GOV.UK PaaS](https://www.cloud.service.gov.uk/), service credentials are appended to the environment variable `VCAP_SERVICES` when services [are bound](https://docs.cloud.service.gov.uk/deploying_services/s3/#bind-an-aws-s3-bucket-to-your-app) to an application.
Such services include datastores and S3 buckets.
Our application uses S3 and Redis clients and supports two different ways of parsing their configuration:
* Via the environment variable `VCAP_SERVICES` using the `PaasConfigurationService` class
* Via the environment variables `S3_CONFIG` and `REDIS_CONFIG` using the `EnvConfigurationService` class
`S3_CONFIG` and `REDIS_CONFIG` are populated using a similar structure than `VCAP_SERVICES`:
S3_CONFIG:
```json
[
{
"instance_name": "bucket_1",
"credentials": {
"aws_access_key_id": "123",
"aws_secret_access_key": "456",
"aws_region": "eu-west-1",
"bucket_name": "my-bucket"
}
}
]
```
REDIS_CONFIG:
```json
[
{
"instance_name": "redis_1",
"credentials": {
"uri": "redis_uri"
}
}
]
```
In order to switch from using [GOV.UK PaaS](https://www.cloud.service.gov.uk/) provided services to external ones, instances of `PaasConfigurationService` need to be replaced by `EnvConfigurationService`.
This assumes that `S3_CONFIG` or/and `REDIS_CONFIG` are available.
Please check `full_import.rake` and `rack_attack.rb` for examples of how the configuration is used.
## Deployment
This application is running on [GOV.UK PaaS](https://www.cloud.service.gov.uk/). To deploy you need to:

6
lib/tasks/data_export.rake

@ -5,12 +5,12 @@ namespace :core do
full_update = args[:full_update].present? && args[:full_update] == "true"
storage_service = Storage::S3Service.new(Configuration::PaasConfigurationService.new, ENV["EXPORT_PAAS_INSTANCE"])
export_service = Exports::CaseLogExportService.new(storage_service)
export_service = Exports::LettingsLogExportService.new(storage_service)
if format.present? && format == "CSV"
export_service.export_csv_case_logs
export_service.export_csv_lettings_logs
else
export_service.export_xml_case_logs(full_update:)
export_service.export_xml_lettings_logs(full_update:)
end
end
end

4
lib/tasks/data_import.rake

@ -20,8 +20,8 @@ namespace :core do
Imports::DataProtectionConfirmationImportService.new(storage_service).create_data_protection_confirmations(path)
when "organisation-rent-periods"
Imports::OrganisationRentPeriodImportService.new(storage_service).create_organisation_rent_periods(path)
when "case-logs"
Imports::CaseLogsImportService.new(storage_service).create_logs(path)
when "lettings-logs"
Imports::LettingsLogsImportService.new(storage_service).create_logs(path)
else
raise "Type #{type} is not supported by data_import"
end

2
lib/tasks/data_import_field.rake

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

2
lib/tasks/full_import.rake

@ -17,7 +17,7 @@ namespace :core do
Import.new(Imports::UserImportService, :create_users, "user"),
Import.new(Imports::DataProtectionConfirmationImportService, :create_data_protection_confirmations, "dataprotect"),
Import.new(Imports::OrganisationRentPeriodImportService, :create_organisation_rent_periods, "rent-period"),
Import.new(Imports::CaseLogsImportService, :create_logs, "logs"),
Import.new(Imports::LettingsLogsImportService, :create_logs, "logs"),
]
import_list.each do |step|

14
spec/components/check_answers_summary_list_card_component_spec.rb

@ -3,23 +3,23 @@ require "rails_helper"
RSpec.describe CheckAnswersSummaryListCardComponent, type: :component do
context "when given a set of questions" do
let(:user) { FactoryBot.build(:user) }
let(:case_log) { FactoryBot.build(:case_log, :completed, age2: 99) }
let(:lettings_log) { FactoryBot.build(:lettings_log, :completed, age2: 99) }
let(:subsection_id) { "household_characteristics" }
let(:subsection) { case_log.form.get_subsection(subsection_id) }
let(:questions) { subsection.applicable_questions(case_log) }
let(:subsection) { lettings_log.form.get_subsection(subsection_id) }
let(:questions) { subsection.applicable_questions(lettings_log) }
it "renders a summary list card for the answers to those questions" do
result = render_inline(described_class.new(questions:, case_log:, user:))
expect(result).to have_content(questions.first.answer_label(case_log))
result = render_inline(described_class.new(questions:, lettings_log:, user:))
expect(result).to have_content(questions.first.answer_label(lettings_log))
end
it "applicable questions doesn't return questions that are hidden in check answers" do
summary_list = described_class.new(questions:, case_log:, user:)
summary_list = described_class.new(questions:, lettings_log:, user:)
expect(summary_list.applicable_questions.map(&:id).include?("retirement_value_check")).to eq(false)
end
it "has the correct answer label for a question" do
summary_list = described_class.new(questions:, case_log:, user:)
summary_list = described_class.new(questions:, lettings_log:, user:)
sex1_question = questions[2]
expect(summary_list.get_answer_label(sex1_question)).to eq("Female")
end

2
spec/components/log_summary_component_spec.rb

@ -5,7 +5,7 @@ RSpec.describe LogSummaryComponent, type: :component do
let(:coordinator_user) { FactoryBot.create(:user) }
let(:propcode) { "P3647" }
let(:tenancycode) { "T62863" }
let(:log) { FactoryBot.create(:case_log, needstype: 1, startdate: Time.utc(2022, 1, 1), tenancycode:, propcode:) }
let(:log) { FactoryBot.create(:lettings_log, needstype: 1, startdate: Time.utc(2022, 1, 1), tenancycode:, propcode:) }
context "when rendering log for a support user" do
it "show the log summary with organisational relationships" do

5
spec/factories/case_log.rb → spec/factories/lettings_log.rb

@ -1,5 +1,5 @@
FactoryBot.define do
factory :case_log do
factory :lettings_log do
created_by { FactoryBot.create(:user) }
owning_organisation { created_by.organisation }
managing_organisation { created_by.organisation }
@ -87,6 +87,9 @@ FactoryBot.define do
chr { 1 }
cap { 0 }
reasonother { nil }
housingneeds { 1 }
housingneeds_type { 0 }
housingneeds_other { 0 }
housingneeds_a { 1 }
housingneeds_b { 0 }
housingneeds_c { 0 }

46
spec/features/form/accessible_autocomplete_spec.rb

@ -4,9 +4,9 @@ require_relative "helpers"
RSpec.describe "Accessible Automcomplete" do
include Helpers
let(:user) { FactoryBot.create(:user) }
let(:case_log) do
let(:lettings_log) do
FactoryBot.create(
:case_log,
:lettings_log,
:in_progress,
previous_la_known: 1,
prevloc: "E09000033",
@ -24,71 +24,71 @@ RSpec.describe "Accessible Automcomplete" do
context "when using accessible autocomplete" do
before do
visit("/logs/#{case_log.id}/accessible-select")
visit("/logs/#{lettings_log.id}/accessible-select")
end
it "allows type ahead filtering", js: true do
find("#case-log-prevloc-field").click.native.send_keys("T", "h", "a", "n", :down, :enter)
expect(find("#case-log-prevloc-field").value).to eq("Thanet")
find("#lettings-log-prevloc-field").click.native.send_keys("T", "h", "a", "n", :down, :enter)
expect(find("#lettings-log-prevloc-field").value).to eq("Thanet")
end
it "ignores punctuation", js: true do
find("#case-log-prevloc-field").click.native.send_keys("T", "h", "a", "'", "n", :down, :enter)
expect(find("#case-log-prevloc-field").value).to eq("Thanet")
find("#lettings-log-prevloc-field").click.native.send_keys("T", "h", "a", "'", "n", :down, :enter)
expect(find("#lettings-log-prevloc-field").value).to eq("Thanet")
end
it "ignores stop words", js: true do
find("#case-log-prevloc-field").click.native.send_keys("t", "h", "e", " ", "W", "e", "s", "t", "m", :down, :enter)
expect(find("#case-log-prevloc-field").value).to eq("Westminster")
find("#lettings-log-prevloc-field").click.native.send_keys("t", "h", "e", " ", "W", "e", "s", "t", "m", :down, :enter)
expect(find("#lettings-log-prevloc-field").value).to eq("Westminster")
end
it "does not perform an exact match", js: true do
find("#case-log-prevloc-field").click.native.send_keys("o", "n", "l", "y", " ", "t", "o", "w", "n", :down, :enter)
expect(find("#case-log-prevloc-field").value).to eq("The one and only york town")
find("#lettings-log-prevloc-field").click.native.send_keys("o", "n", "l", "y", " ", "t", "o", "w", "n", :down, :enter)
expect(find("#lettings-log-prevloc-field").value).to eq("The one and only york town")
end
it "maintains enhancement state across back navigation", js: true do
find("#case-log-prevloc-field").click.native.send_keys("T", "h", "a", "n", :down, :enter)
find("#lettings-log-prevloc-field").click.native.send_keys("T", "h", "a", "n", :down, :enter)
click_button("Save and continue")
click_link(text: "Back")
expect(page).to have_selector("input", class: "autocomplete__input", count: 1)
end
it "has a disabled null option" do
expect(page).to have_select("case-log-prevloc-field", disabled_options: ["Select an option"])
expect(page).to have_select("lettings-log-prevloc-field", disabled_options: ["Select an option"])
end
end
context "when searching schemes" do
let(:scheme) { FactoryBot.create(:scheme, owning_organisation_id: case_log.created_by.organisation_id, managing_organisation_id: case_log.created_by.organisation_id, primary_client_group: "Q", secondary_client_group: "P") }
let(:scheme) { FactoryBot.create(:scheme, owning_organisation_id: lettings_log.created_by.organisation_id, managing_organisation_id: lettings_log.created_by.organisation_id, primary_client_group: "Q", secondary_client_group: "P") }
before do
FactoryBot.create(:location, scheme:, postcode: "W6 0ST")
FactoryBot.create(:location, scheme:, postcode: "SE6 1LB")
case_log.update!(needstype: 2)
visit("/logs/#{case_log.id}/scheme")
lettings_log.update!(needstype: 2)
visit("/logs/#{lettings_log.id}/scheme")
end
it "can match on synonyms", js: true do
find("#case-log-scheme-id-field").click.native.send_keys("w", "6", :down, :enter)
expect(find("#case-log-scheme-id-field").value).to eq(scheme.service_name)
find("#lettings-log-scheme-id-field").click.native.send_keys("w", "6", :down, :enter)
expect(find("#lettings-log-scheme-id-field").value).to eq(scheme.service_name)
end
it "displays appended text next to the options", js: true do
find("#case-log-scheme-id-field").click.native.send_keys("w", "6", :down, :enter)
find("#lettings-log-scheme-id-field").click.native.send_keys("w", "6", :down, :enter)
expect(find(".autocomplete__option", visible: :hidden, text: scheme.service_name)).to be_present
expect(find("span", visible: :hidden, text: "2 locations")).to be_present
end
it "displays hint text under the options", js: true do
find("#case-log-scheme-id-field").click.native.send_keys("w", "6", :down, :enter)
find("#lettings-log-scheme-id-field").click.native.send_keys("w", "6", :down, :enter)
expect(find(".autocomplete__option__hint", visible: :hidden, text: /Young people at risk, Young people leaving care/)).to be_present
end
end
it "has the correct option selected if one has been saved" do
case_log.update!(postcode_known: 0, previous_la_known: 1, prevloc: "E07000178")
visit("/logs/#{case_log.id}/accessible-select")
expect(page).to have_select("case-log-prevloc-field", selected: %w[Oxford])
lettings_log.update!(postcode_known: 0, previous_la_known: 1, prevloc: "E07000178")
visit("/logs/#{lettings_log.id}/accessible-select")
expect(page).to have_select("lettings-log-prevloc-field", selected: %w[Oxford])
end
end

94
spec/features/form/check_answers_page_spec.rb

@ -9,9 +9,9 @@ RSpec.describe "Form Check Answers Page" do
let(:scheme) { FactoryBot.create(:scheme, owning_organisation: user.organisation) }
let(:location) { FactoryBot.create(:location, scheme:, mobility_type: "N") }
let(:case_log) do
let(:lettings_log) do
FactoryBot.create(
:case_log,
:lettings_log,
:in_progress,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
@ -20,9 +20,9 @@ RSpec.describe "Form Check Answers Page" do
location:,
)
end
let(:empty_case_log) do
let(:empty_lettings_log) do
FactoryBot.create(
:case_log,
:lettings_log,
previous_la_known: 1,
prevloc: "E09000033",
is_previous_la_inferred: false,
@ -30,15 +30,15 @@ RSpec.describe "Form Check Answers Page" do
managing_organisation: user.organisation,
)
end
let(:completed_case_log) do
let(:completed_lettings_log) do
FactoryBot.create(
:case_log,
:lettings_log,
:completed,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
)
end
let(:id) { case_log.id }
let(:id) { lettings_log.id }
before do
sign_in user
@ -66,10 +66,10 @@ RSpec.describe "Form Check Answers Page" do
end
it "displays answers given by the user for the question in the subsection" do
fill_in_number_question(empty_case_log.id, "age1", 28, "person-1-age")
choose("case-log-sex1-x-field")
fill_in_number_question(empty_lettings_log.id, "age1", 28, "person-1-age")
choose("lettings-log-sex1-x-field")
click_button("Save and continue")
visit("/logs/#{empty_case_log.id}/#{subsection}/check-answers")
visit("/logs/#{empty_lettings_log.id}/#{subsection}/check-answers")
expect(page).to have_content("28")
expect(page).to have_content("Non-binary")
end
@ -77,31 +77,31 @@ RSpec.describe "Form Check Answers Page" do
# Regex explanation: match the string "Answer" but not if it's follow by "the missing questions"
# This way only the links in the table will get picked up
it "has an answer link for questions missing an answer" do
visit("/logs/#{empty_case_log.id}/#{subsection}/check-answers?referrer=check_answers")
visit("/logs/#{empty_lettings_log.id}/#{subsection}/check-answers?referrer=check_answers")
assert_selector "a", text: /Answer (?!the missing questions)/, count: 5
assert_selector "a", text: "Change", count: 0
expect(page).to have_link("Answer", href: "/logs/#{empty_case_log.id}/person-1-age?referrer=check_answers")
expect(page).to have_link("Answer", href: "/logs/#{empty_lettings_log.id}/person-1-age?referrer=check_answers")
end
it "has a change link for answered questions" do
fill_in_number_question(empty_case_log.id, "age1", 28, "person-1-age")
visit("/logs/#{empty_case_log.id}/#{subsection}/check-answers")
fill_in_number_question(empty_lettings_log.id, "age1", 28, "person-1-age")
visit("/logs/#{empty_lettings_log.id}/#{subsection}/check-answers")
assert_selector "a", text: /Answer (?!the missing questions)/, count: 4
assert_selector "a", text: "Change", count: 1
expect(page).to have_link("Change", href: "/logs/#{empty_case_log.id}/person-1-age?referrer=check_answers")
expect(page).to have_link("Change", href: "/logs/#{empty_lettings_log.id}/person-1-age?referrer=check_answers")
end
it "updates the change/answer link when answers get updated" do
visit("/logs/#{empty_case_log.id}/household-needs/check-answers")
visit("/logs/#{empty_lettings_log.id}/household-needs/check-answers")
assert_selector "a", text: /Answer (?!the missing questions)/, count: 3
assert_selector "a", text: "Change", count: 1
visit("/logs/#{empty_case_log.id}/accessibility-requirements")
check("case-log-accessibility-requirements-housingneeds-c-field")
visit("/logs/#{empty_lettings_log.id}/accessibility-requirements")
check("lettings-log-accessibility-requirements-housingneeds-c-field")
click_button("Save and continue")
visit("/logs/#{empty_case_log.id}/household-needs/check-answers")
visit("/logs/#{empty_lettings_log.id}/household-needs/check-answers")
assert_selector "a", text: /Answer (?!the missing questions)/, count: 2
assert_selector "a", text: "Change", count: 2
expect(page).to have_link("Change", href: "/logs/#{empty_case_log.id}/accessibility-requirements?referrer=check_answers")
expect(page).to have_link("Change", href: "/logs/#{empty_lettings_log.id}/accessibility-requirements?referrer=check_answers")
end
it "does not display conditional questions that were not visited" do
@ -119,7 +119,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-2-field", allow_label_click: true)
choose("lettings-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?"]
@ -134,13 +134,13 @@ RSpec.describe "Form Check Answers Page" do
end
it "does not group questions into summary cards if the questions in the subsection don't have a check_answers_card_number attribute" do
visit("/logs/#{completed_case_log.id}/household-needs/check-answers")
visit("/logs/#{completed_lettings_log.id}/household-needs/check-answers")
assert_selector ".x-govuk-summary-card__title", count: 0
end
context "when the user is checking their answers for the household characteristics subsection" do
it "they see a seperate summary card for each member of the household" do
visit("/logs/#{completed_case_log.id}/#{subsection}/check-answers")
visit("/logs/#{completed_lettings_log.id}/#{subsection}/check-answers")
assert_selector ".x-govuk-summary-card__title", text: "Lead tenant", count: 1
assert_selector ".x-govuk-summary-card__title", text: "Person 2", count: 1
end
@ -152,14 +152,14 @@ RSpec.describe "Form Check Answers Page" do
end
it "displays inferred postcode with the location id" do
case_log.update!(location:)
lettings_log.update!(location:)
visit("/logs/#{id}/setup/check-answers")
expect(page).to have_content("Location")
expect(page).to have_content(location.name)
end
it "displays inferred postcode with the location_admin_district" do
case_log.update!(location:)
lettings_log.update!(location:)
visit("/logs/#{id}/setup/check-answers")
expect(page).to have_content("Location")
expect(page).to have_content(location.location_admin_district)
@ -168,22 +168,22 @@ RSpec.describe "Form Check Answers Page" do
context "when the user changes their answer from check answer page" do
it "routes back to check answers" do
visit("/logs/#{empty_case_log.id}/accessibility-requirements")
check("case-log-accessibility-requirements-housingneeds-c-field")
visit("/logs/#{empty_lettings_log.id}/accessibility-requirements")
check("lettings-log-accessibility-requirements-housingneeds-c-field")
click_button("Save and continue")
visit("/logs/#{empty_case_log.id}/household-needs/check-answers")
visit("/logs/#{empty_lettings_log.id}/household-needs/check-answers")
first("a", text: /Change/).click
uncheck("case-log-accessibility-requirements-housingneeds-c-field")
check("case-log-accessibility-requirements-housingneeds-b-field")
uncheck("lettings-log-accessibility-requirements-housingneeds-c-field")
check("lettings-log-accessibility-requirements-housingneeds-b-field")
click_button("Save changes")
expect(page).to have_current_path("/logs/#{empty_case_log.id}/household-needs/check-answers")
expect(page).to have_current_path("/logs/#{empty_lettings_log.id}/household-needs/check-answers")
end
end
context "when the user wants to bypass the tasklist page from check answers" do
let(:section_completed_case_log) do
let(:section_completed_lettings_log) do
FactoryBot.create(
:case_log,
:lettings_log,
:in_progress,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
@ -194,9 +194,9 @@ RSpec.describe "Form Check Answers Page" do
)
end
let(:next_section_in_progress_case_log) do
let(:next_section_in_progress_lettings_log) do
FactoryBot.create(
:case_log,
:lettings_log,
:in_progress,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
@ -209,9 +209,9 @@ RSpec.describe "Form Check Answers Page" do
)
end
let(:skip_section_case_log) do
let(:skip_section_lettings_log) do
FactoryBot.create(
:case_log,
:lettings_log,
:in_progress,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
@ -227,9 +227,9 @@ RSpec.describe "Form Check Answers Page" do
)
end
let(:cycle_sections_case_log) do
let(:cycle_sections_lettings_log) do
FactoryBot.create(
:case_log,
:lettings_log,
:in_progress,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
@ -247,27 +247,27 @@ RSpec.describe "Form Check Answers Page" do
end
it "they can click a button to move onto the first page of the next (not started) incomplete section" do
visit("/logs/#{section_completed_case_log.id}/household-characteristics/check-answers")
visit("/logs/#{section_completed_lettings_log.id}/household-characteristics/check-answers")
click_link("Save and go to next incomplete section")
expect(page).to have_current_path("/logs/#{section_completed_case_log.id}/armed-forces")
expect(page).to have_current_path("/logs/#{section_completed_lettings_log.id}/armed-forces")
end
it "they can click a button to move onto the check answers page of the next (in progress) incomplete section" do
visit("/logs/#{next_section_in_progress_case_log.id}/household-characteristics/check-answers")
visit("/logs/#{next_section_in_progress_lettings_log.id}/household-characteristics/check-answers")
click_link("Save and go to next incomplete section")
expect(page).to have_current_path("/logs/#{next_section_in_progress_case_log.id}/household-needs/check-answers")
expect(page).to have_current_path("/logs/#{next_section_in_progress_lettings_log.id}/household-needs/check-answers")
end
it "they can click a button to skip sections until the next incomplete section" do
visit("/logs/#{skip_section_case_log.id}/household-characteristics/check-answers")
visit("/logs/#{skip_section_lettings_log.id}/household-characteristics/check-answers")
click_link("Save and go to next incomplete section")
expect(page).to have_current_path("/logs/#{skip_section_case_log.id}/property-information/check-answers")
expect(page).to have_current_path("/logs/#{skip_section_lettings_log.id}/property-information/check-answers")
end
it "they can click a button to cycle around to the next incomplete section" do
visit("/logs/#{cycle_sections_case_log.id}/declaration/check-answers")
visit("/logs/#{cycle_sections_lettings_log.id}/declaration/check-answers")
click_link("Save and go to next incomplete section")
expect(page).to have_current_path("/logs/#{cycle_sections_case_log.id}/tenant-code-test")
expect(page).to have_current_path("/logs/#{cycle_sections_lettings_log.id}/tenant-code-test")
end
end
end

38
spec/features/form/checkboxes_spec.rb

@ -5,15 +5,15 @@ require_relative "../../request_helper"
RSpec.describe "Checkboxes" do
include Helpers
let(:user) { FactoryBot.create(:user) }
let(:case_log) do
let(:lettings_log) do
FactoryBot.create(
:case_log,
:lettings_log,
:in_progress,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
)
end
let(:id) { case_log.id }
let(:id) { lettings_log.id }
before do
RequestHelper.stub_http_requests
@ -23,42 +23,42 @@ RSpec.describe "Checkboxes" do
context "when exclusive checkbox is selected", js: true do
it "deselects all other checkboxes" do
visit("/logs/#{id}/accessibility-requirements")
page.check("case-log-accessibility-requirements-housingneeds-a-field", allow_label_click: true)
page.check("lettings-log-accessibility-requirements-housingneeds-a-field", allow_label_click: true)
click_button("Save and continue")
case_log.reload
expect(case_log["housingneeds_a"]).to eq(1)
lettings_log.reload
expect(lettings_log["housingneeds_a"]).to eq(1)
visit("/logs/#{id}/accessibility-requirements")
page.check("case-log-accessibility-requirements-housingneeds-h-field", allow_label_click: true)
page.check("lettings-log-accessibility-requirements-housingneeds-h-field", allow_label_click: true)
click_button("Save and continue")
case_log.reload
expect(case_log["housingneeds_a"]).to eq(0)
expect(case_log["housingneeds_h"]).to eq(1)
lettings_log.reload
expect(lettings_log["housingneeds_a"]).to eq(0)
expect(lettings_log["housingneeds_h"]).to eq(1)
end
end
context "when a checkbox question is submitted with invalid answers" do
before do
allow(case_log).to receive(:update).and_return(false)
lettings_log.update!(illness: 100)
allow(lettings_log).to receive(:update).and_return(false)
end
it "shows an error summary" do
visit("/logs/#{id}/accessibility-requirements")
page.check("case-log-accessibility-requirements-housingneeds-a-field")
page.check("case-log-accessibility-requirements-housingneeds-c-field")
visit("/logs/#{id}/condition-effects")
page.check("lettings-log-condition-effects-illness-type-1-field")
page.check("lettings-log-condition-effects-illness-type-2-field")
click_button("Save and continue")
expect(page).to have_title("Error")
end
it "persists the original selections" do
visit("/logs/#{id}/accessibility-requirements")
page.check("case-log-accessibility-requirements-housingneeds-a-field")
page.check("case-log-accessibility-requirements-housingneeds-c-field")
visit("/logs/#{id}/condition-effects")
page.check("lettings-log-condition-effects-illness-type-1-field")
page.check("lettings-log-condition-effects-illness-type-2-field")
click_button("Save and continue")
expect(page).to have_checked_field("case-log-accessibility-requirements-housingneeds-a-field")
expect(page).to have_checked_field("case-log-accessibility-requirements-housingneeds-c-field")
expect(page).to have_checked_field("lettings-log-condition-effects-illness-type-2-field")
end
end
end

22
spec/features/form/conditional_questions_spec.rb

@ -4,15 +4,15 @@ require_relative "helpers"
RSpec.describe "Form Conditional Questions" do
include Helpers
let(:user) { FactoryBot.create(:user) }
let(:case_log) do
let(:lettings_log) do
FactoryBot.create(
:case_log,
:lettings_log,
:in_progress,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
)
end
let(:id) { case_log.id }
let(:id) { lettings_log.id }
before do
sign_in user
@ -27,20 +27,20 @@ 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-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-1-field", allow_label_click: true)
expect(page).to have_field("case-log-leftreg-field", with: "")
choose("lettings-log-armedforces-1-field", allow_label_click: true)
fill_in("lettings-log-leftreg-field", with: "text")
choose("lettings-log-armedforces-4-field", allow_label_click: true)
expect(page).not_to have_field("lettings-log-leftreg-field")
choose("lettings-log-armedforces-1-field", allow_label_click: true)
expect(page).to have_field("lettings-log-leftreg-field", with: "")
end
end
context "when a conditional question has a saved answer", js: true do
it "is displayed correctly" do
case_log.update!(postcode_known: 1, postcode_full: "NW1 6RT")
lettings_log.update!(postcode_known: 1, postcode_full: "NW1 6RT")
visit("/logs/#{id}/property-postcode")
expect(page).to have_field("case-log-postcode-full-field", with: "NW1 6RT")
expect(page).to have_field("lettings-log-postcode-full-field", with: "NW1 6RT")
end
end
end

40
spec/features/form/form_navigation_spec.rb

@ -4,25 +4,25 @@ require_relative "helpers"
RSpec.describe "Form Navigation" do
include Helpers
let(:user) { FactoryBot.create(:user) }
let(:case_log) do
let(:lettings_log) do
FactoryBot.create(
:case_log,
:lettings_log,
:in_progress,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
created_by: user,
)
end
let(:empty_case_log) do
let(:empty_lettings_log) do
FactoryBot.create(
:case_log,
:lettings_log,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
created_by: user,
)
end
let(:id) { case_log.id }
let(:id) { lettings_log.id }
let(:question_answers) do
{
tenancycode: { type: "text", answer: "BZ737", path: "tenant-code-test" },
@ -41,7 +41,7 @@ RSpec.describe "Form Navigation" do
it "redirects to the task list for the new log" do
visit("/logs")
click_button("Create a new lettings log")
id = CaseLog.order(created_at: :desc).first.id
id = LettingsLog.order(created_at: :desc).first.id
expect(page).to have_content("Log #{id}")
end
end
@ -49,7 +49,7 @@ RSpec.describe "Form Navigation" do
describe "Viewing a log" do
it "questions can be accessed by url" do
visit("/logs/#{id}/person-1-age")
expect(page).to have_field("case-log-age1-field")
expect(page).to have_field("lettings-log-age1-field")
end
it "a question page leads to the next question defined in the form definition" do
@ -62,15 +62,15 @@ RSpec.describe "Form Navigation" do
end
it "a question page has a Skip for now link that lets you move on to the next question without inputting anything" do
visit("logs/#{empty_case_log.id}/tenant-code-test")
visit("logs/#{empty_lettings_log.id}/tenant-code-test")
click_link(text: "Skip for now")
expect(page).to have_current_path("/logs/#{empty_case_log.id}/person-1-age")
expect(page).to have_current_path("/logs/#{empty_lettings_log.id}/person-1-age")
end
it "routes to check answers when skipping on the last page in the form" do
visit("logs/#{empty_case_log.id}/propcode")
visit("logs/#{empty_lettings_log.id}/propcode")
click_link(text: "Skip for now")
expect(page).to have_current_path("/logs/#{empty_case_log.id}/household-characteristics/check-answers")
expect(page).to have_current_path("/logs/#{empty_lettings_log.id}/household-characteristics/check-answers")
end
describe "Back link directs correctly", js: true do
@ -86,14 +86,14 @@ RSpec.describe "Form Navigation" do
click_button("Save and continue")
visit("/logs/#{id}/person-1-age")
click_link(text: "Back")
expect(page).to have_field("case-log-tenancycode-field")
expect(page).to have_field("lettings-log-tenancycode-field")
end
it "doesn't get stuck in infinite loops", js: true do
visit("/logs")
visit("/logs/#{id}/net-income")
fill_in("case-log-earnings-field", with: 740)
choose("case-log-incfreq-1-field", allow_label_click: true)
fill_in("lettings-log-earnings-field", with: 740)
choose("lettings-log-incfreq-1-field", allow_label_click: true)
click_button("Save and continue")
click_link(text: "Back")
click_link(text: "Back")
@ -119,13 +119,13 @@ RSpec.describe "Form Navigation" do
end
context "when clicking save and continue on a mandatory question with no input" do
let(:id) { empty_case_log.id }
let(:id) { empty_lettings_log.id }
it "shows a validation error on radio questions" do
visit("/logs/#{id}/renewal")
click_button("Save and continue")
expect(page).to have_selector("#error-summary-title")
expect(page).to have_selector("#case-log-renewal-error")
expect(page).to have_selector("#lettings-log-renewal-error")
expect(page).to have_title("Error")
end
@ -133,7 +133,7 @@ RSpec.describe "Form Navigation" do
visit("/logs/#{id}/tenancy-start-date")
click_button("Save and continue")
expect(page).to have_selector("#error-summary-title")
expect(page).to have_selector("#case-log-startdate-error")
expect(page).to have_selector("#lettings-log-startdate-error")
expect(page).to have_title("Error")
end
@ -141,10 +141,10 @@ RSpec.describe "Form Navigation" do
context "when the conditional question is required but not answered" do
it "shows a validation error for the conditional question" do
visit("/logs/#{id}/armed-forces")
choose("case-log-armedforces-1-field", allow_label_click: true)
choose("lettings-log-armedforces-1-field", allow_label_click: true)
click_button("Save and continue")
expect(page).to have_selector("#error-summary-title")
expect(page).to have_selector("#case-log-leftreg-error")
expect(page).to have_selector("#lettings-log-leftreg-error")
expect(page).to have_title("Error")
end
end
@ -152,7 +152,7 @@ RSpec.describe "Form Navigation" do
end
context "when clicking save and continue on an optional question with no input" do
let(:id) { empty_case_log.id }
let(:id) { empty_lettings_log.id }
it "does not show a validation error" do
visit("/logs/#{id}/tenant-code")

18
spec/features/form/helpers.rb

@ -1,18 +1,18 @@
module Helpers
def fill_in_number_question(case_log_id, question, value, path)
visit("/logs/#{case_log_id}/#{path}")
fill_in("case-log-#{question.to_s.dasherize}-field", with: value)
def fill_in_number_question(lettings_log_id, question, value, path)
visit("/logs/#{lettings_log_id}/#{path}")
fill_in("lettings-log-#{question.to_s.dasherize}-field", with: value)
click_button("Save and continue")
end
def answer_all_questions_in_income_subsection(case_log)
visit("/logs/#{case_log.id}/net-income")
fill_in("case-log-earnings-field", with: 18_000)
choose("case-log-incfreq-2-field")
def answer_all_questions_in_income_subsection(lettings_log)
visit("/logs/#{lettings_log.id}/net-income")
fill_in("lettings-log-earnings-field", with: 18_000)
choose("lettings-log-incfreq-2-field")
click_button("Save and continue")
choose("case-log-benefits-0-field")
choose("lettings-log-benefits-0-field")
click_button("Save and continue")
choose("case-log-hb-1-field")
choose("lettings-log-hb-1-field")
click_button("Save and continue")
end

66
spec/features/form/page_routing_spec.rb

@ -4,16 +4,16 @@ require_relative "helpers"
RSpec.describe "Form Page Routing" do
include Helpers
let(:user) { FactoryBot.create(:user) }
let(:case_log) do
let(:lettings_log) do
FactoryBot.create(
:case_log,
:lettings_log,
:in_progress,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
)
end
let(:id) { case_log.id }
let(:validator) { case_log._validators[nil].first }
let(:id) { lettings_log.id }
let(:validator) { lettings_log._validators[nil].first }
before do
allow(validator).to receive(:validate_pregnancy).and_return(true)
@ -24,26 +24,26 @@ 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-1-field", allow_label_click: true)
choose("lettings-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-2-field", allow_label_click: true)
choose("lettings-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
it "can route based on multiple conditions", js: true do
visit("/logs/#{id}/person-1-gender")
choose("case-log-sex1-f-field", allow_label_click: true)
choose("lettings-log-sex1-f-field", allow_label_click: true)
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-2-field", allow_label_click: true)
choose("lettings-log-preg-occ-2-field", allow_label_click: true)
click_button("Save and continue")
expect(page).to have_current_path("/logs/#{id}/conditional-question-no-page")
choose("case-log-cbl-0-field", allow_label_click: true)
choose("lettings-log-cbl-0-field", allow_label_click: true)
click_button("Save and continue")
expect(page).to have_current_path("/logs/#{id}/conditional-question/check-answers")
end
@ -51,7 +51,7 @@ RSpec.describe "Form Page Routing" do
context "when the answers are inferred", js: true do
it "shows question if the answer could not be inferred" do
visit("/logs/#{id}/property-postcode")
fill_in("case-log-postcode-full-field", with: "PO5 3TE")
fill_in("lettings-log-postcode-full-field", with: "PO5 3TE")
click_button("Save and continue")
expect(page).to have_current_path("/logs/#{id}/do-you-know-the-local-authority")
end
@ -67,7 +67,7 @@ RSpec.describe "Form Page Routing" do
.to_return(status: 200, body: "{\"status\":200,\"result\":{\"admin_district\":\"Manchester\", \"codes\":{\"admin_district\": \"E08000003\"}}}", headers: {})
visit("/logs/#{id}/property-postcode")
fill_in("case-log-postcode-full-field", with: "P0 5ST")
fill_in("lettings-log-postcode-full-field", with: "P0 5ST")
click_button("Save and continue")
expect(page).to have_current_path("/logs/#{id}/property-wheelchair-accessible")
end
@ -76,47 +76,47 @@ RSpec.describe "Form Page Routing" do
context "when answer is invalid" do
it "shows error with invalid value in the field" do
visit("/logs/#{id}/property-postcode")
fill_in("case-log-postcode-full-field", with: "fake_postcode")
fill_in("lettings-log-postcode-full-field", with: "fake_postcode")
click_button("Save and continue")
expect(page).to have_current_path("/logs/#{id}/property-postcode")
expect(find("#case-log-postcode-full-field-error").value).to eq("fake_postcode")
expect(find("#lettings-log-postcode-full-field-error").value).to eq("fake_postcode")
end
it "does not reset the displayed date" do
case_log.update!(startdate: "2021/10/13")
lettings_log.update!(startdate: "2021/10/13")
visit("/logs/#{id}/tenancy-start-date")
fill_in("case_log[startdate(1i)]", with: "202")
fill_in("case_log[startdate(2i)]", with: "32")
fill_in("case_log[startdate(3i)]", with: "0")
fill_in("lettings_log[startdate(1i)]", with: "202")
fill_in("lettings_log[startdate(2i)]", with: "32")
fill_in("lettings_log[startdate(3i)]", with: "0")
click_button("Save and continue")
expect(page).to have_current_path("/logs/#{id}/tenancy-start-date")
expect(find_field("case_log[startdate(3i)]").value).to eq("13")
expect(find_field("case_log[startdate(2i)]").value).to eq("10")
expect(find_field("case_log[startdate(1i)]").value).to eq("2021")
expect(find_field("lettings_log[startdate(3i)]").value).to eq("13")
expect(find_field("lettings_log[startdate(2i)]").value).to eq("10")
expect(find_field("lettings_log[startdate(1i)]").value).to eq("2021")
end
it "does not reset the displayed date if it's empty" do
case_log.update!(startdate: nil)
lettings_log.update!(startdate: nil)
visit("/logs/#{id}/tenancy-start-date")
fill_in("case_log[startdate(1i)]", with: "202")
fill_in("case_log[startdate(2i)]", with: "32")
fill_in("case_log[startdate(3i)]", with: "0")
fill_in("lettings_log[startdate(1i)]", with: "202")
fill_in("lettings_log[startdate(2i)]", with: "32")
fill_in("lettings_log[startdate(3i)]", with: "0")
click_button("Save and continue")
expect(page).to have_current_path("/logs/#{id}/tenancy-start-date")
expect(find_field("case_log[startdate(3i)]").value).to eq(nil)
expect(find_field("case_log[startdate(2i)]").value).to eq(nil)
expect(find_field("case_log[startdate(1i)]").value).to eq(nil)
expect(find_field("lettings_log[startdate(3i)]").value).to eq(nil)
expect(find_field("lettings_log[startdate(2i)]").value).to eq(nil)
expect(find_field("lettings_log[startdate(1i)]").value).to eq(nil)
end
end
context "when completing the setup section" do
context "with a supported housing log" do
let(:case_log) do
let(:lettings_log) do
FactoryBot.create(
:case_log,
:lettings_log,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
needstype: 2,
@ -129,17 +129,17 @@ RSpec.describe "Form Page Routing" do
before do
FactoryBot.create(:location, scheme:, startdate: Time.zone.today + 20.days)
visit("/logs/#{case_log.id}/scheme")
select(scheme.service_name, from: "case_log[scheme_id]")
visit("/logs/#{lettings_log.id}/scheme")
select(scheme.service_name, from: "lettings_log[scheme_id]")
click_button("Save and continue")
end
it "does not route to the scheme location question" do
expect(page).to have_current_path("/logs/#{case_log.id}/renewal")
expect(page).to have_current_path("/logs/#{lettings_log.id}/renewal")
end
it "infers the scheme location" do
expect(case_log.reload.location_id).to eq(active_location.id)
expect(lettings_log.reload.location_id).to eq(active_location.id)
end
end
end

44
spec/features/form/progressive_total_field_spec.rb

@ -4,9 +4,9 @@ require_relative "helpers"
RSpec.describe "Accessible Automcomplete" do
include Helpers
let(:user) { FactoryBot.create(:user) }
let(:case_log) do
let(:lettings_log) do
FactoryBot.create(
:case_log,
:lettings_log,
:in_progress,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
@ -18,35 +18,35 @@ RSpec.describe "Accessible Automcomplete" do
end
it "does not show when js is not enabled" do
visit("/logs/#{case_log.id}/rent")
visit("/logs/#{lettings_log.id}/rent")
expect(page).to have_selector("#tcharge_div", visible: :all)
end
it "does show when js is enabled and calculates the total", js: true do
visit("/logs/#{case_log.id}/rent")
visit("/logs/#{lettings_log.id}/rent")
expect(page).to have_selector("#tcharge_div")
fill_in("case-log-brent-field", with: 5)
expect(find("#case-log-tcharge-field").value).to eq("5.00")
fill_in("case-log-pscharge-field", with: 3)
expect(find("#case-log-tcharge-field").value).to eq("8.00")
fill_in("lettings-log-brent-field", with: 5)
expect(find("#lettings-log-tcharge-field").value).to eq("5.00")
fill_in("lettings-log-pscharge-field", with: 3)
expect(find("#lettings-log-tcharge-field").value).to eq("8.00")
end
it "total displays despite error message", js: true do
visit("/logs/#{case_log.id}/rent")
choose("case-log-period-1-field", allow_label_click: true)
fill_in("case-log-brent-field", with: 500)
fill_in("case-log-scharge-field", with: 50)
fill_in("case-log-pscharge-field", with: 50)
fill_in("case-log-supcharg-field", with: 5000)
expect(find("#case-log-tcharge-field").value).to eq("5600.00")
visit("/logs/#{lettings_log.id}/rent")
choose("lettings-log-period-1-field", allow_label_click: true)
fill_in("lettings-log-brent-field", with: 500)
fill_in("lettings-log-scharge-field", with: 50)
fill_in("lettings-log-pscharge-field", with: 50)
fill_in("lettings-log-supcharg-field", with: 5000)
expect(find("#lettings-log-tcharge-field").value).to eq("5600.00")
click_button("Save and continue")
expect(page).to have_selector(".govuk-error-summary")
fill_in("case-log-scharge-field", with: nil)
fill_in("case-log-pscharge-field", with: nil)
fill_in("case-log-supcharg-field-error", with: nil)
fill_in("case-log-brent-field", with: 500)
expect(find("#case-log-tcharge-field").value).to eq("500.00")
fill_in("case-log-supcharg-field-error", with: 50)
expect(find("#case-log-tcharge-field").value).to eq("550.00")
fill_in("lettings-log-scharge-field", with: nil)
fill_in("lettings-log-pscharge-field", with: nil)
fill_in("lettings-log-supcharg-field-error", with: nil)
fill_in("lettings-log-brent-field", with: 500)
expect(find("#lettings-log-tcharge-field").value).to eq("500.00")
fill_in("lettings-log-supcharg-field-error", with: 50)
expect(find("#lettings-log-tcharge-field").value).to eq("550.00")
end
end

46
spec/features/form/saving_data_spec.rb

@ -4,18 +4,18 @@ require_relative "helpers"
RSpec.describe "Form Saving Data" do
include Helpers
let(:user) { FactoryBot.create(:user) }
let(:case_log) do
let(:lettings_log) do
FactoryBot.create(
:case_log,
:lettings_log,
:in_progress,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
)
end
let(:id) { case_log.id }
let(:case_log_with_checkbox_questions_answered) do
let(:id) { lettings_log.id }
let(:lettings_log_with_checkbox_questions_answered) do
FactoryBot.create(
:case_log, :in_progress,
:lettings_log, :in_progress,
housingneeds_a: 1,
owning_organisation: user.organisation,
managing_organisation: user.organisation
@ -38,18 +38,18 @@ RSpec.describe "Form Saving Data" do
type = hsh[:type]
answer = hsh[:answer].respond_to?(:keys) ? hsh[:answer].keys.first : hsh[:answer]
path = hsh[:path]
original_value = case_log.send(question)
original_value = lettings_log.send(question)
visit("/logs/#{id}/#{path.to_s.dasherize}")
case type
when "text"
fill_in("case-log-#{question.to_s.dasherize}-field", with: answer)
fill_in("lettings-log-#{question.to_s.dasherize}-field", with: answer)
when "radio"
choose("case-log-#{question.to_s.dasherize}-#{hsh[:answer].keys.first.downcase}-field")
choose("lettings-log-#{question.to_s.dasherize}-#{hsh[:answer].keys.first.downcase}-field")
else
fill_in("case-log-#{question.to_s.dasherize}-field", with: answer)
fill_in("lettings-log-#{question.to_s.dasherize}-field", with: answer)
end
expect { click_button("Save and continue") }.to change {
case_log.reload.send(question.to_s)
lettings_log.reload.send(question.to_s)
}.from(original_value).to(answer)
end
end
@ -57,37 +57,37 @@ RSpec.describe "Form Saving Data" do
it "updates total value of the rent", js: true do
visit("/logs/#{id}/rent")
fill_in("case-log-brent-field", with: 3.02)
expect(page.find("#case-log-tcharge-field")).to have_content("3.02")
fill_in("lettings-log-brent-field", with: 3.02)
expect(page.find("#lettings-log-tcharge-field")).to have_content("3.02")
fill_in("case-log-scharge-field", with: 2.8)
expect(page.find("#case-log-tcharge-field")).to have_content("5.82")
fill_in("lettings-log-scharge-field", with: 2.8)
expect(page.find("#lettings-log-tcharge-field")).to have_content("5.82")
fill_in("case-log-pscharge-field", with: 1)
expect(page.find("#case-log-tcharge-field")).to have_content("6.82")
fill_in("lettings-log-pscharge-field", with: 1)
expect(page.find("#lettings-log-tcharge-field")).to have_content("6.82")
fill_in("case-log-supcharg-field", with: 4.11)
expect(page.find("#case-log-tcharge-field")).to have_content("10.93")
fill_in("lettings-log-supcharg-field", with: 4.11)
expect(page.find("#lettings-log-tcharge-field")).to have_content("10.93")
end
it "displays number answers in inputs if they are already saved" do
visit("/logs/#{id}/property-postcode")
expect(page).to have_field("case-log-postcode-full-field", with: case_log.postcode_full)
expect(page).to have_field("lettings-log-postcode-full-field", with: lettings_log.postcode_full)
end
it "displays text answers in inputs if they are already saved" do
visit("/logs/#{id}/person-1-age")
expect(page).to have_field("case-log-age1-field", with: "17")
expect(page).to have_field("lettings-log-age1-field", with: "17")
end
it "displays checkbox answers in inputs if they are already saved" do
visit("/logs/#{case_log_with_checkbox_questions_answered.id.to_s.dasherize}/accessibility-requirements")
visit("/logs/#{lettings_log_with_checkbox_questions_answered.id.to_s.dasherize}/accessibility-requirements")
expect(page).to have_checked_field(
"case-log-accessibility-requirements-housingneeds-a-field",
"lettings-log-accessibility-requirements-housingneeds-a-field",
visible: :all,
)
expect(page).to have_unchecked_field(
"case-log-accessibility-requirements-housingneeds-b-field",
"lettings-log-accessibility-requirements-housingneeds-b-field",
visible: :all,
)
end

26
spec/features/form/tasklist_page_spec.rb

@ -4,27 +4,27 @@ require_relative "helpers"
RSpec.describe "Task List" do
include Helpers
let(:user) { FactoryBot.create(:user) }
let(:case_log) do
let(:lettings_log) do
FactoryBot.create(
:case_log,
:lettings_log,
:in_progress,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
created_by: user,
)
end
let(:completed_case_log) do
let(:completed_lettings_log) do
FactoryBot.create(
:case_log,
:lettings_log,
:completed,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
created_by: user,
)
end
let(:empty_case_log) do
let(:empty_lettings_log) do
FactoryBot.create(
:case_log,
:lettings_log,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
created_by: user,
@ -32,7 +32,7 @@ RSpec.describe "Task List" do
end
let(:setup_completed_log) do
FactoryBot.create(
:case_log,
:lettings_log,
:about_completed,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
@ -40,15 +40,15 @@ RSpec.describe "Task List" do
created_by: user,
)
end
let(:id) { case_log.id }
let(:status) { case_log.status }
let(:id) { lettings_log.id }
let(:status) { lettings_log.status }
before do
sign_in user
end
it "shows if the section has not been started" do
visit("/logs/#{empty_case_log.id}")
visit("/logs/#{empty_lettings_log.id}")
expect(page).to have_content("This log has not been started.")
end
@ -63,10 +63,10 @@ RSpec.describe "Task List" do
expect(page).to have_link("Skip to next incomplete section", href: /#household-characteristics/)
end
it "has a review section which has a button that allows the data inputter to review the case log" do
visit("/logs/#{completed_case_log.id}")
it "has a review section which has a button that allows the data inputter to review the lettings log" do
visit("/logs/#{completed_lettings_log.id}")
expect(page).to have_content("review and make changes to this log")
click_link(text: "review and make changes to this log")
expect(page).to have_current_path("/logs/#{completed_case_log.id}/review")
expect(page).to have_current_path("/logs/#{completed_lettings_log.id}/review")
end
end

60
spec/features/form/validations_spec.rb

@ -8,24 +8,24 @@ RSpec.describe "validations" do
include Helpers
let(:user) { FactoryBot.create(:user) }
let(:case_log) do
let(:lettings_log) do
FactoryBot.create(
:case_log,
:lettings_log,
:in_progress,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
)
end
let(:empty_case_log) do
let(:empty_lettings_log) do
FactoryBot.create(
:case_log,
:lettings_log,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
)
end
let(:completed_without_declaration) do
FactoryBot.create(
:case_log,
:lettings_log,
:completed,
owning_organisation: user.organisation,
managing_organisation: user.organisation,
@ -33,36 +33,36 @@ RSpec.describe "validations" do
declaration: nil,
)
end
let(:id) { case_log.id }
let(:id) { lettings_log.id }
describe "Question validation" do
context "when the tenant age is invalid" do
it "shows validation for under 0" do
visit("/logs/#{id}/person-1-age")
fill_in_number_question(empty_case_log.id, "age1", -5, "person-1-age")
fill_in_number_question(empty_lettings_log.id, "age1", -5, "person-1-age")
expect(page).to have_selector("#error-summary-title")
expect(page).to have_selector("#case-log-age1-error")
expect(page).to have_selector("#case-log-age1-field-error")
expect(page).to have_selector("#lettings-log-age1-error")
expect(page).to have_selector("#lettings-log-age1-field-error")
expect(page).to have_title("Error")
end
it "shows validation for over 120" do
visit("/logs/#{id}/person-1-age")
fill_in_number_question(empty_case_log.id, "age1", 121, "person-1-age")
fill_in_number_question(empty_lettings_log.id, "age1", 121, "person-1-age")
expect(page).to have_selector("#error-summary-title")
expect(page).to have_selector("#case-log-age1-error")
expect(page).to have_selector("#case-log-age1-field-error")
expect(page).to have_selector("#lettings-log-age1-error")
expect(page).to have_selector("#lettings-log-age1-field-error")
expect(page).to have_title("Error")
end
end
end
describe "date validation", js: true do
def fill_in_date(case_log_id, question, day, month, year, path)
visit("/logs/#{case_log_id}/#{path}")
fill_in("case_log[#{question}(1i)]", with: year)
fill_in("case_log[#{question}(2i)]", with: month)
fill_in("case_log[#{question}(3i)]", with: day)
def fill_in_date(lettings_log_id, question, day, month, year, path)
visit("/logs/#{lettings_log_id}/#{path}")
fill_in("lettings_log[#{question}(1i)]", with: year)
fill_in("lettings_log[#{question}(2i)]", with: month)
fill_in("lettings_log[#{question}(3i)]", with: day)
end
it "does not allow out of range dates to be submitted" do
@ -112,9 +112,9 @@ RSpec.describe "validations" do
describe "Soft Validation" do
context "when a weekly net income is above the expected amount for the given economic status but below the hard max" do
let(:case_log) do
let(:lettings_log) do
FactoryBot.create(
:case_log,
:lettings_log,
:in_progress,
ecstat1: 1,
owning_organisation: user.organisation,
@ -125,27 +125,27 @@ RSpec.describe "validations" do
let(:income_under_soft_limit) { 700 }
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-1-field", allow_label_click: true)
visit("/logs/#{lettings_log.id}/net-income")
fill_in("lettings-log-earnings-field", with: income_over_soft_limit)
choose("lettings-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_current_path("/logs/#{lettings_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")
expect(page).to have_content("You told us the lead tenant’s working situation is: full-time – 30 hours or more")
expect(page).to have_content("The household income you have entered is £750.00 every week")
choose("case-log-net-income-value-check-0-field", allow_label_click: true)
choose("lettings-log-net-income-value-check-0-field", allow_label_click: true)
click_button("Save and continue")
expect(page).to have_current_path("/logs/#{case_log.id}/net-income-uc-proportion")
expect(page).to have_current_path("/logs/#{lettings_log.id}/net-income-uc-proportion")
end
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-1-field", allow_label_click: true)
visit("/logs/#{lettings_log.id}/net-income")
fill_in("lettings-log-earnings-field", with: income_over_soft_limit)
choose("lettings-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)
choose("lettings-log-net-income-value-check-1-field", allow_label_click: true)
click_button("Save and continue")
expect(page).to have_current_path("/logs/#{case_log.id}/net-income")
expect(page).to have_current_path("/logs/#{lettings_log.id}/net-income")
end
end
end

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

Loading…
Cancel
Save