Browse Source

Merge branch 'main' into CLDC-2262-clear-validations-data-when-correcting

# Conflicts:
#	app/models/log.rb
#	app/models/validations/local_authority_validations.rb
pull/1570/head
natdeanlewissoftwire 3 years ago
parent
commit
f31d6145aa
  1. 2
      app/components/check_answers_summary_list_card_component.html.erb
  2. 4
      app/components/check_answers_summary_list_card_component.rb
  3. 40
      app/controllers/form_controller.rb
  4. 19
      app/controllers/lettings_logs_controller.rb
  5. 4
      app/controllers/logs_controller.rb
  6. 7
      app/controllers/merge_requests_controller.rb
  7. 36
      app/controllers/organisations_controller.rb
  8. 21
      app/controllers/sales_logs_controller.rb
  9. 5
      app/helpers/form_page_helper.rb
  10. 4
      app/helpers/interruption_screen_helper.rb
  11. 39
      app/helpers/logs_helper.rb
  12. 19
      app/jobs/email_csv_job.rb
  13. 55
      app/models/derived_variables/lettings_log_variables.rb
  14. 33
      app/models/derived_variables/sales_log_variables.rb
  15. 27
      app/models/derived_variables/shared_logic.rb
  16. 4
      app/models/form/lettings/pages/care_home_charges_value_check.rb
  17. 4
      app/models/form/lettings/pages/females_in_soft_age_range_in_pregnant_household_lead_age_value_check.rb
  18. 4
      app/models/form/lettings/pages/females_in_soft_age_range_in_pregnant_household_lead_hhmemb_value_check.rb
  19. 4
      app/models/form/lettings/pages/females_in_soft_age_range_in_pregnant_household_lead_value_check.rb
  20. 4
      app/models/form/lettings/pages/females_in_soft_age_range_in_pregnant_household_person_age_value_check.rb
  21. 4
      app/models/form/lettings/pages/females_in_soft_age_range_in_pregnant_household_person_value_check.rb
  22. 4
      app/models/form/lettings/pages/females_in_soft_age_range_in_pregnant_household_value_check.rb
  23. 6
      app/models/form/lettings/pages/lead_tenant_over_retirement_value_check.rb
  24. 22
      app/models/form/lettings/pages/lead_tenant_under_retirement_value_check.rb
  25. 4
      app/models/form/lettings/pages/max_rent_value_check.rb
  26. 4
      app/models/form/lettings/pages/min_rent_value_check.rb
  27. 26
      app/models/form/lettings/pages/net_income_value_check.rb
  28. 4
      app/models/form/lettings/pages/no_females_pregnant_household_lead_age_value_check.rb
  29. 4
      app/models/form/lettings/pages/no_females_pregnant_household_lead_hhmemb_value_check.rb
  30. 4
      app/models/form/lettings/pages/no_females_pregnant_household_lead_value_check.rb
  31. 4
      app/models/form/lettings/pages/no_females_pregnant_household_person_age_value_check.rb
  32. 4
      app/models/form/lettings/pages/no_females_pregnant_household_person_value_check.rb
  33. 4
      app/models/form/lettings/pages/no_females_pregnant_household_value_check.rb
  34. 7
      app/models/form/lettings/pages/person_over_retirement_value_check.rb
  35. 27
      app/models/form/lettings/pages/person_under_retirement_value_check.rb
  36. 9
      app/models/form/lettings/pages/property_major_repairs_value_check.rb
  37. 9
      app/models/form/lettings/pages/void_date_value_check.rb
  38. 6
      app/models/form/lettings/questions/no_retirement_value_check.rb
  39. 6
      app/models/form/lettings/questions/retirement_value_check.rb
  40. 3
      app/models/form/page.rb
  41. 4
      app/models/form/question.rb
  42. 8
      app/models/form/sales/pages/about_price_value_check.rb
  43. 11
      app/models/form/sales/pages/buyer1_income_value_check.rb
  44. 11
      app/models/form/sales/pages/buyer2_income_value_check.rb
  45. 9
      app/models/form/sales/pages/buyer_live_in_value_check.rb
  46. 4
      app/models/form/sales/pages/deposit_and_mortgage_value_check.rb
  47. 24
      app/models/form/sales/pages/deposit_value_check.rb
  48. 4
      app/models/form/sales/pages/discounted_sale_value_check.rb
  49. 18
      app/models/form/sales/pages/extra_borrowing_value_check.rb
  50. 19
      app/models/form/sales/pages/grant_value_check.rb
  51. 4
      app/models/form/sales/pages/handover_date_check.rb
  52. 5
      app/models/form/sales/pages/household_wheelchair_check.rb
  53. 15
      app/models/form/sales/pages/monthly_charges_value_check.rb
  54. 18
      app/models/form/sales/pages/mortgage_value_check.rb
  55. 11
      app/models/form/sales/pages/old_persons_shared_ownership_value_check.rb
  56. 9
      app/models/form/sales/pages/percentage_discount_value_check.rb
  57. 4
      app/models/form/sales/pages/person_student_not_child_value_check.rb
  58. 24
      app/models/form/sales/pages/retirement_value_check.rb
  59. 19
      app/models/form/sales/pages/savings_value_check.rb
  60. 8
      app/models/form/sales/pages/shared_ownership_deposit_value_check.rb
  61. 11
      app/models/form/sales/pages/staircase_bought_value_check.rb
  62. 2
      app/models/form/sales/questions/household_wheelchair_check.rb
  63. 26
      app/models/form_handler.rb
  64. 19
      app/models/lettings_log.rb
  65. 4
      app/models/log.rb
  66. 14
      app/models/sales_log.rb
  67. 0
      app/models/validations/local_authority_validations.rb
  68. 6
      app/models/validations/sales/financial_validations.rb
  69. 4
      app/models/validations/sales/soft_validations.rb
  70. 10
      app/models/validations/soft_validations.rb
  71. 1
      app/models/validations/tenancy_validations.rb
  72. 109
      app/services/bulk_upload/lettings/year2022/row_parser.rb
  73. 131
      app/services/bulk_upload/lettings/year2023/row_parser.rb
  74. 25
      app/services/bulk_upload/sales/year2022/row_parser.rb
  75. 107
      app/services/csv/sales_log_csv_service.rb
  76. 23
      app/services/imports/lettings_logs_import_service.rb
  77. 60
      app/services/imports/sales_logs_import_service.rb
  78. 4
      app/views/form/_check_answers_summary_list.html.erb
  79. 13
      app/views/form/_interruption_screen_banner.html.erb
  80. 46
      app/views/form/_interruption_screen_question.html.erb
  81. 2
      app/views/form/check_answers.html.erb
  82. 4
      app/views/form/page.html.erb
  83. 2
      app/views/form/review.html.erb
  84. 2
      app/views/logs/_log_list.html.erb
  85. 2
      app/views/logs/csv_confirmation.html.erb
  86. 4
      app/views/logs/index.html.erb
  87. 7
      app/views/merge_requests/confirm_telephone_number.html.erb
  88. 30
      app/views/merge_requests/new_organisation_address.html.erb
  89. 5
      app/views/merge_requests/new_organisation_telephone_number.html.erb
  90. 4
      app/views/organisations/logs.html.erb
  91. 377
      config/forms/2022_2023.json
  92. 57
      config/locales/en.yml
  93. 14
      config/routes.rb
  94. 9
      db/migrate/20230504111352_add_new_organisation_address.rb
  95. 5
      db/schema.rb
  96. 3
      package.json
  97. 14
      spec/components/check_answers_summary_list_card_component_spec.rb
  98. 25
      spec/factories/form.rb
  99. 21
      spec/factories/page.rb
  100. 6
      spec/factories/question.rb
  101. Some files were not shown because too many files have changed in this diff Show More

2
app/components/check_answers_summary_list_card_component.html.erb

@ -36,7 +36,7 @@
<% if @log.collection_period_open? %>
<% row.action(
text: question.action_text(log),
href: question.action_href(log, question.page.id),
href: action_href(log, question.page.id),
visually_hidden_text: question.check_answer_label.to_s.downcase,
) %>
<% end %>

4
app/components/check_answers_summary_list_card_component.rb

@ -28,6 +28,10 @@ class CheckAnswersSummaryListCardComponent < ViewComponent::Base
"Person #{question.check_answers_card_number}"
end
def action_href(log, page_id, referrer = "check_answers")
send("#{log.model_name.param_key}_#{page_id}_path", log, referrer:)
end
private
def unanswered_value

40
app/controllers/form_controller.rb

@ -11,6 +11,7 @@ class FormController < ApplicationController
mandatory_questions_with_no_response = mandatory_questions_with_no_response(responses_for_page)
if mandatory_questions_with_no_response.empty? && @log.update(responses_for_page.merge(updated_by: current_user))
flash[:notice] = "You have successfully updated #{@page.questions.map(&:check_answer_label).first.downcase}" if previous_interruption_screen_page_id.present?
redirect_to(successful_redirect_path)
else
mandatory_questions_with_no_response.map do |question|
@ -45,11 +46,16 @@ class FormController < ApplicationController
end
def show_page
if request.params["referrer"] == "interruption_screen"
@interruption_page_id = URI.parse(request.headers["HTTP_REFERER"]).path.split("/").last.underscore
@interruption_page_referrer_type = referrer_from_query
end
if @log
page_id = request.path.split("/")[-1].underscore
@page = form.get_page(page_id)
@subsection = form.subsection_for_page(@page)
if @page.routed_to?(@log, current_user)
if @page.routed_to?(@log, current_user) || is_referrer_type?("interruption_screen")
render "form/page"
else
redirect_to @log.lettings? ? lettings_log_path(@log) : sales_log_path(@log)
@ -115,13 +121,33 @@ private
end
end
def is_referrer_check_answers?
referrer = request.headers["HTTP_REFERER"].presence || ""
referrer.present? && CGI.parse(referrer.split("?")[-1]).present? && CGI.parse(referrer.split("?")[-1])["referrer"][0] == "check_answers"
def is_referrer_type?(referrer_type)
referrer_from_query == referrer_type
end
def referrer_from_query
referrer = request.headers["HTTP_REFERER"]
return unless referrer
query_params = URI.parse(referrer).query
return unless query_params
parsed_params = CGI.parse(query_params)
return unless parsed_params["referrer"]
parsed_params["referrer"][0]
end
def previous_interruption_screen_page_id
params[@log.model_name.param_key]["interruption_page_id"]
end
def previous_interruption_screen_referrer
params[@log.model_name.param_key]["interruption_page_referrer_type"].presence
end
def successful_redirect_path
if is_referrer_check_answers?
if is_referrer_type?("check_answers")
next_page_id = form.next_page_id(@page, @log, current_user)
next_page = form.get_page(next_page_id)
previous_page = form.previous_page_id(@page, @log, current_user)
@ -132,6 +158,10 @@ private
return send("#{@log.model_name.param_key}_#{form.subsection_for_page(@page).id}_check_answers_path", @log)
end
end
if previous_interruption_screen_page_id.present?
return send("#{@log.class.name.underscore}_#{previous_interruption_screen_page_id}_path", @log, { referrer: previous_interruption_screen_referrer }.compact)
end
redirect_path = form.next_page_redirect_path(@page, @log, current_user)
send(redirect_path, @log)
end

19
app/controllers/lettings_logs_controller.rb

@ -7,11 +7,6 @@ class LettingsLogsController < LogsController
before_action :extract_bulk_upload_from_session_filters, only: [:index]
before_action :redirect_if_bulk_upload_resolved, only: [:index]
def authenticate_scope!
codes_only_export = codes_only_export?(params)
head :unauthorized and return unless current_user.support? || !codes_only_export
end
def index
respond_to do |format|
format.html do
@ -86,19 +81,13 @@ class LettingsLogsController < LogsController
def download_csv
unpaginated_filtered_logs = filtered_logs(current_user.lettings_logs, search_term, @session_filters)
codes_only = codes_only_export?(params)
render "download_csv", locals: { search_term:, count: unpaginated_filtered_logs.size, post_path: email_csv_lettings_logs_path, codes_only: }
end
def codes_only_export?(params)
params.require(:codes_only) == "true"
render "download_csv", locals: { search_term:, count: unpaginated_filtered_logs.size, post_path: email_csv_lettings_logs_path, codes_only: codes_only_export? }
end
def email_csv
all_orgs = params["organisation_select"] == "all"
codes_only_export = params.require(:codes_only) == "true"
EmailCsvJob.perform_later(current_user, search_term, @session_filters, all_orgs, nil, codes_only_export)
EmailCsvJob.perform_later(current_user, search_term, @session_filters, all_orgs, nil, codes_only_export?)
redirect_to csv_confirmation_lettings_logs_path
end
@ -124,6 +113,10 @@ class LettingsLogsController < LogsController
private
def authenticate_scope!
head :unauthorized and return if codes_only_export? && !current_user.support?
end
def redirect_if_bulk_upload_resolved
if @bulk_upload && @bulk_upload.lettings_logs.in_progress.count.zero?
redirect_to resume_bulk_upload_lettings_result_path(@bulk_upload)

4
app/controllers/logs_controller.rb

@ -28,6 +28,10 @@ private
end
end
def codes_only_export?
params.require(:codes_only) == "true"
end
def post_create_redirect_url
raise "implement in sub class"
end

7
app/controllers/merge_requests_controller.rb

@ -8,6 +8,7 @@ class MergeRequestsController < ApplicationController
confirm_telephone_number
new_organisation_name
new_organisation_address
new_organisation_telephone_number
merge_date
]
before_action :authenticate_user!
@ -17,6 +18,7 @@ class MergeRequestsController < ApplicationController
def confirm_telephone_number; end
def new_organisation_name; end
def new_organisation_address; end
def new_organisation_telephone_number; end
def merge_date; end
def create
@ -79,6 +81,8 @@ private
merge_date_merge_request_path(@merge_request)
when "new_organisation_name"
new_organisation_address_merge_request_path(@merge_request)
when "new_organisation_address"
new_organisation_telephone_number_merge_request_path(@merge_request)
end
end
@ -108,6 +112,9 @@ private
:telephone_number_correct,
:new_telephone_number,
:new_organisation_name,
:new_organisation_address_line1,
:new_organisation_address_line2,
:new_organisation_postcode,
)
if merge_params[:requesting_organisation_id].present? && (current_user.data_coordinator? || current_user.data_provider?)

36
app/controllers/organisations_controller.rb

@ -6,8 +6,8 @@ class OrganisationsController < ApplicationController
before_action :authenticate_user!
before_action :find_resource, except: %i[index new create]
before_action :authenticate_scope!, except: [:index]
before_action -> { session_filters(specific_org: true) }, if: -> { current_user.support? || current_user.organisation.has_managing_agents? }, only: %i[lettings_logs sales_logs email_csv download_csv]
before_action :set_session_filters, if: -> { current_user.support? || current_user.organisation.has_managing_agents? }, only: %i[lettings_logs sales_logs email_csv download_csv]
before_action -> { session_filters(specific_org: true) }, if: -> { current_user.support? || current_user.organisation.has_managing_agents? }, only: %i[lettings_logs sales_logs email_lettings_csv download_lettings_csv email_sales_csv download_sales_csv]
before_action :set_session_filters, if: -> { current_user.support? || current_user.organisation.has_managing_agents? }, only: %i[lettings_logs sales_logs email_lettings_csv download_lettings_csv email_sales_csv download_sales_csv]
def index
redirect_to organisation_path(current_user.organisation) unless current_user.support?
@ -99,23 +99,23 @@ class OrganisationsController < ApplicationController
@pagy, @logs = pagy(unpaginated_filtered_logs)
@searched = search_term.presence
@total_count = organisation_logs.size
@log_type = :lettings
render "logs", layout: "application"
end
end
end
def download_csv
def download_lettings_csv
organisation_logs = LettingsLog.visible.where(owning_organisation_id: @organisation.id)
unpaginated_filtered_logs = filtered_logs(organisation_logs, search_term, @session_filters)
codes_only = params.require(:codes_only) == "true"
render "logs/download_csv", locals: { search_term:, count: unpaginated_filtered_logs.size, post_path: logs_email_csv_organisation_path, codes_only: }
render "logs/download_csv", locals: { search_term:, count: unpaginated_filtered_logs.size, post_path: lettings_logs_email_csv_organisation_path, codes_only: }
end
def email_csv
codes_only_export = params.require(:codes_only) == "true"
EmailCsvJob.perform_later(current_user, search_term, @session_filters, false, @organisation, codes_only_export)
redirect_to logs_csv_confirmation_organisation_path
def email_lettings_csv
EmailCsvJob.perform_later(current_user, search_term, @session_filters, false, @organisation, codes_only_export?)
redirect_to lettings_logs_csv_confirmation_organisation_path
end
def sales_logs
@ -128,6 +128,7 @@ class OrganisationsController < ApplicationController
@pagy, @logs = pagy(unpaginated_filtered_logs)
@searched = search_term.presence
@total_count = organisation_logs.size
@log_type = :sales
render "logs", layout: "application"
end
@ -137,6 +138,19 @@ class OrganisationsController < ApplicationController
end
end
def download_sales_csv
organisation_logs = SalesLog.visible.where(owning_organisation_id: @organisation.id)
unpaginated_filtered_logs = filtered_logs(organisation_logs, search_term, @session_filters)
codes_only = params.require(:codes_only) == "true"
render "logs/download_csv", locals: { search_term:, count: unpaginated_filtered_logs.size, post_path: sales_logs_email_csv_organisation_path, codes_only: }
end
def email_sales_csv
EmailCsvJob.perform_later(current_user, search_term, @session_filters, false, @organisation, codes_only_export?, "sales")
redirect_to sales_logs_csv_confirmation_organisation_path
end
def merge_request
@merge_request = MergeRequest.new
end
@ -147,12 +161,16 @@ private
params.require(:organisation).permit(:name, :address_line1, :address_line2, :postcode, :phone, :holds_own_stock, :provider_type, :housing_registration_no)
end
def codes_only_export?
params.require(:codes_only) == "true"
end
def search_term
params["search"]
end
def authenticate_scope!
if %w[create new lettings_logs download_csv email_csv].include? action_name
if %w[create new lettings_logs sales_logs download_lettings_csv email_lettings_csv email_sales_csv download_sales_csv].include? action_name
head :unauthorized and return unless current_user.support?
elsif current_user.organisation != @organisation && !current_user.support?
render_not_found

21
app/controllers/sales_logs_controller.rb

@ -1,6 +1,7 @@
class SalesLogsController < LogsController
before_action :session_filters, if: :current_user, only: %i[index email_csv download_csv]
before_action :set_session_filters, if: :current_user, only: %i[index email_csv download_csv]
before_action :authenticate_scope!, only: %i[download_csv email_csv]
def create
super { SalesLog.new(log_params) }
@ -36,6 +37,20 @@ class SalesLogsController < LogsController
end
end
def download_csv
unpaginated_filtered_logs = filtered_logs(current_user.sales_logs, search_term, @session_filters)
render "download_csv", locals: { search_term:, count: unpaginated_filtered_logs.size, post_path: email_csv_sales_logs_path, codes_only: codes_only_export? }
end
def email_csv
all_orgs = params["organisation_select"] == "all"
EmailCsvJob.perform_later(current_user, search_term, @session_filters, all_orgs, nil, codes_only_export?, "sales")
redirect_to csv_confirmation_sales_logs_path
end
def csv_confirmation; end
def post_create_redirect_url(log)
sales_log_url(log)
end
@ -43,4 +58,10 @@ class SalesLogsController < LogsController
def permitted_log_params
params.require(:sales_log).permit(SalesLog.editable_fields)
end
private
def authenticate_scope!
head :unauthorized and return if codes_only_export? && !current_user.support?
end
end

5
app/helpers/form_page_helper.rb

@ -0,0 +1,5 @@
module FormPageHelper
def action_href(log, page_id, referrer = "check_answers")
send("#{log.model_name.param_key}_#{page_id}_path", log, referrer:)
end
end

4
app/helpers/interruption_screen_helper.rb

@ -29,6 +29,10 @@ module InterruptionScreenHelper
I18n.t(title_text["translation"], **translation_params).to_s
end
def soft_validation_affected_questions(question, log)
question.page.interruption_screen_question_ids.map { |question_id| log.form.get_question(question_id, log) }.compact
end
private
def get_value_from_argument(log, argument)

39
app/helpers/logs_helper.rb

@ -1,10 +1,8 @@
module LogsHelper
def log_type_for_controller(controller)
case controller.class.to_s
when "LettingsLogsController"
"lettings"
when "SalesLogsController"
"sales"
case controller.class.name
when "LettingsLogsController" then "lettings"
when "SalesLogsController" then "sales"
else
raise "Log type not found for #{controller.class}"
end
@ -12,10 +10,8 @@ module LogsHelper
def bulk_upload_path_for_controller(controller, id:)
case log_type_for_controller(controller)
when "lettings"
bulk_upload_lettings_log_path(id:)
when "sales"
bulk_upload_sales_log_path(id:)
when "lettings" then bulk_upload_lettings_log_path(id:)
when "sales" then bulk_upload_sales_log_path(id:)
end
end
@ -26,16 +22,29 @@ module LogsHelper
def search_label_for_controller(controller)
case log_type_for_controller(controller)
when "lettings"
"Search by log ID, tenant code, property reference or postcode"
when "sales"
"Search by log ID, purchaser code or postcode"
when "lettings" then "Search by log ID, tenant code, property reference or postcode"
when "sales" then "Search by log ID, purchaser code or postcode"
end
end
def csv_download_url_for_controller(controller_type:, search:, codes_only:)
case log_type_for_controller(controller_type)
def csv_download_url_for_controller(controller:, search:, codes_only:)
case log_type_for_controller(controller)
when "lettings" then csv_download_lettings_logs_path(search:, codes_only:)
when "sales" then csv_download_sales_logs_path(search:, codes_only:)
end
end
def logs_path_for_controller(controller)
case log_type_for_controller(controller)
when "lettings" then lettings_logs_path
when "sales" then sales_logs_path
end
end
def csv_download_url_by_log_type(log_type, organisation, search:, codes_only:)
case log_type
when :lettings then lettings_logs_csv_download_organisation_path(organisation, search:, codes_only:)
when :sales then sales_logs_csv_download_organisation_path(organisation, search:, codes_only:)
end
end

19
app/jobs/email_csv_job.rb

@ -5,14 +5,23 @@ class EmailCsvJob < ApplicationJob
EXPIRATION_TIME = 3.hours.to_i
def perform(user, search_term = nil, filters = {}, all_orgs = false, organisation = nil, codes_only_export = false) # rubocop:disable Style/OptionalBooleanParameter - sidekiq can't serialise named params
unfiltered_logs = organisation.present? && user.support? ? LettingsLog.visible.where(owning_organisation_id: organisation.id) : user.lettings_logs.visible
filtered_logs = FilterService.filter_logs(unfiltered_logs, search_term, filters, all_orgs, user)
def perform(user, search_term = nil, filters = {}, all_orgs = false, organisation = nil, codes_only_export = false, log_type = "lettings") # rubocop:disable Style/OptionalBooleanParameter - sidekiq can't serialise named params
case log_type
when "lettings"
unfiltered_logs = organisation.present? && user.support? ? LettingsLog.visible.where(owning_organisation_id: organisation.id) : user.lettings_logs.visible
filtered_logs = FilterService.filter_logs(unfiltered_logs, search_term, filters, all_orgs, user)
csv_string = filtered_logs.to_csv(user, codes_only_export:)
when "sales"
unfiltered_logs = organisation.present? && user.support? ? SalesLog.visible.where(owning_organisation_id: organisation.id) : user.sales_logs.visible
filtered_logs = FilterService.filter_logs(unfiltered_logs, search_term, filters, all_orgs, user)
export_type = codes_only_export ? "codes" : "labels"
csv_string = Csv::SalesLogCsvService.new(export_type:).prepare_csv(filtered_logs)
end
filename = organisation.present? ? "logs-#{organisation.name}-#{Time.zone.now}.csv" : "logs-#{Time.zone.now}.csv"
filename = "#{[log_type, 'logs', organisation&.name, Time.zone.now].compact.join('-')}.csv"
storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["CSV_DOWNLOAD_PAAS_INSTANCE"])
storage_service.write_file(filename, BYTE_ORDER_MARK + filtered_logs.to_csv(user, codes_only_export:))
storage_service.write_file(filename, BYTE_ORDER_MARK + csv_string)
url = storage_service.get_presigned_url(filename, EXPIRATION_TIME)

55
app/models/derived_variables/lettings_log_variables.rb

@ -1,4 +1,6 @@
module DerivedVariables::LettingsLogVariables
include DerivedVariables::SharedLogic
# renttype and unitletas values are different for intermediate rent (3 for renttype and 4 for unitletas)
RENT_TYPE_MAPPING = {
0 => 1, # "Social Rent" => "Social Rent"
@ -35,10 +37,12 @@ module DerivedVariables::LettingsLogVariables
end
def set_derived_fields!
clear_inapplicable_derived_values!
set_encoded_derived_values!(DEPENDENCIES)
if rsnvac.present?
self.newprop = has_first_let_vacancy_reason? ? 1 : 2
end
self.incref = 1 if net_income_refused?
self.renttype = RENT_TYPE_MAPPING[rent_type]
self.lettype = get_lettype
self.totchild = get_totchild
@ -69,15 +73,9 @@ module DerivedVariables::LettingsLogVariables
self.nocharge = household_charge&.zero? ? 1 : 0
if is_renewal?
self.underoccupation_benefitcap = 2 if collection_start_year == 2021
self.referral = 1
self.waityear = 2
self.offered = 0
self.voiddate = startdate
self.first_time_property_let_as_social_housing = 0
self.rsnvac = 14
self.unitletas = form.start_date.year >= 2023 ? UNITLETAS_MAPPING_23_24[rent_type] : UNITLETAS_MAPPING[rent_type]
if is_general_needs?
# fixed term
self.prevten = 32 if managing_organisation&.provider_type == "PRP"
self.prevten = 30 if managing_organisation&.provider_type == "LA"
end
@ -109,6 +107,49 @@ module DerivedVariables::LettingsLogVariables
private
DEPENDENCIES = [
{
conditions: {
renewal: 1,
},
derived_values: {
referral: 1,
waityear: 2,
offered: 0,
rsnvac: 14,
first_time_property_let_as_social_housing: 0,
},
},
{
conditions: {
net_income_known: 2,
},
derived_values: {
incref: 1,
},
},
].freeze
def clear_inapplicable_derived_values!
reset_invalidated_derived_values!(DEPENDENCIES)
if (startdate_changed? || renewal_changed?) && (renewal_was == 1 && startdate_was&.between?(Time.zone.local(2021, 4, 1), Time.zone.local(2022, 3, 31)))
self.underoccupation_benefitcap = nil
end
if renewal_changed? && renewal_was == 1
self.voiddate = nil
self.unitletas = nil
end
if %w[PRP LA].include?(managing_organisation&.provider_type) &&
(needstype_changed? || renewal_changed?) &&
needstype_was == 1 && renewal_was == 1
self.prevten = nil
end
if needstype_changed? && needstype_was == 2
self.wchair = nil
self.location_id = nil
end
end
def get_totelder
ages = [age1, age2, age3, age4, age5, age6, age7, age8]
ages.count { |x| !x.nil? && x >= 60 }

33
app/models/derived_variables/sales_log_variables.rb

@ -1,6 +1,8 @@
module DerivedVariables::SalesLogVariables
include DerivedVariables::SharedLogic
def set_derived_fields!
reset_invalidated_derived_values!
reset_invalidated_derived_values!(DEPENDENCIES)
self.ethnic = 17 if ethnic_refused?
self.mscharge = nil if no_monthly_leasehold_charges?
@ -15,7 +17,8 @@ module DerivedVariables::SalesLogVariables
self.hoyear = hodate.year
end
self.deposit = value if outright_sale? && mortgage_not_used?
self.pcode1, self.pcode2 = postcode_full.split(" ") if postcode_full.present?
self.pcode1, self.pcode2 = postcode_full.split if postcode_full.present?
self.ppostc1, self.ppostc2 = ppostcode_full.split if ppostcode_full.present?
self.totchild = total_child
self.totadult = total_adult + total_elder
self.hhmemb = number_of_household_members
@ -30,7 +33,7 @@ module DerivedVariables::SalesLogVariables
self.uprn_known = 0
end
set_encoded_derived_values!
set_encoded_derived_values!(DEPENDENCIES)
end
private
@ -73,30 +76,6 @@ private
},
].freeze
def reset_invalidated_derived_values!
DEPENDENCIES.each do |dependency|
any_conditions_changed = dependency[:conditions].any? { |attribute, _value| send("#{attribute}_changed?") }
next unless any_conditions_changed
previously_in_derived_state = dependency[:conditions].all? { |attribute, value| send("#{attribute}_was") == value }
next unless previously_in_derived_state
dependency[:derived_values].each do |derived_attribute, _derived_value|
Rails.logger.debug("Cleared derived #{derived_attribute} value")
send("#{derived_attribute}=", nil)
end
end
end
def set_encoded_derived_values!
DEPENDENCIES.each do |dependency|
derivation_applies = dependency[:conditions].all? { |attribute, value| send(attribute) == value }
if derivation_applies
dependency[:derived_values].each { |attribute, value| send("#{attribute}=", value) }
end
end
end
def number_of_household_members
return unless hholdcount.present? && jointpur.present?

27
app/models/derived_variables/shared_logic.rb

@ -0,0 +1,27 @@
module DerivedVariables::SharedLogic
private
def reset_invalidated_derived_values!(dependencies)
dependencies.each do |dependency|
any_conditions_changed = dependency[:conditions].any? { |attribute, _value| send("#{attribute}_changed?") }
next unless any_conditions_changed
previously_in_derived_state = dependency[:conditions].all? { |attribute, value| send("#{attribute}_was") == value }
next unless previously_in_derived_state
dependency[:derived_values].each do |derived_attribute, _derived_value|
Rails.logger.debug("Cleared derived #{derived_attribute} value")
send("#{derived_attribute}=", nil)
end
end
end
def set_encoded_derived_values!(dependencies)
dependencies.each do |dependency|
derivation_applies = dependency[:conditions].all? { |attribute, value| send(attribute) == value }
if derivation_applies
dependency[:derived_values].each { |attribute, value| send("#{attribute}=", value) }
end
end
end
end

4
app/models/form/lettings/pages/care_home_charges_value_check.rb

@ -12,4 +12,8 @@ class Form::Lettings::Pages::CareHomeChargesValueCheck < ::Form::Page
def questions
@questions ||= [Form::Lettings::Questions::CareHomeChargesValueCheck.new(nil, nil, self)]
end
def interruption_screen_question_ids
%w[chcharge is_carehome]
end
end

4
app/models/form/lettings/pages/females_in_soft_age_range_in_pregnant_household_lead_age_value_check.rb

@ -16,4 +16,8 @@ class Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdLeadAgeValu
def questions
@questions ||= [Form::Lettings::Questions::PregnancyValueCheck.new(nil, nil, self)]
end
def interruption_screen_question_ids
%w[preg_occ sex1 sex2 sex3 sex4 sex5 sex6 sex7 sex8 age1 age2 age3 age4 age5 age6 age7 age8]
end
end

4
app/models/form/lettings/pages/females_in_soft_age_range_in_pregnant_household_lead_hhmemb_value_check.rb

@ -16,4 +16,8 @@ class Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdLeadHhmembV
def questions
@questions ||= [Form::Lettings::Questions::PregnancyValueCheck.new(nil, nil, self)]
end
def interruption_screen_question_ids
%w[preg_occ sex1 sex2 sex3 sex4 sex5 sex6 sex7 sex8 age1 age2 age3 age4 age5 age6 age7 age8]
end
end

4
app/models/form/lettings/pages/females_in_soft_age_range_in_pregnant_household_lead_value_check.rb

@ -16,4 +16,8 @@ class Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdLeadValueCh
def questions
@questions ||= [Form::Lettings::Questions::PregnancyValueCheck.new(nil, nil, self)]
end
def interruption_screen_question_ids
%w[preg_occ sex1 sex2 sex3 sex4 sex5 sex6 sex7 sex8 age1 age2 age3 age4 age5 age6 age7 age8]
end
end

4
app/models/form/lettings/pages/females_in_soft_age_range_in_pregnant_household_person_age_value_check.rb

@ -33,4 +33,8 @@ class Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdPersonAgeVa
def questions
@questions ||= [Form::Lettings::Questions::PregnancyValueCheck.new(nil, nil, self)]
end
def interruption_screen_question_ids
%w[preg_occ sex1 sex2 sex3 sex4 sex5 sex6 sex7 sex8 age1 age2 age3 age4 age5 age6 age7 age8]
end
end

4
app/models/form/lettings/pages/females_in_soft_age_range_in_pregnant_household_person_value_check.rb

@ -33,4 +33,8 @@ class Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdPersonValue
def questions
@questions ||= [Form::Lettings::Questions::PregnancyValueCheck.new(nil, nil, self)]
end
def interruption_screen_question_ids
%w[preg_occ sex1 sex2 sex3 sex4 sex5 sex6 sex7 sex8 age1 age2 age3 age4 age5 age6 age7 age8]
end
end

4
app/models/form/lettings/pages/females_in_soft_age_range_in_pregnant_household_value_check.rb

@ -16,4 +16,8 @@ class Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdValueCheck
def questions
@questions ||= [Form::Lettings::Questions::PregnancyValueCheck.new(nil, nil, self)]
end
def interruption_screen_question_ids
%w[preg_occ sex1 sex2 sex3 sex4 sex5 sex6 sex7 sex8 age1 age2 age3 age4 age5 age6 age7 age8]
end
end

6
app/models/form/lettings/pages/lead_tenant_over_retirement_value_check.rb

@ -27,6 +27,10 @@ class Form::Lettings::Pages::LeadTenantOverRetirementValueCheck < ::Form::Page
end
def questions
@questions ||= [Form::Lettings::Questions::RetirementValueCheck.new(nil, nil, self)]
@questions ||= [Form::Lettings::Questions::RetirementValueCheck.new(nil, nil, self, person_index: 1)]
end
def interruption_screen_question_ids
%w[ecstat1 sex1 age1]
end
end

22
app/models/form/lettings/pages/lead_tenant_under_retirement_value_check.rb

@ -7,26 +7,20 @@ class Form::Lettings::Pages::LeadTenantUnderRetirementValueCheck < ::Form::Page
"translation" => "soft_validations.retirement.min.title",
"arguments" => [
{
"key" => "retirement_age_for_person_1",
"label" => false,
"i18n_template" => "age",
},
],
}
@informative_text = {
"translation" => "soft_validations.retirement.min.hint_text",
"arguments" => [
{ "key" => "plural_gender_for_person_1", "label" => false, "i18n_template" => "gender" },
{
"key" => "retirement_age_for_person_1",
"label" => false,
"key" => "age1",
"label" => true,
"i18n_template" => "age",
},
],
}
@informative_text = {}
end
def questions
@questions ||= [Form::Lettings::Questions::NoRetirementValueCheck.new(nil, nil, self)]
@questions ||= [Form::Lettings::Questions::NoRetirementValueCheck.new(nil, nil, self, person_index: 1)]
end
def interruption_screen_question_ids
%w[ecstat1 age1]
end
end

4
app/models/form/lettings/pages/max_rent_value_check.rb

@ -28,4 +28,8 @@ class Form::Lettings::Pages::MaxRentValueCheck < ::Form::Page
def questions
@questions ||= [Form::Lettings::Questions::RentValueCheck.new(nil, nil, self, check_answers_card_number: @check_answers_card_number)]
end
def interruption_screen_question_ids
%w[brent startdate la beds rent_type needstype]
end
end

4
app/models/form/lettings/pages/min_rent_value_check.rb

@ -24,4 +24,8 @@ class Form::Lettings::Pages::MinRentValueCheck < ::Form::Page
def questions
@questions ||= [Form::Lettings::Questions::RentValueCheck.new(nil, nil, self, check_answers_card_number: @check_answers_card_number)]
end
def interruption_screen_question_ids
%w[brent startdate la beds rent_type needstype]
end
end

26
app/models/form/lettings/pages/net_income_value_check.rb

@ -3,14 +3,13 @@ class Form::Lettings::Pages::NetIncomeValueCheck < ::Form::Page
super
@id = "net_income_value_check"
@depends_on = [{ "net_income_soft_validation_triggered?" => true }]
@title_text = { "translation" => "soft_validations.net_income.title_text" }
@informative_text = {
"translation" => "soft_validations.net_income.hint_text",
@title_text = {
"translation" => "soft_validations.net_income.title_text",
"arguments" => [
{
"key" => "field_formatted_as_currency",
"arguments_for_key" => "ecstat1",
"i18n_template" => "ecstat1",
"key" => "incfreq",
"label" => true,
"i18n_template" => "incfreq",
},
{
"key" => "field_formatted_as_currency",
@ -19,9 +18,24 @@ class Form::Lettings::Pages::NetIncomeValueCheck < ::Form::Page
},
],
}
@informative_text = {
"translation" => "soft_validations.net_income.hint_text",
"arguments" => [
{
"key" => "net_income_higher_or_lower_text",
"label" => false,
"i18n_template" => "net_income_higher_or_lower_text",
},
],
}
end
def questions
@questions ||= [Form::Lettings::Questions::NetIncomeValueCheck.new(nil, nil, self)]
end
def interruption_screen_question_ids
%w[incfreq earnings ecstat1]
end
end

4
app/models/form/lettings/pages/no_females_pregnant_household_lead_age_value_check.rb

@ -16,4 +16,8 @@ class Form::Lettings::Pages::NoFemalesPregnantHouseholdLeadAgeValueCheck < ::For
def questions
@questions ||= [Form::Lettings::Questions::PregnancyValueCheck.new(nil, nil, self)]
end
def interruption_screen_question_ids
%w[preg_occ sex1 sex2 sex3 sex4 sex5 sex6 sex7 sex8]
end
end

4
app/models/form/lettings/pages/no_females_pregnant_household_lead_hhmemb_value_check.rb

@ -16,4 +16,8 @@ class Form::Lettings::Pages::NoFemalesPregnantHouseholdLeadHhmembValueCheck < ::
def questions
@questions ||= [Form::Lettings::Questions::PregnancyValueCheck.new(nil, nil, self)]
end
def interruption_screen_question_ids
%w[preg_occ sex1 sex2 sex3 sex4 sex5 sex6 sex7 sex8]
end
end

4
app/models/form/lettings/pages/no_females_pregnant_household_lead_value_check.rb

@ -16,4 +16,8 @@ class Form::Lettings::Pages::NoFemalesPregnantHouseholdLeadValueCheck < ::Form::
def questions
@questions ||= [Form::Lettings::Questions::PregnancyValueCheck.new(nil, nil, self)]
end
def interruption_screen_question_ids
%w[preg_occ sex1 sex2 sex3 sex4 sex5 sex6 sex7 sex8]
end
end

4
app/models/form/lettings/pages/no_females_pregnant_household_person_age_value_check.rb

@ -16,4 +16,8 @@ class Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonAgeValueCheck < ::F
def questions
@questions ||= [Form::Lettings::Questions::PregnancyValueCheck.new(nil, nil, self)]
end
def interruption_screen_question_ids
%w[preg_occ sex1 sex2 sex3 sex4 sex5 sex6 sex7 sex8]
end
end

4
app/models/form/lettings/pages/no_females_pregnant_household_person_value_check.rb

@ -16,4 +16,8 @@ class Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonValueCheck < ::Form
def questions
@questions ||= [Form::Lettings::Questions::PregnancyValueCheck.new(nil, nil, self)]
end
def interruption_screen_question_ids
%w[preg_occ sex1 sex2 sex3 sex4 sex5 sex6 sex7 sex8]
end
end

4
app/models/form/lettings/pages/no_females_pregnant_household_value_check.rb

@ -16,4 +16,8 @@ class Form::Lettings::Pages::NoFemalesPregnantHouseholdValueCheck < ::Form::Page
def questions
@questions ||= [Form::Lettings::Questions::PregnancyValueCheck.new(nil, nil, self)]
end
def interruption_screen_question_ids
%w[preg_occ sex1 sex2 sex3 sex4 sex5 sex6 sex7 sex8]
end
end

7
app/models/form/lettings/pages/person_over_retirement_value_check.rb

@ -28,9 +28,14 @@ class Form::Lettings::Pages::PersonOverRetirementValueCheck < ::Form::Page
},
],
}
@person_index = person_index
end
def questions
@questions ||= [Form::Lettings::Questions::RetirementValueCheck.new(nil, nil, self)]
@questions ||= [Form::Lettings::Questions::RetirementValueCheck.new(nil, nil, self, person_index: @person_index)]
end
def interruption_screen_question_ids
["ecstat#{@person_index}", "sex#{@person_index}", "age#{@person_index}"]
end
end

27
app/models/form/lettings/pages/person_under_retirement_value_check.rb

@ -7,30 +7,21 @@ class Form::Lettings::Pages::PersonUnderRetirementValueCheck < ::Form::Page
"translation" => "soft_validations.retirement.min.title",
"arguments" => [
{
"key" => "retirement_age_for_person_#{person_index}",
"label" => false,
"i18n_template" => "age",
},
],
}
@informative_text = {
"translation" => "soft_validations.retirement.min.hint_text",
"arguments" => [
{
"key" => "plural_gender_for_person_#{person_index}",
"label" => false,
"i18n_template" => "gender",
},
{
"key" => "retirement_age_for_person_#{person_index}",
"label" => false,
"key" => "age#{person_index}",
"label" => true,
"i18n_template" => "age",
},
],
}
@informative_text = {}
@person_index = person_index
end
def questions
@questions ||= [Form::Lettings::Questions::NoRetirementValueCheck.new(nil, nil, self)]
@questions ||= [Form::Lettings::Questions::NoRetirementValueCheck.new(nil, nil, self, person_index: @person_index)]
end
def interruption_screen_question_ids
["ecstat#{@person_index}", "age#{@person_index}"]
end
end

9
app/models/form/lettings/pages/property_major_repairs_value_check.rb

@ -4,10 +4,17 @@ class Form::Lettings::Pages::PropertyMajorRepairsValueCheck < ::Form::Page
@id = "property_major_repairs_value_check"
@depends_on = [{ "major_repairs_date_in_soft_range?" => true }]
@title_text = { "translation" => "soft_validations.major_repairs_date.title_text" }
@informative_text = {}
@informative_text = {
"translation" => "soft_validations.major_repairs_date.hint_text",
"arguments" => [],
}
end
def questions
@questions ||= [Form::Lettings::Questions::MajorRepairsDateValueCheck.new(nil, nil, self)]
end
def interruption_screen_question_ids
%w[mrcdate startdate]
end
end

9
app/models/form/lettings/pages/void_date_value_check.rb

@ -4,10 +4,17 @@ class Form::Lettings::Pages::VoidDateValueCheck < ::Form::Page
@id = "void_date_value_check"
@depends_on = [{ "voiddate_in_soft_range?" => true }]
@title_text = { "translation" => "soft_validations.void_date.title_text" }
@informative_text = {}
@informative_text = {
"translation" => "soft_validations.void_date.hint_text",
"arguments" => [],
}
end
def questions
@questions ||= [Form::Lettings::Questions::VoidDateValueCheck.new(nil, nil, self)]
end
def interruption_screen_question_ids
%w[voiddate startdate]
end
end

6
app/models/form/lettings/questions/no_retirement_value_check.rb

@ -1,11 +1,11 @@
class Form::Lettings::Questions::NoRetirementValueCheck < ::Form::Question
def initialize(id, hsh, page)
super
def initialize(id, hsh, page, person_index:)
super(id, hsh, page)
@id = "retirement_value_check"
@check_answer_label = "Retirement confirmation"
@header = "Are you sure this person is retired?"
@type = "interruption_screen"
@check_answers_card_number = 8
@check_answers_card_number = person_index
@answer_options = ANSWER_OPTIONS
@hidden_in_check_answers = {
"depends_on" => [

6
app/models/form/lettings/questions/retirement_value_check.rb

@ -1,11 +1,11 @@
class Form::Lettings::Questions::RetirementValueCheck < ::Form::Question
def initialize(id, hsh, page)
super
def initialize(id, hsh, page, person_index:)
super(id, hsh, page)
@id = "retirement_value_check"
@check_answer_label = "Retirement confirmation"
@header = "Are you sure this person isn’t retired?"
@type = "interruption_screen"
@check_answers_card_number = 8
@check_answers_card_number = person_index
@answer_options = ANSWER_OPTIONS
@hidden_in_check_answers = {
"depends_on" => [

3
app/models/form/page.rb

@ -1,7 +1,7 @@
class Form::Page
attr_accessor :id, :header, :header_partial, :description, :questions, :depends_on, :title_text,
:informative_text, :subsection, :hide_subsection_label, :next_unresolved_page_id,
:skip_text
:skip_text, :interruption_screen_question_ids
def initialize(id, hsh, subsection)
@id = id
@ -17,6 +17,7 @@ class Form::Page
@hide_subsection_label = hsh["hide_subsection_label"]
@next_unresolved_page_id = hsh["next_unresolved_page_id"]
@skip_text = hsh["skip_text"]
@interruption_screen_question_ids = hsh["interruption_screen_question_ids"] || []
end
end

4
app/models/form/question.rb

@ -120,10 +120,6 @@ class Form::Question
end
end
def action_href(log, page_id)
"/#{log.model_name.param_key.dasherize}s/#{log.id}/#{page_id.to_s.dasherize}?referrer=check_answers"
end
def unanswered?(log)
return answer_options.keys.none? { |key| value_is_yes?(log[key]) } if type == "checkbox"

8
app/models/form/sales/pages/about_price_value_check.rb

@ -25,8 +25,8 @@ class Form::Sales::Pages::AboutPriceValueCheck < ::Form::Page
"i18n_template" => "soft_min_or_soft_max",
},
{
"key" => "purchase_price_min_or_max_text",
"i18n_template" => "min_or_max",
"key" => "purchase_price_higher_or_lower_text",
"i18n_template" => "higher_or_lower",
},
],
}
@ -37,4 +37,8 @@ class Form::Sales::Pages::AboutPriceValueCheck < ::Form::Page
Form::Sales::Questions::AboutPriceValueCheck.new(nil, nil, self),
]
end
def interruption_screen_question_ids
%w[value beds la]
end
end

11
app/models/form/sales/pages/buyer1_income_value_check.rb

@ -7,7 +7,7 @@ class Form::Sales::Pages::Buyer1IncomeValueCheck < ::Form::Page
},
]
@title_text = {
"translation" => "soft_validations.income.under_soft_min_for_economic_status",
"translation" => "soft_validations.income.under_soft_min_for_economic_status.title_text",
"arguments" => [
{
"key" => "field_formatted_as_currency",
@ -21,7 +21,10 @@ class Form::Sales::Pages::Buyer1IncomeValueCheck < ::Form::Page
},
],
}
@informative_text = {}
@informative_text = {
"translation" => "soft_validations.income.under_soft_min_for_economic_status.hint_text",
"arguments" => [],
}
end
def questions
@ -29,4 +32,8 @@ class Form::Sales::Pages::Buyer1IncomeValueCheck < ::Form::Page
Form::Sales::Questions::Buyer1IncomeValueCheck.new(nil, nil, self),
]
end
def interruption_screen_question_ids
%w[ecstat1 income1]
end
end

11
app/models/form/sales/pages/buyer2_income_value_check.rb

@ -10,7 +10,7 @@ class Form::Sales::Pages::Buyer2IncomeValueCheck < ::Form::Page
},
]
@title_text = {
"translation" => "soft_validations.income.under_soft_min_for_economic_status",
"translation" => "soft_validations.income.under_soft_min_for_economic_status.title_text",
"arguments" => [
{
"key" => "field_formatted_as_currency",
@ -24,7 +24,10 @@ class Form::Sales::Pages::Buyer2IncomeValueCheck < ::Form::Page
},
],
}
@informative_text = {}
@informative_text = {
"translation" => "soft_validations.income.under_soft_min_for_economic_status.hint_text",
"arguments" => [],
}
end
def questions
@ -32,4 +35,8 @@ class Form::Sales::Pages::Buyer2IncomeValueCheck < ::Form::Page
Form::Sales::Questions::Buyer2IncomeValueCheck.new(nil, nil, self),
]
end
def interruption_screen_question_ids
%w[ecstat2 income2]
end
end

9
app/models/form/sales/pages/buyer_live_in_value_check.rb

@ -10,7 +10,10 @@ class Form::Sales::Pages::BuyerLiveInValueCheck < Form::Sales::Pages::Person
"translation" => "soft_validations.buyer#{person_index}_livein_wrong_for_ownership_type.title_text",
"arguments" => [{ "key" => "ownership_scheme", "label" => false, "i18n_template" => "ownership_scheme" }],
}
@informative_text = {}
@informative_text = {
"translation" => "soft_validations.buyer#{person_index}_livein_wrong_for_ownership_type.hint_text",
"arguments" => [{ "key" => "ownership_scheme", "label" => false, "i18n_template" => "ownership_scheme" }],
}
end
def questions
@ -18,4 +21,8 @@ class Form::Sales::Pages::BuyerLiveInValueCheck < Form::Sales::Pages::Person
Form::Sales::Questions::BuyerLiveInValueCheck.new(nil, nil, self, person_index: @person_index),
]
end
def interruption_screen_question_ids
["ownershipsch", "buy#{@person_index}livein"]
end
end

4
app/models/form/sales/pages/deposit_and_mortgage_value_check.rb

@ -14,4 +14,8 @@ class Form::Sales::Pages::DepositAndMortgageValueCheck < ::Form::Page
Form::Sales::Questions::DepositAndMortgageValueCheck.new(nil, nil, self),
]
end
def interruption_screen_question_ids
%w[mortgage deposit value discount]
end
end

24
app/models/form/sales/pages/deposit_value_check.rb

@ -6,7 +6,25 @@ class Form::Sales::Pages::DepositValueCheck < ::Form::Page
"deposit_over_soft_max?" => true,
},
]
@informative_text = {}
@informative_text = {
"translation" => "soft_validations.deposit.hint_text",
"arguments" => [],
}
@title_text = {
"translation" => "soft_validations.deposit.title_text",
"arguments" => [
{
"key" => "field_formatted_as_currency",
"arguments_for_key" => "deposit",
"i18n_template" => "deposit",
},
{
"key" => "field_formatted_as_currency",
"arguments_for_key" => "savings",
"i18n_template" => "savings",
},
],
}
end
def questions
@ -14,4 +32,8 @@ class Form::Sales::Pages::DepositValueCheck < ::Form::Page
Form::Sales::Questions::DepositValueCheck.new(nil, nil, self),
]
end
def interruption_screen_question_ids
%w[savings deposit]
end
end

4
app/models/form/sales/pages/discounted_sale_value_check.rb

@ -35,4 +35,8 @@ class Form::Sales::Pages::DiscountedSaleValueCheck < ::Form::Page
Form::Sales::Questions::DiscountedSaleValueCheck.new(nil, nil, self),
]
end
def interruption_screen_question_ids
%w[value deposit ownershipsch mortgage mortgageused discount grant type]
end
end

18
app/models/form/sales/pages/extra_borrowing_value_check.rb

@ -7,9 +7,19 @@ class Form::Sales::Pages::ExtraBorrowingValueCheck < Form::Page
},
]
@title_text = {
"translation" => "soft_validations.extra_borrowing.title",
"translation" => "soft_validations.extra_borrowing.title_text",
"arguments" => [
{
"key" => "field_formatted_as_currency",
"arguments_for_key" => "mortgage_and_deposit_total",
"i18n_template" => "mortgage_and_deposit_total",
},
],
}
@informative_text = {
"translation" => "soft_validations.extra_borrowing.hint_text",
"arguments" => [],
}
@informative_text = {}
end
def questions
@ -17,4 +27,8 @@ class Form::Sales::Pages::ExtraBorrowingValueCheck < Form::Page
Form::Sales::Questions::ExtraBorrowingValueCheck.new(nil, nil, self),
]
end
def interruption_screen_question_ids
%w[extrabor mortgage deposit value discount]
end
end

19
app/models/form/sales/pages/grant_value_check.rb

@ -7,7 +7,20 @@ class Form::Sales::Pages::GrantValueCheck < ::Form::Page
"grant_outside_common_range?" => true,
},
]
@informative_text = {}
@title_text = {
"translation" => "soft_validations.grant.title_text",
"arguments" => [
{
"key" => "field_formatted_as_currency",
"arguments_for_key" => "grant",
"i18n_template" => "grant",
},
],
}
@informative_text = {
"translation" => "soft_validations.grant.hint_text",
"arguments" => [],
}
end
def questions
@ -15,4 +28,8 @@ class Form::Sales::Pages::GrantValueCheck < ::Form::Page
Form::Sales::Questions::GrantValueCheck.new(nil, nil, self),
]
end
def interruption_screen_question_ids
%w[grant]
end
end

4
app/models/form/sales/pages/handover_date_check.rb

@ -16,4 +16,8 @@ class Form::Sales::Pages::HandoverDateCheck < ::Form::Page
Form::Sales::Questions::HandoverDateCheck.new(nil, nil, self),
]
end
def interruption_screen_question_ids
%w[hodate saledate]
end
end

5
app/models/form/sales/pages/household_wheelchair_check.rb

@ -7,6 +7,7 @@ class Form::Sales::Pages::HouseholdWheelchairCheck < ::Form::Page
},
]
@informative_text = {}
@title_text = { "translation" => "soft_validations.wheelchair.title_text" }
end
def questions
@ -14,4 +15,8 @@ class Form::Sales::Pages::HouseholdWheelchairCheck < ::Form::Page
Form::Sales::Questions::HouseholdWheelchairCheck.new(nil, nil, self),
]
end
def interruption_screen_question_ids
%w[disabled wheel]
end
end

15
app/models/form/sales/pages/monthly_charges_value_check.rb

@ -8,9 +8,18 @@ class Form::Sales::Pages::MonthlyChargesValueCheck < ::Form::Page
]
@title_text = {
"translation" => "soft_validations.monthly_charges_over_soft_max.title_text",
"arguments" => [
{
"key" => "field_formatted_as_currency",
"arguments_for_key" => "mscharge",
"i18n_template" => "mscharge",
},
],
}
@informative_text = {
"translation" => "soft_validations.monthly_charges_over_soft_max.hint_text",
"arguments" => [],
}
@informative_text = {}
end
def questions
@ -18,4 +27,8 @@ class Form::Sales::Pages::MonthlyChargesValueCheck < ::Form::Page
Form::Sales::Questions::MonthlyChargesValueCheck.new(nil, nil, self),
]
end
def interruption_screen_question_ids
%w[type mscharge proptype]
end
end

18
app/models/form/sales/pages/mortgage_value_check.rb

@ -4,6 +4,20 @@ class Form::Sales::Pages::MortgageValueCheck < ::Form::Page
@depends_on = depends_on
@informative_text = {}
@person_index = person_index
@title_text = {
"translation" => "soft_validations.mortgage.title_text",
"arguments" => [
{
"key" => "field_formatted_as_currency",
"arguments_for_key" => "mortgage",
"i18n_template" => "mortgage",
},
],
}
@informative_text = {
"translation" => "soft_validations.mortgage.hint_text",
"arguments" => [],
}
end
def questions
@ -28,4 +42,8 @@ class Form::Sales::Pages::MortgageValueCheck < ::Form::Page
]
end
end
def interruption_screen_question_ids
%w[mortgage inc1mort inc2mort jointpur income1 income2 inc1mort inc2mort]
end
end

11
app/models/form/sales/pages/old_persons_shared_ownership_value_check.rb

@ -7,10 +7,13 @@ class Form::Sales::Pages::OldPersonsSharedOwnershipValueCheck < ::Form::Page
},
]
@title_text = {
"translation" => "soft_validations.old_persons_shared_ownership",
"translation" => "soft_validations.old_persons_shared_ownership.title_text",
"arguments" => [],
}
@informative_text = {
"translation" => "soft_validations.old_persons_shared_ownership.hint_text",
"arguments" => [],
}
@informative_text = {}
end
def questions
@ -18,4 +21,8 @@ class Form::Sales::Pages::OldPersonsSharedOwnershipValueCheck < ::Form::Page
Form::Sales::Questions::OldPersonsSharedOwnershipValueCheck.new(nil, nil, self),
]
end
def interruption_screen_question_ids
%w[type jointpur age1 age2]
end
end

9
app/models/form/sales/pages/percentage_discount_value_check.rb

@ -5,11 +5,18 @@ class Form::Sales::Pages::PercentageDiscountValueCheck < ::Form::Page
"translation" => "soft_validations.percentage_discount_value.title_text",
"arguments" => [{ "key" => "discount", "label" => true, "i18n_template" => "discount" }],
}
@informative_text = {}
@informative_text = {
"translation" => "soft_validations.percentage_discount_value.hint_text",
"arguments" => [],
}
@depends_on = [{ "percentage_discount_invalid?" => true }]
end
def questions
@questions ||= [Form::Sales::Questions::PercentageDiscountValueCheck.new(nil, nil, self)]
end
def interruption_screen_question_ids
%w[discount proptype]
end
end

4
app/models/form/sales/pages/person_student_not_child_value_check.rb

@ -18,4 +18,8 @@ class Form::Sales::Pages::PersonStudentNotChildValueCheck < Form::Sales::Pages::
Form::Sales::Questions::PersonStudentNotChildValueCheck.new(nil, nil, self, person_index: @person_index),
]
end
def interruption_screen_question_ids
["relat#{@person_index}", "ecstat#{@person_index}", "age#{@person_index}"]
end
end

24
app/models/form/sales/pages/retirement_value_check.rb

@ -11,27 +11,13 @@ class Form::Sales::Pages::RetirementValueCheck < Form::Sales::Pages::Person
"translation" => "soft_validations.retirement.min.title",
"arguments" => [
{
"key" => "retirement_age_for_person_#{person_index}",
"label" => false,
"i18n_template" => "age",
},
],
}
@informative_text = {
"translation" => "soft_validations.retirement.min.hint_text",
"arguments" => [
{
"key" => "plural_gender_for_person_#{person_index}",
"label" => false,
"i18n_template" => "gender",
},
{
"key" => "retirement_age_for_person_#{person_index}",
"label" => false,
"key" => "age#{person_index}",
"label" => true,
"i18n_template" => "age",
},
],
}
@informative_text = {}
end
def questions
@ -39,4 +25,8 @@ class Form::Sales::Pages::RetirementValueCheck < Form::Sales::Pages::Person
Form::Sales::Questions::RetirementValueCheck.new(nil, nil, self, person_index: @person_index),
]
end
def interruption_screen_question_ids
["age#{@person_index}", "ecstat#{@person_index}"]
end
end

19
app/models/form/sales/pages/savings_value_check.rb

@ -6,7 +6,20 @@ class Form::Sales::Pages::SavingsValueCheck < ::Form::Page
"savings_over_soft_max?" => true,
},
]
@informative_text = {}
@title_text = {
"translation" => "soft_validations.savings.title_text",
"arguments" => [
{
"key" => "field_formatted_as_currency",
"arguments_for_key" => "savings",
"i18n_template" => "savings",
},
],
}
@informative_text = {
"translation" => "soft_validations.savings.hint_text",
"arguments" => [],
}
end
def questions
@ -14,4 +27,8 @@ class Form::Sales::Pages::SavingsValueCheck < ::Form::Page
Form::Sales::Questions::SavingsValueCheck.new(nil, nil, self),
]
end
def interruption_screen_question_ids
%w[savings]
end
end

8
app/models/form/sales/pages/shared_ownership_deposit_value_check.rb

@ -12,8 +12,8 @@ class Form::Sales::Pages::SharedOwnershipDepositValueCheck < ::Form::Page
"arguments" => [
{
"key" => "field_formatted_as_currency",
"arguments_for_key" => "expected_shared_ownership_deposit_value",
"i18n_template" => "expected_shared_ownership_deposit_value",
"arguments_for_key" => "mortgage_deposit_and_discount_total",
"i18n_template" => "mortgage_deposit_and_discount_total",
},
],
}
@ -24,4 +24,8 @@ class Form::Sales::Pages::SharedOwnershipDepositValueCheck < ::Form::Page
Form::Sales::Questions::SharedOwnershipDepositValueCheck.new(nil, nil, self),
]
end
def interruption_screen_question_ids
%w[mortgage mortgageused cashdis type deposit value equity]
end
end

11
app/models/form/sales/pages/staircase_bought_value_check.rb

@ -8,7 +8,7 @@ class Form::Sales::Pages::StaircaseBoughtValueCheck < ::Form::Page
},
]
@title_text = {
"translation" => "soft_validations.staircase_bought_seems_high",
"translation" => "soft_validations.staircase_bought_seems_high.title_text",
"arguments" => [
{
"key" => "stairbought",
@ -16,7 +16,10 @@ class Form::Sales::Pages::StaircaseBoughtValueCheck < ::Form::Page
},
],
}
@informative_text = {}
@informative_text = {
"translation" => "soft_validations.staircase_bought_seems_high.hint_text",
"arguments" => [],
}
end
def questions
@ -24,4 +27,8 @@ class Form::Sales::Pages::StaircaseBoughtValueCheck < ::Form::Page
Form::Sales::Questions::StaircaseBoughtValueCheck.new(nil, nil, self),
]
end
def interruption_screen_question_ids
%w[stairbought]
end
end

2
app/models/form/sales/questions/household_wheelchair_check.rb

@ -3,7 +3,7 @@ class Form::Sales::Questions::HouseholdWheelchairCheck < ::Form::Question
super
@id = "wheel_value_check"
@check_answer_label = "Does anyone in the household use a wheelchair?"
@header = "Are you sure? You said previously that somebody in household uses a wheelchair"
@header = "You told us that someone in the household uses a wheelchair."
@type = "interruption_screen"
@answer_options = {
"0" => { "value" => "Yes" },

26
app/models/form_handler.rb

@ -40,6 +40,28 @@ class FormHandler
}
end
def ordered_sales_questions_for_all_years
sales_forms = forms.filter { |name, _form| name.end_with? "sales" }.values
ordered_questions = sales_forms.pop.questions.uniq(&:id)
question_ids = ordered_questions.map(&:id)
all_questions_from_previous_forms = sales_forms.flat_map(&:questions)
deprecated_questions_by_preceding_question_id(question_ids, all_questions_from_previous_forms).each do |preceding_question_id, deprecated_question|
index_of_preceding_question = ordered_questions.index { |q| q.id == preceding_question_id }
ordered_questions.insert(index_of_preceding_question + 1, deprecated_question)
end
ordered_questions
end
def deprecated_questions_by_preceding_question_id(current_form_question_ids, all_questions_from_previous_forms)
deprecated_questions = {}
all_questions_from_previous_forms.each_cons(2) do |preceding_question, question|
next if current_form_question_ids.include?(question.id) || deprecated_questions.values.map(&:id).include?(question.id)
deprecated_questions[preceding_question.id] = question
end
deprecated_questions
end
def lettings_forms
forms = {}
directories.each do |directory|
@ -95,9 +117,9 @@ class FormHandler
forms.count { |form| now.between?(form.start_date, form.end_date) } > 1
end
def use_fake_forms!
def use_fake_forms!(fake_forms = nil)
@directories = ["spec/fixtures/forms"]
@forms = get_all_forms
@forms = fake_forms || get_all_forms
end
def use_real_forms!

19
app/models/lettings_log.rb

@ -540,24 +540,6 @@ class LettingsLog < Log
private
def reset_derived_questions
dependent_questions = { waityear: [{ key: :renewal, value: 0 }],
referral: [{ key: :renewal, value: 0 }],
rsnvac: [{ key: :renewal, value: 0 }],
underoccupation_benefitcap: [{ key: :renewal, value: 0 }],
wchair: [{ key: :needstype, value: 1 }],
location_id: [{ key: :needstype, value: 1 }] }
dependent_questions.each do |dependent, conditions|
condition_key = conditions.first[:key]
condition_value = conditions.first[:value]
if public_send("#{condition_key}_changed?") && condition_value == public_send(condition_key) && !public_send("#{dependent}_changed?")
Rails.logger.debug("Cleared derived #{dependent} value")
self[dependent] = nil
end
end
end
def reset_invalid_unresolved_log_fields!
return unless unresolved?
@ -590,7 +572,6 @@ private
reset_invalid_unresolved_log_fields!
reset_scheme
reset_derived_questions
end
def dynamically_not_required

4
app/models/log.rb

@ -158,6 +158,10 @@ class Log < ApplicationRecord
end
end
def creation_method
bulk_upload_id ? "bulk upload" : "single log"
end
private
# Handle logs that are older than previous collection start date

14
app/models/sales_log.rb

@ -233,6 +233,20 @@ class SalesLog < Log
value * equity / 100
end
def mortgage_deposit_and_discount_total
mortgage_amount = mortgage || 0
deposit_amount = deposit || 0
cashdis_amount = cashdis || 0
mortgage_amount + deposit_amount + cashdis_amount
end
def mortgage_and_deposit_total
return unless mortgage && deposit
mortgage + deposit
end
def process_postcode(postcode, postcode_known_key, la_inferred_key, la_key)
return if postcode.blank?

0
app/models/validations/local_authority_validations.rb

6
app/models/validations/sales/financial_validations.rb

@ -7,7 +7,7 @@ module Validations::Sales::FinancialValidations
relevant_fields = %i[income1 ownershipsch uprn la postcode_full]
if record.london_property? && record.income1 > 90_000
relevant_fields.each { |field| record.errors.add field, I18n.t("validations.financial.income.over_hard_max_for_london") }
relevant_fields.each { |field| record.errors.add field, :over_hard_max_for_london, message: I18n.t("validations.financial.income.over_hard_max_for_london") }
elsif record.property_not_in_london? && record.income1 > 80_000
relevant_fields.each { |field| record.errors.add field, :over_hard_max_for_outside_london, message: I18n.t("validations.financial.income.over_hard_max_for_outside_london") }
end
@ -18,9 +18,9 @@ module Validations::Sales::FinancialValidations
relevant_fields = %i[income2 ownershipsch uprn la postcode_full]
if record.london_property? && record.income2 > 90_000
relevant_fields.each { |field| record.errors.add field, I18n.t("validations.financial.income.over_hard_max_for_london") }
relevant_fields.each { |field| record.errors.add field, :over_hard_max_for_london, message: I18n.t("validations.financial.income.over_hard_max_for_london") }
elsif record.property_not_in_london? && record.income2 > 80_000
relevant_fields.each { |field| record.errors.add field, I18n.t("validations.financial.income.over_hard_max_for_outside_london") }
relevant_fields.each { |field| record.errors.add field, :over_hard_max_for_outside_london, message: I18n.t("validations.financial.income.over_hard_max_for_outside_london") }
end
end

4
app/models/validations/sales/soft_validations.rb

@ -82,8 +82,8 @@ module Validations::Sales::SoftValidations
saledate - hodate >= 3.years
end
def purchase_price_min_or_max_text
value < sale_range.soft_min ? "minimum" : "maximum"
def purchase_price_higher_or_lower_text
value < sale_range.soft_min ? "lower" : "higher"
end
def purchase_price_soft_min_or_soft_max

10
app/models/validations/soft_validations.rb

@ -93,6 +93,10 @@ module Validations::SoftValidations
voiddate.present? && startdate.present? && voiddate.between?(startdate.to_date - TEN_YEARS_IN_DAYS, startdate.to_date - TWO_YEARS_IN_DAYS)
end
def net_income_higher_or_lower_text
net_income_in_soft_max_range? ? "higher" : "lower"
end
private
def details_known_or_lead_tenant?(tenant_number)
@ -124,11 +128,9 @@ private
def retired_under_soft_min_age?(person_num)
age = public_send("age#{person_num}")
economic_status = public_send("ecstat#{person_num}")
gender = public_send("sex#{person_num}")
return unless age && economic_status && gender
return unless age && economic_status
%w[M X].include?(gender) && tenant_is_retired?(economic_status) && age < retirement_age_for_person(person_num) ||
gender == "F" && tenant_is_retired?(economic_status) && age < 60
tenant_is_retired?(economic_status) && age < 60
end
def not_retired_over_soft_max_age?(person_num)

1
app/models/validations/tenancy_validations.rb

@ -30,6 +30,7 @@ module Validations::TenancyValidations
conditions.each do |condition|
next unless condition[:condition]
record.errors.add :needstype, condition[:error]
record.errors.add :tenancylength, :tenancylength_invalid, message: condition[:error]
record.errors.add :tenancy, condition[:error]
end

109
app/services/bulk_upload/lettings/year2022/row_parser.rb

@ -1,6 +1,7 @@
class BulkUpload::Lettings::Year2022::RowParser
include ActiveModel::Model
include ActiveModel::Attributes
include InterruptionScreenHelper
QUESTIONS = {
field_1: "What is the letting type?",
@ -279,9 +280,20 @@ class BulkUpload::Lettings::Year2022::RowParser
attribute :field_133, :integer
attribute :field_134, :integer
validates :field_1, presence: { message: I18n.t("validations.not_answered", question: "letting type") },
inclusion: { in: (1..12).to_a, message: I18n.t("validations.invalid_option", question: "letting type") }, on: :after_log
validates :field_4, presence: { if: proc { [2, 4, 6, 8, 10, 12].include?(field_1) } }, on: :after_log
validate :validate_valid_radio_option, on: :before_log
validates :field_1,
presence: {
message: I18n.t("validations.not_answered", question: "letting type"),
category: :setup,
},
inclusion: {
in: (1..12).to_a,
message: I18n.t("validations.invalid_option", question: "letting type"),
category: :setup,
unless: -> { field_1.blank? },
},
on: :after_log
validates :field_12, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 1 must be a number or the letter R" }, on: :after_log
validates :field_13, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 2 must be a number or the letter R" }, allow_blank: true, on: :after_log
@ -292,14 +304,33 @@ class BulkUpload::Lettings::Year2022::RowParser
validates :field_18, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 7 must be a number or the letter R" }, allow_blank: true, on: :after_log
validates :field_19, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 8 must be a number or the letter R" }, allow_blank: true, on: :after_log
validates :field_96, presence: { message: I18n.t("validations.not_answered", question: "tenancy start date (day)") }, on: :after_log
validates :field_97, presence: { message: I18n.t("validations.not_answered", question: "tenancy start date (month)") }, on: :after_log
validates :field_98, presence: { message: I18n.t("validations.not_answered", question: "tenancy start date (year)") }, on: :after_log
validates :field_98, format: { with: /\A\d{2}\z/, message: I18n.t("validations.setup.startdate.year_not_two_digits") }, on: :after_log
validates :field_96,
presence: {
message: I18n.t("validations.not_answered", question: "tenancy start date (day)"),
category: :setup,
}, on: :after_log
validates :field_97,
presence: {
message: I18n.t("validations.not_answered", question: "tenancy start date (month)"),
category: :setup,
},
on: :after_log
validates :field_98,
presence: {
message: I18n.t("validations.not_answered", question: "tenancy start date (year)"),
category: :setup,
},
format: {
with: /\A\d{2}\z/,
message: I18n.t("validations.setup.startdate.year_not_two_digits"),
unless: -> { field_98.blank? },
category: :setup,
},
on: :after_log
validate :validate_data_types, on: :after_log
validate :validate_nulls, on: :after_log
validate :validate_relevant_collection_window, on: :after_log
validate :validate_la_with_local_housing_referral, on: :after_log
validate :validate_cannot_be_la_referral_if_general_needs_and_la, on: :after_log
@ -309,6 +340,7 @@ class BulkUpload::Lettings::Year2022::RowParser
validate :validate_no_disabled_needs_conjunction, on: :after_log
validate :validate_dont_know_disabled_needs_conjunction, on: :after_log
validate :validate_no_and_dont_know_disabled_needs_conjunction, on: :after_log
validate :validate_no_housing_needs_questions_answered, on: :after_log
validate :validate_if_log_already_exists, on: :after_log, if: -> { FeatureToggle.bulk_upload_duplicate_log_check_enabled? }
validate :validate_owning_org_data_given, on: :after_log
@ -334,7 +366,9 @@ class BulkUpload::Lettings::Year2022::RowParser
validate :validate_declaration_acceptance, on: :after_log
validate :validate_valid_radio_option, on: :before_log
validate :validate_incomplete_soft_validations, on: :after_log
validate :validate_nulls, on: :after_log
def self.question_for_field(field)
QUESTIONS[field]
@ -422,7 +456,11 @@ private
fields = field_mapping_for_errors[question_id.to_sym] || []
fields.each do |field|
errors.add(field, I18n.t("validations.invalid_option", question: QUESTIONS[field]))
if setup_question?(question)
errors.add(field, I18n.t("validations.invalid_option", question: QUESTIONS[field]), category: :setup)
else
errors.add(field, I18n.t("validations.invalid_option", question: QUESTIONS[field]))
end
end
end
end
@ -479,13 +517,13 @@ private
def validate_location_exists
if scheme && field_5.present? && location.nil?
errors.add(:field_5, "Location could be found with provided scheme code")
errors.add(:field_5, "Location could be found with provided scheme code", category: :setup)
end
end
def validate_location_data_given
if bulk_upload.supported_housing? && field_5.blank?
errors.add(:field_5, "The scheme code must be present", category: "setup")
errors.add(:field_5, I18n.t("validations.not_answered", question: "scheme code"), category: :setup)
end
end
@ -497,19 +535,19 @@ private
unless owned_by_owning_org || owned_by_managing_org
block_log_creation!
errors.add(:field_4, "This management group code does not belong to your organisation, or any of your stock owners / managing agents")
errors.add(:field_4, "This management group code does not belong to your organisation, or any of your stock owners / managing agents", category: :setup)
end
end
def validate_scheme_exists
if field_4.present? && scheme.nil?
errors.add(:field_4, "The management group code is not correct")
errors.add(:field_4, "The management group code is not correct", category: :setup)
end
end
def validate_scheme_data_given
if bulk_upload.supported_housing? && field_4.blank?
errors.add(:field_4, "The management group code is not correct", category: "setup")
errors.add(:field_4, I18n.t("validations.not_answered", question: "management group code"), category: :setup)
end
end
@ -536,7 +574,7 @@ private
def validate_managing_org_data_given
if field_113.blank?
block_log_creation!
errors.add(:field_113, "The managing organisation code is incorrect", category: :setup)
errors.add(:field_113, I18n.t("validations.not_answered", question: "managing organisation"), category: :setup)
end
end
@ -565,7 +603,7 @@ private
block_log_creation!
if errors[:field_111].blank?
errors.add(:field_111, "The owning organisation code is incorrect", category: :setup)
errors.add(:field_111, I18n.t("validations.not_answered", question: "owning organisation"), category: :setup)
end
end
end
@ -611,6 +649,16 @@ private
end
end
def validate_no_housing_needs_questions_answered
if [field_55, field_56, field_57, field_58, field_59, field_60].all?(&:blank?)
errors.add(:field_59, I18n.t("validations.not_answered", question: "anybody with disabled access needs"))
errors.add(:field_58, I18n.t("validations.not_answered", question: "other access needs"))
%i[field_55 field_56 field_57].each do |field|
errors.add(field, I18n.t("validations.not_answered", question: "disabled access needs type"))
end
end
end
def validate_lettings_type_matches_bulk_upload
if [1, 3, 5, 7, 9, 11].include?(field_1) && !bulk_upload.general_needs?
errors.add(:field_1, I18n.t("validations.setup.lettype.supported_housing_mismatch"))
@ -643,9 +691,9 @@ private
return if start_date.blank? || bulk_upload.form.blank?
unless bulk_upload.form.valid_start_date_for_form?(start_date)
errors.add(:field_96, I18n.t("validations.date.outside_collection_window"))
errors.add(:field_97, I18n.t("validations.date.outside_collection_window"))
errors.add(:field_98, I18n.t("validations.date.outside_collection_window"))
errors.add(:field_96, I18n.t("validations.date.outside_collection_window"), category: :setup)
errors.add(:field_97, I18n.t("validations.date.outside_collection_window"), category: :setup)
errors.add(:field_98, I18n.t("validations.date.outside_collection_window"), category: :setup)
end
end
@ -702,7 +750,7 @@ private
if setup_question?(question)
fields.each do |field|
if errors[field].present?
if errors.select { |e| fields.include?(e.attribute) }.none?
errors.add(field, I18n.t("validations.not_answered", question: question.check_answer_label&.downcase), category: :setup)
end
end
@ -716,6 +764,23 @@ private
end
end
def validate_incomplete_soft_validations
routed_to_soft_validation_questions = log.form.questions.filter { |q| q.type == "interruption_screen" && q.page.routed_to?(log, nil) }
routed_to_soft_validation_questions.each do |question|
next unless question
next if question.completed?(log)
question.page.interruption_screen_question_ids.each do |interruption_screen_question_id|
field_mapping_for_errors[interruption_screen_question_id.to_sym].each do |field|
unless errors.any? { |e| e.options[:category] == :soft_validation && field_mapping_for_errors[interruption_screen_question_id.to_sym].include?(e.attribute) }
error_message = [display_title_text(question.page.title_text, log), display_informative_text(question.page.informative_text, log)].reject(&:empty?).join(". ")
errors.add(field, message: error_message, category: :soft_validation)
end
end
end
end
end
def setup_question?(question)
log.form.setup_sections[0].subsections[0].questions.include?(question)
end

131
app/services/bulk_upload/lettings/year2023/row_parser.rb

@ -1,6 +1,7 @@
class BulkUpload::Lettings::Year2023::RowParser
include ActiveModel::Model
include ActiveModel::Attributes
include InterruptionScreenHelper
QUESTIONS = {
field_1: "Which organisation owns this property?",
@ -279,9 +280,61 @@ class BulkUpload::Lettings::Year2023::RowParser
attribute :field_133, :integer
attribute :field_134, :decimal
validates :field_5, presence: { message: I18n.t("validations.not_answered", question: "letting type") },
inclusion: { in: (1..12).to_a, message: I18n.t("validations.invalid_option", question: "letting type") }, on: :after_log
validates :field_16, presence: { if: proc { [2, 4, 6, 8, 10, 12].include?(field_5) } }, on: :after_log
validate :validate_valid_radio_option, on: :before_log
validates :field_5,
presence: {
message: I18n.t("validations.not_answered", question: "letting type"),
category: :setup,
},
inclusion: {
in: (1..12).to_a,
message: I18n.t("validations.invalid_option", question: "letting type"),
unless: -> { field_5.blank? },
category: :setup,
},
on: :after_log
validates :field_6,
presence: {
message: I18n.t("validations.not_answered", question: "property renewal"),
category: :setup,
},
on: :after_log
validates :field_7,
presence: {
message: I18n.t("validations.not_answered", question: "tenancy start date (day)"),
category: :setup,
},
on: :after_log
validates :field_8,
presence: {
message: I18n.t("validations.not_answered", question: "tenancy start date (month)"),
category: :setup,
},
on: :after_log
validates :field_9,
presence: {
message: I18n.t("validations.not_answered", question: "tenancy start date (year)"),
category: :setup,
},
format: {
with: /\A\d{2}\z/,
message: I18n.t("validations.setup.startdate.year_not_two_digits"),
category: :setup,
unless: -> { field_9.blank? },
},
on: :after_log
validates :field_16,
presence: {
if: proc { [2, 4, 6, 8, 10, 12].include?(field_5) },
category: :setup,
},
on: :after_log
validates :field_46, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 1 must be a number or the letter R" }, on: :after_log
validates :field_52, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 2 must be a number or the letter R" }, allow_blank: true, on: :after_log
@ -292,16 +345,8 @@ class BulkUpload::Lettings::Year2023::RowParser
validates :field_72, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 7 must be a number or the letter R" }, allow_blank: true, on: :after_log
validates :field_76, format: { with: /\A\d{1,3}\z|\AR\z/, message: "Age of person 8 must be a number or the letter R" }, allow_blank: true, on: :after_log
validates :field_4, presence: { message: I18n.t("validations.not_answered", question: "needs type") }, on: :after_log
validates :field_6, presence: { message: I18n.t("validations.not_answered", question: "property renewal") }, on: :after_log
validates :field_7, presence: { message: I18n.t("validations.not_answered", question: "tenancy start date (day)") }, on: :after_log
validates :field_8, presence: { message: I18n.t("validations.not_answered", question: "tenancy start date (month)") }, on: :after_log
validates :field_9, presence: { message: I18n.t("validations.not_answered", question: "tenancy start date (year)") }, on: :after_log
validates :field_9, format: { with: /\A\d{2}\z/, message: I18n.t("validations.setup.startdate.year_not_two_digits") }, on: :after_log
validate :validate_needs_type_present, on: :after_log
validate :validate_data_types, on: :after_log
validate :validate_nulls, on: :after_log
validate :validate_relevant_collection_window, on: :after_log
validate :validate_la_with_local_housing_referral, on: :after_log
validate :validate_cannot_be_la_referral_if_general_needs_and_la, on: :after_log
@ -311,6 +356,7 @@ class BulkUpload::Lettings::Year2023::RowParser
validate :validate_no_disabled_needs_conjunction, on: :after_log
validate :validate_dont_know_disabled_needs_conjunction, on: :after_log
validate :validate_no_and_dont_know_disabled_needs_conjunction, on: :after_log
validate :validate_no_housing_needs_questions_answered, on: :after_log
validate :validate_if_log_already_exists, on: :after_log, if: -> { FeatureToggle.bulk_upload_duplicate_log_check_enabled? }
validate :validate_owning_org_data_given, on: :after_log
@ -335,10 +381,12 @@ class BulkUpload::Lettings::Year2023::RowParser
validate :validate_declaration_acceptance, on: :after_log
validate :validate_valid_radio_option, on: :before_log
validate :validate_nulls, on: :after_log
validate :validate_uprn_exists_if_any_key_adddress_fields_are_blank, on: :after_log
validate :validate_incomplete_soft_validations, on: :after_log
def self.question_for_field(field)
QUESTIONS[field]
end
@ -425,7 +473,11 @@ private
fields = field_mapping_for_errors[question_id.to_sym] || []
fields.each do |field|
errors.add(field, I18n.t("validations.invalid_option", question: QUESTIONS[field]))
if setup_question?(question)
errors.add(field, I18n.t("validations.invalid_option", question: QUESTIONS[field]), category: :setup)
else
errors.add(field, I18n.t("validations.invalid_option", question: QUESTIONS[field]))
end
end
end
end
@ -457,6 +509,23 @@ private
end
end
def validate_incomplete_soft_validations
routed_to_soft_validation_questions = log.form.questions.filter { |q| q.type == "interruption_screen" && q.page.routed_to?(log, nil) }
routed_to_soft_validation_questions.each do |question|
next unless question
next if question.completed?(log)
question.page.interruption_screen_question_ids.each do |interruption_screen_question_id|
field_mapping_for_errors[interruption_screen_question_id.to_sym].each do |field|
unless errors.any? { |e| field_mapping_for_errors[interruption_screen_question_id.to_sym].include?(e.attribute) }
error_message = [display_title_text(question.page.title_text, log), display_informative_text(question.page.informative_text, log)].reject(&:empty?).join(". ")
errors.add(field, message: error_message, category: :soft_validation)
end
end
end
end
end
def duplicate_check_fields
%w[
startdate
@ -473,7 +542,7 @@ private
def validate_needs_type_present
if field_4.blank?
errors.add(:field_4, I18n.t("validations.not_answered", question: "needs type"))
errors.add(:field_4, I18n.t("validations.not_answered", question: "needs type"), category: :setup)
end
end
@ -516,6 +585,16 @@ private
end
end
def validate_no_housing_needs_questions_answered
if [field_83, field_84, field_85, field_86, field_87, field_88].all?(&:blank?)
errors.add(:field_87, I18n.t("validations.not_answered", question: "anybody with disabled access needs"))
errors.add(:field_86, I18n.t("validations.not_answered", question: "other access needs"))
%i[field_83 field_84 field_85].each do |field|
errors.add(field, I18n.t("validations.not_answered", question: "disabled access needs type"))
end
end
end
def validate_lettings_type_matches_bulk_upload
if [1, 3, 5, 7, 9, 11].include?(field_5) && !general_needs?
errors.add(:field_5, I18n.t("validations.setup.lettype.supported_housing_mismatch"))
@ -556,9 +635,9 @@ private
return if start_date.blank? || bulk_upload.form.blank?
unless bulk_upload.form.valid_start_date_for_form?(start_date)
errors.add(:field_7, I18n.t("validations.date.outside_collection_window"))
errors.add(:field_8, I18n.t("validations.date.outside_collection_window"))
errors.add(:field_9, I18n.t("validations.date.outside_collection_window"))
errors.add(:field_7, I18n.t("validations.date.outside_collection_window"), category: :setup)
errors.add(:field_8, I18n.t("validations.date.outside_collection_window"), category: :setup)
errors.add(:field_9, I18n.t("validations.date.outside_collection_window"), category: :setup)
end
end
@ -579,7 +658,7 @@ private
if setup_question?(question)
fields.each do |field|
if errors[field].present?
if errors.select { |e| fields.include?(e.attribute) }.none?
question_text = question.check_answer_label.presence || question.header.presence || "this question"
errors.add(field, I18n.t("validations.not_answered", question: question_text.downcase), category: :setup)
end
@ -600,19 +679,19 @@ private
unless location.scheme == scheme
block_log_creation!
errors.add(:field_17, "Scheme code must relate to a location that is owned by owning organisation or managing organisation")
errors.add(:field_17, "Scheme code must relate to a location that is owned by owning organisation or managing organisation", category: :setup)
end
end
def validate_location_exists
if scheme && field_17.present? && location.nil?
errors.add(:field_17, "Location could be found with provided scheme code")
errors.add(:field_17, "Location could be found with provided scheme code", category: :setup)
end
end
def validate_location_data_given
if supported_housing? && field_17.blank?
errors.add(:field_17, "The scheme code must be present", category: "setup")
errors.add(:field_17, I18n.t("validations.not_answered", question: "scheme code"), category: "setup")
end
end
@ -624,19 +703,19 @@ private
unless owned_by_owning_org || owned_by_managing_org
block_log_creation!
errors.add(:field_16, "This management group code does not belong to your organisation, or any of your stock owners / managing agents")
errors.add(:field_16, "This management group code does not belong to your organisation, or any of your stock owners / managing agents", category: :setup)
end
end
def validate_scheme_exists
if field_16.present? && scheme.nil?
errors.add(:field_16, "The management group code is not correct")
errors.add(:field_16, "The management group code is not correct", category: :setup)
end
end
def validate_scheme_data_given
if supported_housing? && field_16.blank?
errors.add(:field_16, "The management group code is not correct", category: "setup")
errors.add(:field_16, I18n.t("validations.not_answered", question: "management group code"), category: "setup")
end
end
@ -690,7 +769,7 @@ private
def validate_owning_org_data_given
if field_1.blank?
block_log_creation!
errors.add(:field_1, "The owning organisation code is incorrect", category: :setup)
errors.add(:field_1, I18n.t("validations.not_answered", question: "owning organisation"), category: :setup)
end
end

25
app/services/bulk_upload/sales/year2022/row_parser.rb

@ -1,6 +1,7 @@
class BulkUpload::Sales::Year2022::RowParser
include ActiveModel::Model
include ActiveModel::Attributes
include InterruptionScreenHelper
QUESTIONS = {
field_1: "What is the purchaser code?",
@ -283,6 +284,7 @@ class BulkUpload::Sales::Year2022::RowParser
validate :validate_created_by_exists, on: :after_log
validate :validate_created_by_related, on: :after_log
validate :validate_relevant_collection_window, on: :after_log
validate :validate_incomplete_soft_validations, on: :after_log
def self.question_for_field(field)
QUESTIONS[field]
@ -939,9 +941,26 @@ private
return if saledate.blank? || bulk_upload.form.blank?
unless bulk_upload.form.valid_start_date_for_form?(saledate)
errors.add(:field_2, I18n.t("validations.date.outside_collection_window"))
errors.add(:field_3, I18n.t("validations.date.outside_collection_window"))
errors.add(:field_4, I18n.t("validations.date.outside_collection_window"))
errors.add(:field_2, I18n.t("validations.date.outside_collection_window"), category: :setup)
errors.add(:field_3, I18n.t("validations.date.outside_collection_window"), category: :setup)
errors.add(:field_4, I18n.t("validations.date.outside_collection_window"), category: :setup)
end
end
def validate_incomplete_soft_validations
routed_to_soft_validation_questions = log.form.questions.filter { |q| q.type == "interruption_screen" && q.page.routed_to?(log, nil) }
routed_to_soft_validation_questions.each do |question|
next unless question
next if question.completed?(log)
question.page.interruption_screen_question_ids.each do |interruption_screen_question_id|
field_mapping_for_errors[interruption_screen_question_id.to_sym].each do |field|
unless errors.any? { |e| e.options[:category] == :soft_validation && field_mapping_for_errors[interruption_screen_question_id.to_sym].include?(e.attribute) }
error_message = [display_title_text(question.page.title_text, log), display_informative_text(question.page.informative_text, log)].reject(&:empty?).join(". ")
errors.add(field, message: error_message, category: :soft_validation)
end
end
end
end
end
end

107
app/services/csv/sales_log_csv_service.rb

@ -0,0 +1,107 @@
module Csv
class SalesLogCsvService
def initialize(export_type:)
@export_type = export_type
@attributes = sales_log_attributes
end
def prepare_csv(logs)
CSV.generate(headers: true) do |csv|
csv << @attributes
logs.find_each do |log|
csv << @attributes.map { |attribute| value(attribute, log) }
end
end
end
private
ATTRIBUTES_OF_RELATED_OBJECTS = {
day: %i[saledate day],
month: %i[saledate month],
year: %i[saledate year],
is_dpo: %i[created_by is_dpo],
created_by_name: %i[created_by name],
owning_organisation_name: %i[owning_organisation name],
}.freeze
FIELDS_ALWAYS_EXPORTED_AS_CODES = %w[
la
prevloc
].freeze
FIELDS_ALWAYS_EXPORTED_AS_LABELS = {
"la_label" => "la",
"prevloc_label" => "prevloc",
}.freeze
DATE_FIELDS = %w[
created_at
updated_at
].freeze
def value(attribute, log)
if ATTRIBUTES_OF_RELATED_OBJECTS.key? attribute.to_sym
call_chain = ATTRIBUTES_OF_RELATED_OBJECTS[attribute.to_sym]
call_chain.reduce(log) { |object, next_call| object&.public_send(next_call) }
elsif FIELDS_ALWAYS_EXPORTED_AS_CODES.include? attribute
log.send(attribute)
elsif FIELDS_ALWAYS_EXPORTED_AS_LABELS.key? attribute
attribute = FIELDS_ALWAYS_EXPORTED_AS_LABELS[attribute]
field_value = log.send(attribute)
get_label(field_value, attribute, log)
elsif DATE_FIELDS.include? attribute
log.send(attribute)&.iso8601
else
value = log.public_send(attribute)
case @export_type
when "codes"
value
when "labels"
answer_label = get_label(value, attribute, log)
answer_label || label_if_boolean_value(value) || value
end
end
end
def get_label(value, attribute, log)
log.form
.get_question(attribute, log)
&.label_from_value(value)
end
def label_if_boolean_value(value)
return "Yes" if value == true
return "No" if value == false
end
ATTRIBUTE_MAPPINGS = {
"saledate" => %w[day month year],
"exdate" => %w[exday exmonth exyear],
"hodate" => %w[hoday homonth hoyear],
"postcode_full" => %w[pcode1 pcode2],
"ppostcode_full" => %w[ppostc1 ppostc2],
"la" => %w[la la_label],
"prevloc" => %w[prevloc prevloc_label],
"created_by_id" => %w[created_by_name],
"owning_organisation_id" => %w[owning_organisation_name],
}.freeze
def sales_log_attributes
ordered_questions = FormHandler.instance.ordered_sales_questions_for_all_years
ordered_questions.reject! { |q| q.id.match?(/((?<!la)_known)|(_check)|(_asked)/) }
attributes = ordered_questions.flat_map do |question|
if question.type == "checkbox"
question.answer_options.keys
elsif ATTRIBUTE_MAPPINGS.key? question.id
ATTRIBUTE_MAPPINGS[question.id]
else
question.id
end
end
non_question_fields = %w[id status created_at updated_at old_id collection_start_year creation_method is_dpo]
non_question_fields + attributes
end
end
end

23
app/services/imports/lettings_logs_import_service.rb

@ -105,7 +105,7 @@ module Imports
attributes["hb"] = unsafe_string_as_integer(xml_doc, "Q6Ben")
attributes["benefits"] = unsafe_string_as_integer(xml_doc, "Q7Ben")
attributes["earnings"] = safe_string_as_decimal(xml_doc, "Q8Money")
attributes["earnings"] = safe_string_as_decimal(xml_doc, "Q8Money")&.round
attributes["net_income_known"] = net_income_known(xml_doc, attributes["earnings"])
attributes["incfreq"] = unsafe_string_as_integer(xml_doc, "Q8a")
@ -284,11 +284,14 @@ module Imports
end
def rescue_validation_or_raise(lettings_log, attributes, previous_status, exception)
charges_attributes = %w[brent scharge pscharge supcharg tcharge]
# Blank out all invalid fields for in-progress logs
if %w[saved submitted-invalid].include?(previous_status)
lettings_log.errors.each do |error|
@logger.warn("Log #{lettings_log.old_id}: Removing field #{error.attribute} from log triggering validation: #{error.type}")
attributes.delete(error.attribute.to_s)
charges_attributes.each { |attribute| attributes.delete(attribute) } if error.attribute == :tcharge
end
@logs_overridden << lettings_log.old_id
return save_lettings_log(attributes, previous_status)
@ -306,16 +309,16 @@ module Imports
%i[earnings over_hard_max] => %w[ecstat1],
%i[tshortfall no_outstanding_charges] => %w[tshortfall hbrentshortfall],
%i[beds outside_the_range] => %w[beds],
%i[tcharge complete_1_of_3] => %w[brent scharge pscharge supcharg tcharge],
%i[scharge under_min] => %w[brent scharge pscharge supcharg tcharge],
%i[tcharge complete_1_of_3] => charges_attributes,
%i[scharge under_min] => charges_attributes,
%i[tshortfall must_be_positive] => %w[tshortfall tshortfall_known],
%i[referral referral_invalid] => %w[referral],
%i[pscharge outside_the_range] => %w[brent scharge pscharge supcharg tcharge],
%i[supcharg outside_the_range] => %w[brent scharge pscharge supcharg tcharge],
%i[scharge outside_the_range] => %w[brent scharge pscharge supcharg tcharge],
%i[pscharge outside_the_range] => charges_attributes,
%i[supcharg outside_the_range] => charges_attributes,
%i[scharge outside_the_range] => charges_attributes,
%i[location_id not_active] => %w[location_id scheme_id],
%i[tcharge under_10] => %w[brent scharge pscharge supcharg tcharge],
%i[brent over_hard_max] => %w[brent scharge pscharge supcharg tcharge],
%i[tcharge under_10] => charges_attributes,
%i[brent over_hard_max] => charges_attributes,
}
(2..8).each do |person|
@ -464,11 +467,11 @@ module Imports
def previous_postcode_known(xml_doc, previous_postcode, prevloc)
previous_postcode_known = string_or_nil(xml_doc, "Q12bnot")
if previous_postcode_known == "Temporary_or_Unknown" || (previous_postcode.nil? && prevloc.present?)
0
0 # not known
elsif previous_postcode.nil?
nil
else
1
1 # known
end
end

60
app/services/imports/sales_logs_import_service.rb

@ -70,7 +70,7 @@ module Imports
attributes["inc1mort"] = unsafe_string_as_integer(xml_doc, "Q2Person1Mortgage")
attributes["income2"] = safe_string_as_integer(xml_doc, "Q2Person2Income")
attributes["income2nk"] = income_known(unsafe_string_as_integer(xml_doc, "P2IncKnown"))
attributes["savings"] = safe_string_as_integer(xml_doc, "Q3Savings")
attributes["savings"] = safe_string_as_integer(xml_doc, "Q3Savings")&.round(-1)
attributes["savingsnk"] = savings_known(xml_doc)
attributes["prevown"] = unsafe_string_as_integer(xml_doc, "Q4PrevOwnedProperty")
attributes["mortgage"] = safe_string_as_decimal(xml_doc, "CALCMORT")
@ -217,38 +217,46 @@ module Imports
attributes.delete("ppcodenk") if error.attribute == :ppostcode_full
end
@logs_overridden << sales_log.old_id
save_sales_log(attributes, previous_status)
elsif sales_log.errors.of_kind?(:postcode_full, :postcodes_not_matching)
@logger.warn("Log #{sales_log.old_id}: Removing previous postcode known and previous postcode as the postcode is invalid")
@logs_overridden << sales_log.old_id
attributes.delete("ppcodenk")
attributes.delete("ppostcode_full")
save_sales_log(attributes, previous_status)
elsif sales_log.errors.of_kind?(:exdate, :over_a_year_from_saledate)
@logger.warn("Log #{sales_log.old_id}: Removing exchange date as the exchange date is invalid")
@logs_overridden << sales_log.old_id
attributes.delete("exdate")
save_sales_log(attributes, previous_status)
elsif sales_log.errors.of_kind?(:income1, :over_hard_max_for_outside_london)
@logger.warn("Log #{sales_log.old_id}: Removing income1 as the income1 is invalid")
@logs_overridden << sales_log.old_id
attributes.delete("income1")
save_sales_log(attributes, previous_status)
elsif sales_log.errors.of_kind?(:equity, :over_max) || sales_log.errors.of_kind?(:equity, :under_min)
@logger.warn("Log #{sales_log.old_id}: Removing equity as the equity is invalid")
return save_sales_log(attributes, previous_status)
end
errors = {
%i[postcode_full postcodes_not_matching] => %w[ppcodenk ppostcode_full],
%i[exdate over_a_year_from_saledate] => %w[exdate],
%i[income1 over_hard_max_for_outside_london] => %w[income1],
%i[income1 over_hard_max_for_london] => %w[income1],
%i[income2 over_hard_max_for_outside_london] => %w[income2],
%i[income2 over_hard_max_for_london] => %w[income2],
%i[equity over_max] => %w[equity],
%i[equity under_min] => %w[equity],
%i[mscharge under_min] => %w[mscharge has_mscharge],
%i[mortgage cannot_be_0] => %w[mortgage],
%i[frombeds outside_the_range] => %w[frombeds],
}
errors.each do |(error, fields)|
next unless sales_log.errors.of_kind?(*error)
attribute, _type = error
fields.each do |field|
@logger.warn("Log #{sales_log.old_id}: Removing #{field} with error: #{sales_log.errors[attribute].sort.join(', ')}")
attributes.delete(field)
end
@logs_overridden << sales_log.old_id
attributes.delete("equity")
save_sales_log(attributes, previous_status)
elsif sales_log.errors.of_kind?(:postcode_full, :wrong_format)
return save_sales_log(attributes, previous_status)
end
if sales_log.errors.of_kind?(:postcode_full, :wrong_format)
@logger.warn("Log #{sales_log.old_id}: Removing postcode as the postcode is invalid")
@logs_overridden << sales_log.old_id
attributes.delete("postcode_full")
attributes["pcodenk"] = attributes["la"].present? ? 1 : nil
save_sales_log(attributes, previous_status)
elsif sales_log.errors.of_kind?(:mortgage, :cannot_be_0)
@logger.warn("Log #{sales_log.old_id}: Removing mortgage because it cannot be 0")
elsif sales_log.errors.of_kind?(:ppostcode_full, :wrong_format)
@logger.warn("Log #{sales_log.old_id}: Removing previous postcode as the postcode is invalid")
@logs_overridden << sales_log.old_id
attributes.delete("mortgage")
attributes.delete("ppostcode_full")
attributes["ppcodenk"] = attributes["prevloc"].present? ? 1 : nil
save_sales_log(attributes, previous_status)
elsif sales_log.errors.of_kind?(:uprn, :uprn_error)
@logger.warn("Log #{sales_log.old_id}: Setting uprn_known to no with error: #{sales_log.errors[:uprn].join(', ')}")

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

@ -1,5 +1,5 @@
<%= govuk_summary_list do |summary_list| %>
<% total_applicable_questions(subsection, @log, current_user).each do |question| %>
<% questions.each do |question| %>
<% summary_list.row do |row| %>
<% row.key { get_question_label(question) } %>
<% row.value do %>
@ -23,7 +23,7 @@
<% if @log.collection_period_open? %>
<% row.action(
text: question.action_text(@log),
href: question.action_href(@log, question.page.id),
href: action_href(@log, question.page.id, referrer),
visually_hidden_text: question.check_answer_label.to_s.downcase,
) %>
<% end %>

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

@ -0,0 +1,13 @@
<% if question.page.routed_to?(@log, current_user) %>
<%= govuk_panel(
classes: "app-panel--interruption",
) do %>
<p class="govuk-heading-l"><%= display_title_text(title_text, lettings_log) %></p>
<% if informative_text.present? %>
<p class="govuk-body-l"><%= display_informative_text(informative_text, lettings_log) %></p>
<% end %>
<% if question.hint_text.present? %>
<p class="govuk-body-l"><%= question.hint_text&.html_safe %></p>
<% end %>
<% end %>
<% end %>

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

@ -1,22 +1,24 @@
<%= govuk_panel(
title_text: display_title_text(title_text, lettings_log),
classes: "app-panel--interruption",
) do %>
<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 %>
<% question.answer_options.map do |key, options| %>
<% if key.starts_with?("divider") %>
<%= f.govuk_radio_divider %>
<% else %>
<%= f.govuk_radio_button question.id,
key,
label: { text: options["value"] },
hint: { text: options["hint"] },
**stimulus_html_attributes(question) %>
<% end %>
<% end %>
<% end %>
<%= f.govuk_submit "Save and continue", accesskey: "s", class: "app-button--inverse govuk-!-margin-bottom-0" %>
<% end %>
<%= render partial: "form/interruption_screen_banner", locals: { question:, title_text:, informative_text:, lettings_log: } %>
<h1 class="govuk-heading-m">
Make sure these answers are correct:
</h1>
<div class="x-govuk-summary-card govuk-!-margin-bottom-6">
<div class="x-govuk-summary-card__body">
<%= render partial: "form/check_answers_summary_list", locals: {
lettings_log:,
questions: soft_validation_affected_questions(question, @log).filter { |q| q.page.routed_to?(@log, current_user) },
referrer: "interruption_screen",
} %>
</div>
</div>
<%= f.hidden_field question.id, value: "0" %>
<div class="govuk-button-group">
<%= f.govuk_submit "Confirm and continue" %>
<%= govuk_link_to(
(@page.skip_text || "Skip for now"),
(@page.skip_href(@log) || send(@log.form.next_page_redirect_path(@page, @log, current_user), @log)),
) %>
</div>

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

@ -25,6 +25,8 @@
<%= render partial: "form/check_answers_summary_list", locals: {
subsection:,
lettings_log: @log,
questions: total_applicable_questions(subsection, @log, current_user),
referrer: "check_answers",
} %>
<% end %>

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

@ -7,7 +7,7 @@
<div data-controller="govukfrontend"></div>
<%= form_with model: @log, url: request.original_url, 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" %>">
<div class="govuk-grid-column-two-thirds-from-desktop">
<% remove_other_page_errors(@log, @page) %>
<%= f.govuk_error_summary %>
@ -59,6 +59,8 @@
<% end %>
<%= f.hidden_field :page, value: @page.id %>
<%= f.hidden_field :interruption_page_id, value: @interruption_page_id %>
<%= f.hidden_field :interruption_page_referrer_type, value: @interruption_page_referrer_type %>
<div class="govuk-button-group">
<% if !@page.interruption_screen? && if request.query_parameters["referrer"] != "check_answers" %>

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

@ -23,7 +23,7 @@
<h3 class="x-govuk-summary-card__title"><%= subsection.label %></h3>
</div>
<div class="x-govuk-summary-card__body">
<%= render partial: "form/check_answers_summary_list", locals: { subsection: } %>
<%= render partial: "form/check_answers_summary_list", locals: { subsection:, questions: total_applicable_questions(subsection, @log, current_user), referrer: "check_answers" } %>
</div>
</div>
<% end %>

2
app/views/logs/_log_list.html.erb

@ -1,6 +1,6 @@
<h2 class="govuk-body">
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "logs", path: request.path)) %>
<% if logs&.first&.lettings? %>
<% if logs&.any? %>
<%= govuk_link_to "Download (CSV)", csv_download_url, type: "text/csv", class: "govuk-!-margin-right-4" %>
<% if @current_user.support? %>
<%= govuk_link_to "Download (CSV, codes only)", csv_codes_only_download_url, type: "text/csv" %>

2
app/views/logs/csv_confirmation.html.erb

@ -9,7 +9,7 @@
<p class="govuk-body">Open your email inbox and click the link to download your CSV file.</p>
<p class="govuk-body">
<%= govuk_link_to "Return to logs", lettings_logs_path %>
<%= govuk_link_to "Return to logs", logs_path_for_controller(controller) %>
</p>
</div>
</div>

4
app/views/logs/index.html.erb

@ -78,8 +78,8 @@
searched: @searched,
item_label:,
total_count: @total_count,
csv_download_url: csv_download_url_for_controller(controller_type: controller, search: @search_term, codes_only: false),
csv_codes_only_download_url: csv_download_url_for_controller(controller_type: controller, search: @search_term, codes_only: true),
csv_download_url: csv_download_url_for_controller(controller:, search: @search_term, codes_only: false),
csv_codes_only_download_url: csv_download_url_for_controller(controller:, search: @search_term, codes_only: true),
} %>
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "logs" } %>
</div>

7
app/views/merge_requests/confirm_telephone_number.html.erb

@ -21,7 +21,12 @@
<% if @merge_request.absorbing_organisation.phone.present? %>
<%= f.govuk_radio_buttons_fieldset(:telephone_number_correct, legend: nil) do %>
<%= f.govuk_radio_button :telephone_number_correct, true, checked: @merge_request.telephone_number_correct?, label: { text: "This telephone number is correct" }, link_errors: true %>
<%= f.govuk_radio_button :telephone_number_correct, false, checked: @merge_request.new_telephone_number.present?, label: { text: "Enter a new phone number" } do %>
<%= f.govuk_radio_button(
:telephone_number_correct,
false,
checked: (@merge_request.new_telephone_number.present? || @merge_request.errors.key?(:new_telephone_number)),
label: { text: "Enter a new phone number" },
) do %>
<%= f.govuk_text_field :new_telephone_number, label: { text: "Telephone number" }, width: "two-thirds" %>
<% end %>
<%= f.hidden_field :page, value: "confirm_telephone_number" %>

30
app/views/merge_requests/new_organisation_address.html.erb

@ -1,5 +1,33 @@
<% content_for :before_content do %>
<% title = "New organisation address" %>
<% content_for :title, title %>
<%= govuk_back_link href: new_organisation_name_merge_request_path(id: @merge_request) %>
<%= govuk_back_link href: new_organisation_name_merge_request_path(@merge_request) %>
<% end %>
<%= form_with model: @merge_request, url: merge_request_path, method: :patch do |f| %>
<%= f.govuk_error_summary %>
<h2 class="govuk-heading-l">What is <%= @merge_request.new_organisation_name.possessive %> address?</h2>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds-from-desktop">
<%= f.govuk_text_field :new_organisation_address_line1,
label: { text: "Address line 1", size: "m" },
autocomplete: "address-line1" %>
<%= f.govuk_text_field :new_organisation_address_line2,
label: { text: "Address line 2", size: "m" },
autocomplete: "address-line2" %>
<%= f.govuk_text_field :new_organisation_postcode,
label: { text: "Postcode", size: "m" },
autocomplete: "postal-code",
width: 10 %>
<%= f.hidden_field :page, value: "new_organisation_address" %>
<div class="govuk-button-group">
<%= f.govuk_submit %>
<%= govuk_link_to("Skip for now", new_organisation_telephone_number_merge_request_path(@merge_request)) %>
</div>
</div>
</div>
<% end %>

5
app/views/merge_requests/new_organisation_telephone_number.html.erb

@ -0,0 +1,5 @@
<% content_for :before_content do %>
<% title = "New organisation telephone number" %>
<% content_for :title, title %>
<%= govuk_back_link href: new_organisation_address_merge_request_path(@merge_request) %>
<% end %>

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

@ -34,8 +34,8 @@
searched: @searched,
item_label:,
total_count: @total_count,
csv_download_url: logs_csv_download_organisation_path(@organisation, search: @search_term, codes_only: false),
csv_codes_only_download_url: logs_csv_download_organisation_path(@organisation, search: @search_term, codes_only: true),
csv_download_url: csv_download_url_by_log_type(@log_type, @organisation, search: @search_term, codes_only: false),
csv_codes_only_download_url: csv_download_url_by_log_type(@log_type, @organisation, search: @search_term, codes_only: true),
} %>
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "logs" } %>
</div>

377
config/forms/2022_2023.json

@ -789,7 +789,10 @@
"title_text": {
"translation": "soft_validations.void_date.title_text"
},
"informative_text": {},
"informative_text": {
"translation": "soft_validations.void_date.hint_text",
"arguments": []
},
"questions": {
"void_date_value_check": {
"check_answer_label": "Void date confirmation",
@ -814,7 +817,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["voiddate", "startdate"]
},
"property_major_repairs": {
"header": "",
@ -898,7 +902,10 @@
"title_text": {
"translation": "soft_validations.major_repairs_date.title_text"
},
"informative_text": {},
"informative_text": {
"translation": "soft_validations.major_repairs_date.hint_text",
"arguments": []
},
"questions": {
"major_repairs_date_value_check": {
"check_answer_label": "Major repairs date confirmation",
@ -923,7 +930,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["mrcdate", "startdate"]
}
},
"displayed_in_tasklist": [
@ -1237,7 +1245,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8"]
},
"females_in_soft_age_range_in_pregnant_household_lead_hhmemb_value_check": {
"depends_on": [
@ -1280,7 +1289,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8", "age1", "age2", "age3", "age4", "age5", "age6", "age7", "age8"]
},
"lead_tenant_age": {
"header": "",
@ -1379,7 +1389,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8"]
},
"females_in_soft_age_range_in_pregnant_household_lead_age_value_check": {
"depends_on": [
@ -1422,7 +1433,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8", "age1", "age2", "age3", "age4", "age5", "age6", "age7", "age8"]
},
"lead_tenant_gender_identity": {
"header": "",
@ -1500,7 +1512,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8"]
},
"females_in_soft_age_range_in_pregnant_household_lead_value_check": {
"depends_on": [
@ -1543,7 +1556,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8", "age1", "age2", "age3", "age4", "age5", "age6", "age7", "age8"]
},
"lead_tenant_ethnic_group": {
"header": "",
@ -1839,27 +1853,13 @@
"translation": "soft_validations.retirement.min.title",
"arguments": [
{
"key": "retirement_age_for_person_1",
"label": false,
"i18n_template": "age"
}
]
},
"informative_text": {
"translation": "soft_validations.retirement.min.hint_text",
"arguments": [
{
"key": "plural_gender_for_person_1",
"label": false,
"i18n_template": "gender"
},
{
"key": "retirement_age_for_person_1",
"label": false,
"key": "age1",
"label": true,
"i18n_template": "age"
}
]
},
"informative_text": {},
"questions": {
"retirement_value_check": {
"check_answer_label": "Retirement confirmation",
@ -1885,7 +1885,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["ecstat1", "age1"]
},
"lead_tenant_over_retirement_value_check": {
"depends_on": [
@ -1943,7 +1944,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["ecstat1", "sex1", "age1"]
},
"person_2_known": {
"header": "You’ve given us the details for 1 person in the household",
@ -2123,7 +2125,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8"]
},
"females_in_soft_age_range_in_pregnant_household_person_2_age_value_check": {
"depends_on": [
@ -2167,7 +2170,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8", "age1", "age2", "age3", "age4", "age5", "age6", "age7", "age8"]
},
"person_2_gender_identity": {
"header": "",
@ -2246,7 +2250,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8"]
},
"females_in_soft_age_range_in_pregnant_household_person_2_value_check": {
"depends_on": [
@ -2290,7 +2295,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8", "age1", "age2", "age3", "age4", "age5", "age6", "age7", "age8"]
},
"person_2_working_situation": {
"header": "",
@ -2377,27 +2383,13 @@
"translation": "soft_validations.retirement.min.title",
"arguments": [
{
"key": "retirement_age_for_person_2",
"label": false,
"i18n_template": "age"
}
]
},
"informative_text": {
"translation": "soft_validations.retirement.min.hint_text",
"arguments": [
{
"key": "plural_gender_for_person_2",
"label": false,
"i18n_template": "gender"
},
{
"key": "retirement_age_for_person_2",
"label": false,
"key": "age2",
"label": true,
"i18n_template": "age"
}
]
},
"informative_text": {},
"questions": {
"retirement_value_check": {
"check_answer_label": "Retirement confirmation",
@ -2423,7 +2415,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["ecstat2", "age2"]
},
"person_2_over_retirement_value_check": {
"depends_on": [
@ -2481,7 +2474,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["ecstat2", "sex2", "age2"]
},
"person_3_known": {
"header": "You’ve given us the details for 2 people in the household",
@ -2658,7 +2652,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8"]
},
"females_in_soft_age_range_in_pregnant_household_person_3_age_value_check": {
"depends_on": [
@ -2702,7 +2697,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8", "age1", "age2", "age3", "age4", "age5", "age6", "age7", "age8"]
},
"person_3_gender_identity": {
"header": "",
@ -2781,7 +2777,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8"]
},
"females_in_soft_age_range_in_pregnant_household_person_3_value_check": {
"depends_on": [
@ -2825,7 +2822,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8", "age1", "age2", "age3", "age4", "age5", "age6", "age7", "age8"]
},
"person_3_working_situation": {
"header": "",
@ -2912,27 +2910,13 @@
"translation": "soft_validations.retirement.min.title",
"arguments": [
{
"key": "retirement_age_for_person_3",
"label": false,
"i18n_template": "age"
}
]
},
"informative_text": {
"translation": "soft_validations.retirement.min.hint_text",
"arguments": [
{
"key": "plural_gender_for_person_3",
"label": false,
"i18n_template": "gender"
},
{
"key": "retirement_age_for_person_3",
"label": false,
"key": "age3",
"label": true,
"i18n_template": "age"
}
]
},
"informative_text": {},
"questions": {
"retirement_value_check": {
"check_answer_label": "Retirement confirmation",
@ -2958,7 +2942,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["ecstat3", "age3"]
},
"person_3_over_retirement_value_check": {
"depends_on": [
@ -3016,7 +3001,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["ecstat3", "sex3", "age3"]
},
"person_4_known": {
"header": "You’ve given us the details for 3 people in the household",
@ -3190,7 +3176,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8"]
},
"females_in_soft_age_range_in_pregnant_household_person_4_age_value_check": {
"depends_on": [
@ -3234,7 +3221,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8", "age1", "age2", "age3", "age4", "age5", "age6", "age7", "age8"]
},
"person_4_gender_identity": {
"header": "",
@ -3313,7 +3301,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8"]
},
"females_in_soft_age_range_in_pregnant_household_person_4_value_check": {
"depends_on": [
@ -3357,7 +3346,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8", "age1", "age2", "age3", "age4", "age5", "age6", "age7", "age8"]
},
"person_4_working_situation": {
"header": "",
@ -3444,27 +3434,13 @@
"translation": "soft_validations.retirement.min.title",
"arguments": [
{
"key": "retirement_age_for_person_4",
"label": false,
"i18n_template": "age"
}
]
},
"informative_text": {
"translation": "soft_validations.retirement.min.hint_text",
"arguments": [
{
"key": "plural_gender_for_person_4",
"label": false,
"i18n_template": "gender"
},
{
"key": "retirement_age_for_person_4",
"label": false,
"key": "age4",
"label": true,
"i18n_template": "age"
}
]
},
"informative_text": {},
"questions": {
"retirement_value_check": {
"check_answer_label": "Retirement confirmation",
@ -3490,7 +3466,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["ecstat4", "age4"]
},
"person_4_over_retirement_value_check": {
"depends_on": [
@ -3548,7 +3525,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["ecstat4", "sex4", "age4"]
},
"person_5_known": {
"header": "You’ve given us the details for 4 people in the household",
@ -3719,7 +3697,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8"]
},
"females_in_soft_age_range_in_pregnant_household_person_5_age_value_check": {
"depends_on": [
@ -3763,7 +3742,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8", "age1", "age2", "age3", "age4", "age5", "age6", "age7", "age8"]
},
"person_5_gender_identity": {
"header": "",
@ -3842,7 +3822,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8"]
},
"females_in_soft_age_range_in_pregnant_household_person_5_value_check": {
"depends_on": [
@ -3886,7 +3867,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8", "age1", "age2", "age3", "age4", "age5", "age6", "age7", "age8"]
},
"person_5_working_situation": {
"header": "",
@ -3973,27 +3955,13 @@
"translation": "soft_validations.retirement.min.title",
"arguments": [
{
"key": "retirement_age_for_person_5",
"label": false,
"i18n_template": "age"
}
]
},
"informative_text": {
"translation": "soft_validations.retirement.min.hint_text",
"arguments": [
{
"key": "plural_gender_for_person_5",
"label": false,
"i18n_template": "gender"
},
{
"key": "retirement_age_for_person_5",
"label": false,
"key": "age5",
"label": true,
"i18n_template": "age"
}
]
},
"informative_text": {},
"questions": {
"retirement_value_check": {
"check_answer_label": "Retirement confirmation",
@ -4019,7 +3987,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["ecstat5", "age5"]
},
"person_5_over_retirement_value_check": {
"depends_on": [
@ -4077,7 +4046,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["ecstat5", "sex5", "age5"]
},
"person_6_known": {
"header": "You’ve given us the details for 5 people in the household",
@ -4245,7 +4215,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8"]
},
"females_in_soft_age_range_in_pregnant_household_person_6_age_value_check": {
"depends_on": [
@ -4289,7 +4260,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8", "age1", "age2", "age3", "age4", "age5", "age6", "age7", "age8"]
},
"person_6_gender_identity": {
"header": "",
@ -4368,7 +4340,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8"]
},
"females_in_soft_age_range_in_pregnant_household_person_6_value_check": {
"depends_on": [
@ -4412,7 +4385,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8", "age1", "age2", "age3", "age4", "age5", "age6", "age7", "age8"]
},
"person_6_working_situation": {
"header": "",
@ -4499,27 +4473,13 @@
"translation": "soft_validations.retirement.min.title",
"arguments": [
{
"key": "retirement_age_for_person_6",
"label": false,
"i18n_template": "age"
}
]
},
"informative_text": {
"translation": "soft_validations.retirement.min.hint_text",
"arguments": [
{
"key": "plural_gender_for_person_6",
"label": false,
"i18n_template": "gender"
},
{
"key": "retirement_age_for_person_6",
"label": false,
"key": "age6",
"label": true,
"i18n_template": "age"
}
]
},
"informative_text": {},
"questions": {
"retirement_value_check": {
"check_answer_label": "Retirement confirmation",
@ -4545,7 +4505,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["ecstat6", "age6"]
},
"person_6_over_retirement_value_check": {
"depends_on": [
@ -4603,7 +4564,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["ecstat6", "sex6", "age6"]
},
"person_7_known": {
"header": "You’ve given us the details for 6 people in the household",
@ -4768,7 +4730,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8"]
},
"females_in_soft_age_range_in_pregnant_household_person_7_age_value_check": {
"depends_on": [
@ -4812,7 +4775,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8", "age1", "age2", "age3", "age4", "age5", "age6", "age7", "age8"]
},
"person_7_gender_identity": {
"header": "",
@ -4891,7 +4855,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8"]
},
"females_in_soft_age_range_in_pregnant_household_person_7_value_check": {
"depends_on": [
@ -4935,7 +4900,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8", "age1", "age2", "age3", "age4", "age5", "age6", "age7", "age8"]
},
"person_7_working_situation": {
"header": "",
@ -5022,27 +4988,13 @@
"translation": "soft_validations.retirement.min.title",
"arguments": [
{
"key": "retirement_age_for_person_7",
"label": false,
"i18n_template": "age"
}
]
},
"informative_text": {
"translation": "soft_validations.retirement.min.hint_text",
"arguments": [
{
"key": "plural_gender_for_person_7",
"label": false,
"i18n_template": "gender"
},
{
"key": "retirement_age_for_person_7",
"label": false,
"key": "age7",
"label": true,
"i18n_template": "age"
}
]
},
"informative_text": {},
"questions": {
"retirement_value_check": {
"check_answer_label": "Retirement confirmation",
@ -5068,7 +5020,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["ecstat7", "age7"]
},
"person_7_over_retirement_value_check": {
"depends_on": [
@ -5126,7 +5079,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["ecstat7", "sex7", "age7"]
},
"person_8_known": {
"header": "You’ve given us the details for 7 people in the household",
@ -5288,7 +5242,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8"]
},
"females_in_soft_age_range_in_pregnant_household_person_8_age_value_check": {
"depends_on": [
@ -5332,7 +5287,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8", "age1", "age2", "age3", "age4", "age5", "age6", "age7", "age8"]
},
"person_8_gender_identity": {
"header": "",
@ -5411,7 +5367,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8"]
},
"females_in_soft_age_range_in_pregnant_household_person_8_value_check": {
"depends_on": [
@ -5455,7 +5412,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8", "age1", "age2", "age3", "age4", "age5", "age6", "age7", "age8"]
},
"person_8_working_situation": {
"header": "",
@ -5542,27 +5500,13 @@
"translation": "soft_validations.retirement.min.title",
"arguments": [
{
"key": "retirement_age_for_person_8",
"label": false,
"i18n_template": "age"
}
]
},
"informative_text": {
"translation": "soft_validations.retirement.min.hint_text",
"arguments": [
{
"key": "plural_gender_for_person_8",
"label": false,
"i18n_template": "gender"
},
{
"key": "retirement_age_for_person_8",
"label": false,
"key": "age8",
"label": true,
"i18n_template": "age"
}
]
},
"informative_text": {},
"questions": {
"retirement_value_check": {
"check_answer_label": "Retirement confirmation",
@ -5588,7 +5532,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["ecstat8", "age8"]
},
"person_8_over_retirement_value_check": {
"depends_on": [
@ -5646,7 +5591,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["ecstat8", "sex8", "age8"]
}
}
},
@ -5828,7 +5774,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8"]
},
"females_in_soft_age_range_in_pregnant_household_value_check": {
"depends_on": [
@ -5880,7 +5827,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["preg_occ", "sex1", "sex2", "sex3", "sex4", "sex5", "sex6", "sex7", "sex8", "age1", "age2", "age3", "age4", "age5", "age6", "age7", "age8"]
},
"access_needs_exist": {
"header": "",
@ -7351,23 +7299,30 @@
}
],
"title_text": {
"translation": "soft_validations.net_income.title_text"
},
"informative_text": {
"translation": "soft_validations.net_income.hint_text",
"arguments": [
"translation": "soft_validations.net_income.title_text",
"arguments" :[
{
"key": "ecstat1",
"key": "incfreq",
"label": true,
"i18n_template": "ecstat1"
"i18n_template": "incfreq"
},
{
"key": "earnings",
"label": true,
"key": "field_formatted_as_currency",
"arguments_for_key": "earnings",
"i18n_template": "earnings"
}
]
},
"informative_text": {
"translation": "soft_validations.net_income.hint_text",
"arguments": [
{
"key": "net_income_higher_or_lower_text",
"label": false,
"i18n_template": "net_income_higher_or_lower_text"
}
]
},
"questions": {
"net_income_value_check": {
"check_answer_label": "Net income confirmation",
@ -7392,7 +7347,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["incfreq", "earnings", "ecstat1"]
},
"housing_benefit": {
"header": "",
@ -7754,7 +7710,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["chcharge", "is_carehome"]
},
"rent_weekly": {
"header": "Household rent and charges",
@ -8268,8 +8225,8 @@
}
]
},
"header": "This rent is lower than expected for this property type, in this area. Check:",
"hint_text": "<ul><li>the decimal point is not missing (£X.XX)</li><li>the frequency is correct, for example weekly, monthly</li><li>the rent type is correct, for example affordable or social rent</li></ul><p>Are you sure this is correct?</p>",
"header": "Are you sure this is correct?",
"hint_text": "This is lower than we would expect. Check:<ul class=\"govuk-body-l app-panel--interruption\"><li>the decimal point</li><li>the frequency, for example every week or every calendar month</li><li>the rent type is correct, for example affordable or social rent</li></ul>",
"type": "interruption_screen",
"answer_options": {
"0": {
@ -8280,7 +8237,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["brent", "startdate", "la", "beds", "rent_type", "needstype"]
},
"max_rent_value_check": {
"depends_on": [
@ -8312,8 +8270,8 @@
}
]
},
"header": "This rent is higher than expected for this property type, in this area. Check:",
"hint_text": "<ul><li>the decimal point is not missing (£X.XX)</li><li>the frequency is correct, for example weekly, monthly</li><li>the rent type is correct, for example affordable or social rent</li></ul><p>Are you sure this is correct?</p>",
"header": "Are you sure this is correct?",
"hint_text": "This is higher than we would expect. Check:<ul class=\"govuk-body-l app-panel--interruption\"><li>the decimal point</li><li>the frequency, for example every week or every calendar month</li><li>the rent type is correct, for example affordable or social rent</li></ul>",
"type": "interruption_screen",
"answer_options": {
"0": {
@ -8324,7 +8282,8 @@
}
}
}
}
},
"interruption_screen_question_ids": ["brent", "startdate", "la", "beds", "rent_type", "needstype"]
},
"outstanding": {
"header": "",

57
config/locales/en.yml

@ -533,44 +533,54 @@ en:
soft_validations:
net_income:
title_text: "Net income is outside the expected range based on the lead tenant’s working situation"
hint_text: "<p>You told us the lead tenant’s working situation is: <strong>%{ecstat1}</strong>.</p><p>The household income you have entered is <strong>%{earnings}</strong>.</p>"
title_text: "You told us the lead tenant’s income is %{earnings} %{incfreq}."
hint_text: "This is %{net_income_higher_or_lower_text} than we would expect for their working situation."
in_soft_min_range:
message: "Net income is lower than expected based on the lead tenant’s working situation. Are you sure this is correct?"
in_soft_max_range:
message: "Net income is higher than expected based on the lead tenant’s working situation. Are you sure this is correct?"
income:
under_soft_min_for_economic_status: "You said income was %{income}, which is below this working situation's minimum (%{minimum})"
under_soft_min_for_economic_status:
title_text: "You told us income was %{income}."
hint_text: "This is less than we would expect for someone in this working situation."
rent:
outside_range_title: "You told us the rent is %{brent}"
min_hint_text: "The minimum rent expected for this type of property in this local authority is %{soft_min_for_period}."
max_hint_text: "The maximum rent expected for this type of property in this local authority is %{soft_max_for_period}."
purchase_price:
title_text: "You told us the purchase price is %{value}"
hint_text: "The %{min_or_max} purchase price expected for this type of property in this local authority is %{soft_min_or_soft_max}"
hint_text: "This is %{higher_or_lower} than we would expect"
retirement:
min:
title: "You told us this person is under %{age} and retired"
title: "You told us this person is aged %{age} years and retired."
hint_text: "The minimum expected retirement age for %{gender} in England is %{age}."
max:
title: "You told us this person is %{age} or over and not retired"
hint_text: "The minimum expected retirement age for %{gender} in England is %{age}."
extra_borrowing:
title: "The mortgage and deposit are higher than the purchase minus the discount"
title_text: "You told us that the mortgage and deposit total is %{mortgage_and_deposit_total}"
hint_text: "This is higher than the purchase price minus the discount."
pregnancy:
title: "You told us somebody in the household is pregnant"
no_females: "You also told us there are no female tenants living at the property."
females_not_in_soft_age_range: "You also told us that any female tenants living at the property are in the following age ranges:<ul><li>under 16 years old</li><li>over 50 years old</li></ul>"
major_repairs_date:
title_text: "You told us the time between the start of the tenancy and the major repairs completion date is more than 2 years"
title_text: "You told us the property has been vacant for 2 years."
hint_text: "This is higher than we would expect."
void_date:
title_text: "You told us the time between the start of the tenancy and the void date is more than 2 years"
title_text: "You told us that the property has been vacant for more than 2 years."
hint_text: "This is higher than we would expect."
shared_ownership_deposit:
title_text: "Mortgage, deposit and cash discount total should equal %{expected_shared_ownership_deposit_value}"
old_persons_shared_ownership: "At least one buyer should be aged over 64 for Older persons’ shared ownership scheme"
staircase_bought_seems_high: "You said %{percentage}% was bought in this staircasing transaction, which seems high. Are you sure?"
title_text: "You told us that the mortgage, deposit and discount add up to %{mortgage_deposit_and_discount_total}"
old_persons_shared_ownership:
title_text: "You told us the buyer is using the Older Persons Shared Ownership scheme."
hint_text: "At least one buyer must be aged 65 years and over to use this scheme."
staircase_bought_seems_high:
title_text: "You told us that %{percentage}% was bought in this staircasing transaction."
hint_text: "Most staircasing transactions are less than 50%"
monthly_charges_over_soft_max:
title_text: "The amount of monthly charges is high for this type of property and sale type"
title_text: "You told us that the monthly charges were %{mscharge}."
hint_text: "This is higher than we would expect."
student_not_child:
title_text: "You told us this person is a student aged beween 16 and 19"
discounted_sale_value:
@ -579,11 +589,28 @@ en:
care_home_charges:
title_text: "Care home charges should be provided if this is a care home accommodation"
buyer1_livein_wrong_for_ownership_type:
title_text: "You told us that buyer 1 will not live in the property. For %{ownership_scheme} types, the buyer usually lives in the property."
title_text: "You told us that buyer 1 will not live in the property."
hint_text: " For %{ownership_scheme} types, the buyer usually lives in the property."
buyer2_livein_wrong_for_ownership_type:
title_text: "You told us that buyer 2 will not live in the property. For %{ownership_scheme} types, the buyer usually lives in the property."
title_text: "You told us that buyer 2 will not live in the property."
hint_text: " For %{ownership_scheme} types, the buyer usually lives in the property."
percentage_discount_value:
title_text: "You told us that the percentage discount was %{discount}. This seems high for this type of property."
title_text: "You told us that the percentage discount is %{discount}."
hint_text: "This is higher than we would expect."
savings:
title_text: "You told us the buyer’s savings were %{savings}."
hint_text: "This is higher than we would expect."
deposit:
title_text: "You told us the buyer’s deposit was %{deposit} and their savings were %{savings}."
hint_text: "The deposit amount is higher than we would expect for the amount of savings they have."
grant:
title_text: "You told us that the grant amount is %{grant}"
hint_text: "Loans, grants and subsidies are usually between £9,000 and £16,000."
wheelchair:
title_text: "You told us that someone in the household uses a wheelchair."
mortgage:
title_text: "You told us that the mortgage amount is %{mortgage}"
hint_text: "This is more than 5 times the income, which is higher than we would expect."
devise:
two_factor_authentication:

14
config/routes.rb

@ -113,9 +113,12 @@ Rails.application.routes.draw do
get "users/invite", to: "users/account#new"
get "lettings-logs", to: "organisations#lettings_logs"
get "sales-logs", to: "organisations#sales_logs"
get "logs/csv-download", to: "organisations#download_csv"
post "logs/email-csv", to: "organisations#email_csv"
get "logs/csv-confirmation", to: "lettings_logs#csv_confirmation"
get "lettings-logs/csv-download", to: "organisations#download_lettings_csv"
post "lettings-logs/email-csv", to: "organisations#email_lettings_csv"
get "lettings-logs/csv-confirmation", to: "lettings_logs#csv_confirmation"
get "sales-logs/csv-download", to: "organisations#download_sales_csv"
post "sales-logs/email-csv", to: "organisations#email_sales_csv"
get "sales-logs/csv-confirmation", to: "sales_logs#csv_confirmation"
get "schemes", to: "organisations#schemes"
get "stock-owners", to: "organisation_relationships#stock_owners"
get "stock-owners/add", to: "organisation_relationships#add_stock_owner"
@ -140,6 +143,7 @@ Rails.application.routes.draw do
get "confirm-telephone-number"
get "new-organisation-name"
get "new-organisation-address"
get "new-organisation-telephone-number"
get "merge-date"
end
end
@ -197,6 +201,10 @@ Rails.application.routes.draw do
resources :sales_logs, path: "/sales-logs" do
collection do
get "csv-download", to: "sales_logs#download_csv"
post "email-csv", to: "sales_logs#email_csv"
get "csv-confirmation", to: "sales_logs#csv_confirmation"
resources :bulk_upload_sales_logs, path: "bulk-upload-logs" do
collection do
get :start

9
db/migrate/20230504111352_add_new_organisation_address.rb

@ -0,0 +1,9 @@
class AddNewOrganisationAddress < ActiveRecord::Migration[7.0]
def change
change_table :merge_requests, bulk: true do |t|
t.column :new_organisation_address_line1, :string
t.column :new_organisation_address_line2, :string
t.column :new_organisation_postcode, :string
end
end
end

5
db/schema.rb

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2023_05_02_135049) do
ActiveRecord::Schema[7.0].define(version: 2023_05_04_111352) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -372,6 +372,9 @@ ActiveRecord::Schema[7.0].define(version: 2023_05_02_135049) do
t.boolean "telephone_number_correct"
t.string "new_telephone_number"
t.string "new_organisation_name"
t.string "new_organisation_address_line1"
t.string "new_organisation_address_line2"
t.string "new_organisation_postcode"
end
create_table "organisation_relationships", force: :cascade do |t|

3
package.json

@ -14,7 +14,6 @@
"accessible-autocomplete": "^2.0.3",
"babel-loader": "^8.2.3",
"babel-plugin-macros": "^3.1.0",
"chart.js": "^3.6.0",
"copy-webpack-plugin": "^10.2.4",
"core-js": "^3.21.1",
"css-loader": "^6.7.1",
@ -29,7 +28,7 @@
"sass": "^1.49.9",
"sass-loader": "^12.6.0",
"unfetch": "^4.2.0",
"webpack": "^5.70.0",
"webpack": "^5.76.0",
"webpack-cli": "^4.9.2",
"webpack-remove-empty-scripts": "^0.7.3"
},

14
spec/components/check_answers_summary_list_card_component_spec.rb

@ -62,13 +62,21 @@ RSpec.describe CheckAnswersSummaryListCardComponent, type: :component do
context "when in 23/24 collection" do
context "when given a set of questions" do
let(:user) { build(:user) }
let(:log) { build(:lettings_log, :completed, age2: 99, startdate: Time.zone.local(2023, 5, 1)) }
let(:user) { create(:user) }
let(:log) { create(:lettings_log, :completed, age2: 99, startdate: Time.zone.local(2023, 5, 1)) }
let(:subsection_id) { "household_characteristics" }
let(:subsection) { log.form.get_subsection(subsection_id) }
let(:questions) { subsection.applicable_questions(log) }
it "renders a summary list card includinq question numbers for the answers to those questions" do
around do |example|
Timecop.freeze(Time.zone.local(2023, 5, 1))
Singleton.__init__(FormHandler)
example.run
Timecop.return
Singleton.__init__(FormHandler)
end
it "renders a summary list card including question numbers for the answers to those questions" do
expect(rendered).to have_content(questions.first.answer_label(log))
expect(rendered).to have_content("Q32 - Lead tenant’s age")
end

25
spec/factories/form.rb

@ -0,0 +1,25 @@
class FormFixture < Form
attr_accessor :sections, :subsections, :pages, :questions
end
class FormFactory
def initialize(year:, type:)
@year = year
@type = type
end
def with_sections(sections)
@sections = sections
self
end
def build
form = FormFixture.new(nil, @year, [], @type)
@sections.each { |section| section.form = form }
form.sections = @sections
form.subsections = form.sections.flat_map(&:subsections)
form.pages = form.subsections.flat_map(&:pages)
form.questions = form.pages.flat_map(&:questions)
form
end
end

21
spec/factories/page.rb

@ -0,0 +1,21 @@
FactoryBot.define do
factory :page, class: "Form::Page" do
id { "page_id" }
initialize_with { new(id, nil, nil) }
trait :with_question do
transient do
question_id { nil }
question { nil }
end
after :build do |page, evaluator|
page.questions = if (q = evaluator.question)
q.page = page
[q]
else
[build(:question, id: evaluator.question_id, page:)]
end
end
end
end
end

6
spec/factories/question.rb

@ -0,0 +1,6 @@
FactoryBot.define do
factory :question, class: "Form::Question" do
initialize_with { new(id, nil, nil) }
type { "text" }
end
end

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

Loading…
Cancel
Save