Browse Source

Revert to f4e6d6d6bb (#887)

pull/886/head
James Rose 2 years ago committed by GitHub
parent
commit
d3075e4e43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      .github/workflows/production_pipeline.yml
  2. 4
      .github/workflows/staging_pipeline.yml
  3. 7
      .rubocop.yml
  4. 2
      Gemfile
  5. 5
      Gemfile.lock
  6. 8
      app/components/check_answers_summary_list_card_component.html.erb
  7. 10
      app/components/check_answers_summary_list_card_component.rb
  8. 6
      app/components/log_summary_component.html.erb
  9. 4
      app/controllers/bulk_upload_controller.rb
  10. 99
      app/controllers/form_controller.rb
  11. 113
      app/controllers/lettings_logs_controller.rb
  12. 73
      app/controllers/logs_controller.rb
  13. 23
      app/controllers/modules/lettings_logs_filter.rb
  14. 21
      app/controllers/modules/logs_filter.rb
  15. 8
      app/controllers/modules/search_filter.rb
  16. 62
      app/controllers/organisations_controller.rb
  17. 46
      app/controllers/sales_logs_controller.rb
  18. 4
      app/helpers/check_answers_helper.rb
  19. 8
      app/helpers/filters_helper.rb
  20. 35
      app/helpers/navigation_items_helper.rb
  21. 32
      app/helpers/tasklist_helper.rb
  22. 21
      app/jobs/email_csv_job.rb
  23. 11
      app/mailers/csv_download_mailer.rb
  24. 37
      app/mailers/notify_mailer.rb
  25. 158
      app/models/form.rb
  26. 6
      app/models/form/common/pages/created_by.rb
  27. 6
      app/models/form/common/pages/organisation.rb
  28. 12
      app/models/form/common/questions/created_by_id.rb
  29. 8
      app/models/form/common/questions/owning_organisation_id.rb
  30. 4
      app/models/form/lettings/pages/location.rb
  31. 4
      app/models/form/lettings/pages/needs_type.rb
  32. 4
      app/models/form/lettings/pages/property_reference.rb
  33. 4
      app/models/form/lettings/pages/renewal.rb
  34. 6
      app/models/form/lettings/pages/rent_type.rb
  35. 4
      app/models/form/lettings/pages/scheme.rb
  36. 4
      app/models/form/lettings/pages/tenancy_start_date.rb
  37. 4
      app/models/form/lettings/pages/tenant_code.rb
  38. 2
      app/models/form/lettings/questions/irproduct_other.rb
  39. 2
      app/models/form/lettings/questions/location_id.rb
  40. 2
      app/models/form/lettings/questions/needs_type.rb
  41. 2
      app/models/form/lettings/questions/property_reference.rb
  42. 2
      app/models/form/lettings/questions/renewal.rb
  43. 2
      app/models/form/lettings/questions/rent_type.rb
  44. 2
      app/models/form/lettings/questions/scheme_id.rb
  45. 2
      app/models/form/lettings/questions/tenancy_start_date.rb
  46. 2
      app/models/form/lettings/questions/tenant_code.rb
  47. 4
      app/models/form/lettings/sections/setup.rb
  48. 37
      app/models/form/lettings/subsections/setup.rb
  49. 4
      app/models/form/page.rb
  50. 98
      app/models/form/question.rb
  51. 15
      app/models/form/sales/pages/purchaser_code.rb
  52. 15
      app/models/form/sales/pages/sale_date.rb
  53. 18
      app/models/form/sales/pages/shared_ownership_type.rb
  54. 12
      app/models/form/sales/questions/purchaser_code.rb
  55. 10
      app/models/form/sales/questions/sale_date.rb
  56. 22
      app/models/form/sales/questions/shared_ownership_type.rb
  57. 10
      app/models/form/sales/sections/setup.rb
  58. 18
      app/models/form/sales/subsections/setup.rb
  59. 37
      app/models/form/setup/subsections/setup.rb
  60. 28
      app/models/form/subsection.rb
  61. 41
      app/models/form_handler.rb
  62. 94
      app/models/lettings_log.rb
  63. 73
      app/models/log.rb
  64. 6
      app/models/organisation.rb
  65. 2
      app/models/rent_period.rb
  66. 45
      app/models/sales_log.rb
  67. 14
      app/models/user.rb
  68. 8
      app/models/validations/date_validations.rb
  69. 8
      app/services/csv/lettings_log_csv_service.rb
  70. 22
      app/services/filter_service.rb
  71. 6
      app/services/storage/s3_service.rb
  72. 12
      app/views/form/_check_answers_summary_list.html.erb
  73. 4
      app/views/form/_checkbox_question.html.erb
  74. 2
      app/views/form/_numeric_question.html.erb
  75. 2
      app/views/form/_radio_question.html.erb
  76. 6
      app/views/form/_select_question.html.erb
  77. 24
      app/views/form/check_answers.html.erb
  78. 12
      app/views/form/page.html.erb
  79. 8
      app/views/form/review.html.erb
  80. 4
      app/views/layouts/application.html.erb
  81. 2
      app/views/logs/_log_filters.erb
  82. 5
      app/views/logs/_log_list.html.erb
  83. 8
      app/views/logs/_tasklist.html.erb
  84. 0
      app/views/logs/bulk_upload.html.erb
  85. 15
      app/views/logs/csv_confirmation.html.erb
  86. 16
      app/views/logs/download_csv.html.erb
  87. 18
      app/views/logs/edit.html.erb
  88. 9
      app/views/logs/index.html.erb
  89. 2
      app/views/organisations/_organisation_list.html.erb
  90. 4
      app/views/organisations/logs.html.erb
  91. 2
      config/application.rb
  92. 6
      config/initializers/feature_toggle.rb
  93. 2
      config/initializers/rack_attack.rb
  94. 11
      config/initializers/sidekiq.rb
  95. 40
      config/locales/en.yml
  96. 28
      config/routes.rb
  97. 12
      db/migrate/20220826093411_add_sales_log.rb
  98. 5
      db/migrate/20220915093559_add_purchid.rb
  99. 5
      db/migrate/20220916110129_add_shared_ownership_type.rb
  100. 5
      db/migrate/20220916125704_add_ownership_scheme_to_sales_log.rb
  101. Some files were not shown because too many files have changed in this diff Show More

4
.github/workflows/production_pipeline.yml

@ -238,6 +238,8 @@ jobs:
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
IMPORT_PAAS_INSTANCE: ${{ secrets.IMPORT_PAAS_INSTANCE }}
EXPORT_PAAS_INSTANCE: ${{ secrets.EXPORT_PAAS_INSTANCE }}
S3_CONFIG: ${{ secrets.S3_CONFIG }}
CSV_DOWNLOAD_PAAS_INSTANCE: ${{ secrets.CSV_DOWNLOAD_PAAS_INSTANCE }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
run: |
cf api $CF_API_ENDPOINT
@ -248,5 +250,7 @@ jobs:
cf set-env $APP_NAME RAILS_MASTER_KEY $RAILS_MASTER_KEY
cf set-env $APP_NAME IMPORT_PAAS_INSTANCE $IMPORT_PAAS_INSTANCE
cf set-env $APP_NAME EXPORT_PAAS_INSTANCE $EXPORT_PAAS_INSTANCE
cf set-env $APP_NAME S3_CONFIG $S3_CONFIG
cf set-env $APP_NAME CSV_DOWNLOAD_PAAS_INSTANCE $CSV_DOWNLOAD_PAAS_INSTANCE
cf set-env $APP_NAME SENTRY_DSN $SENTRY_DSN
cf push $APP_NAME --strategy rolling

4
.github/workflows/staging_pipeline.yml

@ -214,6 +214,8 @@ jobs:
RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
IMPORT_PAAS_INSTANCE: ${{ secrets.IMPORT_PAAS_INSTANCE }}
EXPORT_PAAS_INSTANCE: ${{ secrets.EXPORT_PAAS_INSTANCE }}
S3_CONFIG: ${{ secrets.S3_CONFIG }}
CSV_DOWNLOAD_PAAS_INSTANCE: ${{ secrets.CSV_DOWNLOAD_PAAS_INSTANCE }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
run: |
cf api $CF_API_ENDPOINT
@ -226,5 +228,7 @@ jobs:
cf set-env $APP_NAME RAILS_MASTER_KEY $RAILS_MASTER_KEY
cf set-env $APP_NAME IMPORT_PAAS_INSTANCE $IMPORT_PAAS_INSTANCE
cf set-env $APP_NAME EXPORT_PAAS_INSTANCE $EXPORT_PAAS_INSTANCE
cf set-env $APP_NAME S3_CONFIG $S3_CONFIG
cf set-env $APP_NAME CSV_DOWNLOAD_PAAS_INSTANCE $CSV_DOWNLOAD_PAAS_INSTANCE
cf set-env $APP_NAME SENTRY_DSN $SENTRY_DSN
cf push $APP_NAME --strategy rolling

7
.rubocop.yml

@ -20,3 +20,10 @@ AllCops:
Style/Documentation:
Enabled: false
Rails/UnknownEnv:
Environments:
- production
- staging
- development
- test

2
Gemfile

@ -58,6 +58,8 @@ gem "sentry-ruby"
gem "possessive"
# Strip whitespace from active record attributes
gem "auto_strip_attributes"
# Use sidekiq for background processing
gem "sidekiq"
group :development, :test do
# Check gems for known vulnerabilities

5
Gemfile.lock

@ -381,6 +381,10 @@ GEM
sentry-ruby (~> 5.4.2)
sentry-ruby (5.4.2)
concurrent-ruby (~> 1.0, >= 1.0.2)
sidekiq (6.5.4)
connection_pool (>= 2.2.2)
rack (~> 2.0)
redis (>= 4.5.0)
simplecov (0.21.2)
docile (~> 1.1)
simplecov-html (~> 0.11)
@ -470,6 +474,7 @@ DEPENDENCIES
selenium-webdriver
sentry-rails
sentry-ruby
sidekiq
simplecov
stimulus-rails
timecop (~> 0.9.4)

8
app/components/check_answers_summary_list_card_component.html.erb

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

10
app/components/check_answers_summary_list_card_component.rb

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

6
app/components/log_summary_component.html.erb

@ -3,11 +3,11 @@
<div class="govuk-grid-column-one-half">
<header class="app-log-summary__header">
<h2 class="app-log-summary__title">
<%= govuk_link_to lettings_log_path(log) do %>
<%= govuk_link_to log.lettings? ? lettings_log_path(log) : sales_log_path(log) do %>
<span class="govuk-visually-hidden">Log </span><%= log.id %>
<% end %>
</h2>
<% if log.tenancycode? or log.propcode? %>
<% if log.lettings? && (log.tenancycode? or log.propcode?) %>
<dl class="app-metadata app-metadata--inline">
<% if log.tenancycode? %>
<div class="app-metadata__item">
@ -25,7 +25,7 @@
<% end %>
</header>
<% if log.needstype? or log.startdate? %>
<% if log.is_a?(LettingsLog) && (log.needstype? or log.startdate?) %>
<p class="govuk-body govuk-!-margin-bottom-2">
<% if log.needstype? %>
<%= log.is_general_needs? ? "General needs" : "Supported housing" %><br>

4
app/controllers/bulk_upload_controller.rb

@ -3,7 +3,7 @@ class BulkUploadController < ApplicationController
def show
@bulk_upload = BulkUpload.new(nil, nil)
render "lettings_logs/bulk_upload"
render "logs/bulk_upload"
end
def bulk_upload
@ -12,7 +12,7 @@ class BulkUploadController < ApplicationController
@bulk_upload = BulkUpload.new(file, content_type)
@bulk_upload.process(current_user)
if @bulk_upload.errors.present?
render "lettings_logs/bulk_upload", status: :unprocessable_entity
render "logs/bulk_upload", status: :unprocessable_entity
else
redirect_to(lettings_logs_path)
end

99
app/controllers/form_controller.rb

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

113
app/controllers/lettings_logs_controller.rb

@ -1,55 +1,33 @@
class LettingsLogsController < ApplicationController
include Pagy::Backend
include Modules::LettingsLogsFilter
include Modules::SearchFilter
skip_before_action :verify_authenticity_token, if: :json_api_request?
before_action :authenticate, if: :json_api_request?
before_action :authenticate_user!, unless: :json_api_request?
class LettingsLogsController < LogsController
before_action :find_resource, except: %i[create index edit]
before_action :session_filters, if: :current_user
before_action :set_session_filters, if: :current_user
def index
set_session_filters
all_logs = current_user.lettings_logs
unpaginated_filtered_logs = filtered_lettings_logs(filtered_collection(all_logs, search_term))
respond_to do |format|
format.html do
@pagy, @lettings_logs = pagy(unpaginated_filtered_logs)
all_logs = current_user.lettings_logs
unpaginated_filtered_logs = filtered_logs(all_logs, search_term, @session_filters)
@search_term = search_term
@pagy, @logs = pagy(unpaginated_filtered_logs)
@searched = search_term.presence
@total_count = all_logs.size
end
format.csv do
send_data byte_order_mark + unpaginated_filtered_logs.to_csv(current_user), filename: "logs-#{Time.zone.now}.csv"
render "logs/index"
end
end
end
def create
lettings_log = LettingsLog.new(lettings_log_params)
respond_to do |format|
format.html do
lettings_log.save!
redirect_to lettings_log_url(lettings_log)
end
format.json do
if lettings_log.save
render json: lettings_log, status: :created
else
render json: { errors: lettings_log.errors.messages }, status: :unprocessable_entity
end
end
end
super { LettingsLog.new(log_params) }
end
def update
if @lettings_log
if @lettings_log.update(api_lettings_log_params)
render json: @lettings_log, status: :ok
if @log
if @log.update(api_log_params)
render json: @log, status: :ok
else
render json: { errors: @lettings_log.errors.messages }, status: :unprocessable_entity
render json: { errors: @log.errors.messages }, status: :unprocessable_entity
end
else
render_not_found_json("Log", params[:id])
@ -61,8 +39,8 @@ class LettingsLogsController < ApplicationController
# We don't have a dedicated non-editable show view
format.html { edit }
format.json do
if @lettings_log
render json: @lettings_log, status: :ok
if @log
render json: @log, status: :ok
else
render_not_found_json("Log", params[:id])
end
@ -71,68 +49,51 @@ class LettingsLogsController < ApplicationController
end
def edit
@lettings_log = current_user.lettings_logs.find_by(id: params[:id])
if @lettings_log
render :edit, locals: { current_user: }
@log = current_user.lettings_logs.find_by(id: params[:id])
if @log
render "logs/edit", locals: { current_user: }
else
render_not_found
end
end
def destroy
if @lettings_log
if @lettings_log.delete
if @log
if @log.delete
head :no_content
else
render json: { errors: @lettings_log.errors.messages }, status: :unprocessable_entity
render json: { errors: @log.errors.messages }, status: :unprocessable_entity
end
else
render_not_found_json("Log", params[:id])
end
end
private
API_ACTIONS = %w[create show update destroy].freeze
def download_csv
unpaginated_filtered_logs = filtered_logs(current_user.lettings_logs, search_term, @session_filters)
def search_term
params["search"]
render "download_csv", locals: { search_term:, count: unpaginated_filtered_logs.size, post_path: email_csv_lettings_logs_path }
end
def json_api_request?
API_ACTIONS.include?(request["action"]) && request.format.json?
def email_csv
all_orgs = params["organisation_select"] == "all"
EmailCsvJob.perform_later(current_user, search_term, @session_filters, all_orgs)
redirect_to csv_confirmation_lettings_logs_path
end
def authenticate
http_basic_authenticate_or_request_with name: ENV["API_USER"], password: ENV["API_KEY"]
end
def csv_confirmation; end
def lettings_log_params
if current_user && !current_user.support?
org_params.merge(api_lettings_log_params)
else
api_lettings_log_params
end
end
private
def org_params
{
"owning_organisation_id" => current_user.organisation.id,
"managing_organisation_id" => current_user.organisation.id,
"created_by_id" => current_user.id,
}
def permitted_log_params
params.require(:lettings_log).permit(LettingsLog.editable_fields)
end
def api_lettings_log_params
return {} unless params[:lettings_log]
permitted = params.require(:lettings_log).permit(LettingsLog.editable_fields)
owning_id = permitted["owning_organisation_id"]
permitted["owning_organisation"] = Organisation.find(owning_id) if owning_id
permitted
def find_resource
@log = LettingsLog.find_by(id: params[:id])
end
def find_resource
@lettings_log = LettingsLog.find_by(id: params[:id])
def post_create_redirect_url(log)
lettings_log_url(log)
end
end

73
app/controllers/logs_controller.rb

@ -0,0 +1,73 @@
class LogsController < ApplicationController
include Pagy::Backend
include Modules::LogsFilter
include Modules::SearchFilter
skip_before_action :verify_authenticity_token, if: :json_api_request?
before_action :authenticate, if: :json_api_request?
before_action :authenticate_user!, unless: :json_api_request?
private
def create
log = yield
raise "Caller must pass a block that implements model creation" if log.blank?
respond_to do |format|
format.html do
log.save!
redirect_to post_create_redirect_url(log)
end
format.json do
if log.save
render json: log, status: :created
else
render json: { errors: log.errors.messages }, status: :unprocessable_entity
end
end
end
end
def post_create_redirect_url
raise "implement in sub class"
end
API_ACTIONS = %w[create show update destroy].freeze
def json_api_request?
API_ACTIONS.include?(request["action"]) && request.format.json?
end
def authenticate
http_basic_authenticate_or_request_with name: ENV["API_USER"], password: ENV["API_KEY"]
end
def log_params
if current_user && !current_user.support?
org_params.merge(api_log_params)
else
api_log_params
end
end
def api_log_params
return {} unless params[:lettings_log] || params[:sales_log]
permitted = permitted_log_params
owning_id = permitted["owning_organisation_id"]
permitted["owning_organisation"] = Organisation.find(owning_id) if owning_id
permitted
end
def org_params
{
"owning_organisation_id" => current_user.organisation.id,
"managing_organisation_id" => current_user.organisation.id,
"created_by_id" => current_user.id,
}
end
def search_term
params["search"]
end
end

23
app/controllers/modules/lettings_logs_filter.rb

@ -1,23 +0,0 @@
module Modules::LettingsLogsFilter
def filtered_lettings_logs(logs)
if session[:lettings_logs_filters].present?
filters = JSON.parse(session[:lettings_logs_filters])
filters.each do |category, values|
next if Array(values).reject(&:empty?).blank?
next if category == "organisation" && params["organisation_select"] == "all"
logs = logs.public_send("filter_by_#{category}", values, current_user)
end
end
logs = logs.order(created_at: :desc)
current_user.support? ? logs.all.includes(:owning_organisation, :managing_organisation) : logs
end
def set_session_filters(specific_org: false)
new_filters = session[:lettings_logs_filters].present? ? JSON.parse(session[:lettings_logs_filters]) : {}
current_user.lettings_logs_filters(specific_org:).each { |filter| new_filters[filter] = params[filter] if params[filter].present? }
new_filters = new_filters.except("organisation") if params["organisation_select"] == "all"
session[:lettings_logs_filters] = new_filters.to_json
end
end

21
app/controllers/modules/logs_filter.rb

@ -0,0 +1,21 @@
module Modules::LogsFilter
def filtered_logs(logs, search_term, filters)
all_orgs = params["organisation_select"] == "all"
FilterService.filter_logs(logs, search_term, filters, all_orgs, current_user)
end
def load_session_filters(specific_org: false)
current_filters = session[:logs_filters]
new_filters = current_filters.present? ? JSON.parse(current_filters) : {}
current_user.logs_filters(specific_org:).each { |filter| new_filters[filter] = params[filter] if params[filter].present? }
params["organisation_select"] == "all" ? new_filters.except("organisation") : new_filters
end
def session_filters(specific_org: false)
@session_filters ||= load_session_filters(specific_org:)
end
def set_session_filters
session[:logs_filters] = @session_filters.to_json
end
end

8
app/controllers/modules/search_filter.rb

@ -1,13 +1,9 @@
module Modules::SearchFilter
def filtered_collection(base_collection, search_term = nil)
if search_term.present?
base_collection.search_by(search_term)
else
base_collection
end
FilterService.filter_by_search(base_collection, search_term)
end
def filtered_users(base_collection, search_term = nil)
filtered_collection(base_collection, search_term).includes(:organisation)
FilterService.filter_by_search(base_collection, search_term).includes(:organisation)
end
end

62
app/controllers/organisations_controller.rb

@ -1,11 +1,13 @@
class OrganisationsController < ApplicationController
include Pagy::Backend
include Modules::LettingsLogsFilter
include Modules::LogsFilter
include Modules::SearchFilter
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? }
before_action :set_session_filters, if: -> { current_user.support? }
def index
redirect_to organisation_path(current_user.organisation) unless current_user.support?
@ -87,27 +89,49 @@ class OrganisationsController < ApplicationController
end
end
def logs
if current_user.support?
set_session_filters(specific_org: true)
def lettings_logs
organisation_logs = LettingsLog.where(owning_organisation_id: @organisation.id)
unpaginated_filtered_logs = filtered_logs(organisation_logs, search_term, @session_filters)
organisation_logs = LettingsLog.all.where(owning_organisation_id: @organisation.id)
unpaginated_filtered_logs = filtered_lettings_logs(filtered_collection(organisation_logs, search_term))
respond_to do |format|
format.html do
@search_term = search_term
@pagy, @logs = pagy(unpaginated_filtered_logs)
@searched = search_term.presence
@total_count = organisation_logs.size
render "logs", layout: "application"
end
end
end
respond_to do |format|
format.html do
@pagy, @lettings_logs = pagy(unpaginated_filtered_logs)
@searched = search_term.presence
@total_count = organisation_logs.size
render "logs", layout: "application"
end
def download_csv
organisation_logs = LettingsLog.all.where(owning_organisation_id: @organisation.id)
unpaginated_filtered_logs = filtered_logs(organisation_logs, search_term, @session_filters)
format.csv do
send_data byte_order_mark + unpaginated_filtered_logs.to_csv, filename: "logs-#{@organisation.name}-#{Time.zone.now}.csv"
end
render "logs/download_csv", locals: { search_term:, count: unpaginated_filtered_logs.size, post_path: logs_email_csv_organisation_path }
end
def email_csv
EmailCsvJob.perform_later(current_user, search_term, @session_filters, false, @organisation)
redirect_to logs_csv_confirmation_organisation_path
end
def sales_logs
organisation_logs = SalesLog.where(owning_organisation_id: @organisation.id)
unpaginated_filtered_logs = filtered_logs(organisation_logs, search_term, @session_filters)
respond_to do |format|
format.html do
@search_term = search_term
@pagy, @logs = pagy(unpaginated_filtered_logs)
@searched = search_term.presence
@total_count = organisation_logs.size
render "logs", layout: "application"
end
format.csv do
send_data byte_order_mark + unpaginated_filtered_logs.to_csv, filename: "sales-logs-#{@organisation.name}-#{Time.zone.now}.csv"
end
else
redirect_to(lettings_logs_path)
end
end
@ -122,7 +146,7 @@ private
end
def authenticate_scope!
if %w[create new].include? action_name
if %w[create new lettings_logs download_csv email_csv].include? action_name
head :unauthorized and return unless current_user.support?
elsif current_user.organisation != @organisation && !current_user.support?
render_not_found

46
app/controllers/sales_logs_controller.rb

@ -0,0 +1,46 @@
class SalesLogsController < LogsController
before_action :session_filters, if: :current_user
before_action :set_session_filters, if: :current_user
def create
super { SalesLog.new(log_params) }
end
def index
respond_to do |format|
format.html do
all_logs = current_user.sales_logs
unpaginated_filtered_logs = filtered_logs(all_logs, search_term, @session_filters)
@search_term = search_term
@pagy, @logs = pagy(unpaginated_filtered_logs)
@searched = search_term.presence
@total_count = all_logs.size
render "logs/index"
end
end
end
def show
respond_to do |format|
format.html { edit }
end
end
def edit
@log = current_user.sales_logs.find_by(id: params[:id])
if @log
render "logs/edit", locals: { current_user: }
else
render_not_found
end
end
def post_create_redirect_url(log)
sales_log_url(log)
end
def permitted_log_params
params.require(:sales_log).permit(SalesLog.editable_fields)
end
end

4
app/helpers/check_answers_helper.rb

@ -28,6 +28,10 @@ module CheckAnswersHelper
subsection.applicable_questions(lettings_log).map(&:check_answers_card_number).compact.length.positive?
end
def next_incomplete_section_path(log, redirect_path)
"#{log.class.name.underscore}_#{redirect_path.underscore.tr('/', '_')}_path"
end
private
def answered_questions_count(subsection, lettings_log, current_user)

8
app/helpers/filters_helper.rb

@ -1,8 +1,8 @@
module FiltersHelper
def filter_selected?(filter, value)
return false unless session[:lettings_logs_filters]
return false unless session[:logs_filters]
selected_filters = JSON.parse(session[:lettings_logs_filters])
selected_filters = JSON.parse(session[:logs_filters])
return true if selected_filters.blank? && filter == "user" && value == :all
return true if !selected_filters.key?("organisation") && filter == "organisation_select" && value == :all
return true if selected_filters["organisation"].present? && filter == "organisation_select" && value == :specific_org
@ -18,8 +18,8 @@ module FiltersHelper
end
def selected_option(filter)
return false unless session[:lettings_logs_filters]
return false unless session[:logs_filters]
JSON.parse(session[:lettings_logs_filters])[filter] || ""
JSON.parse(session[:logs_filters])[filter] || ""
end
end

35
app/helpers/navigation_items_helper.rb

@ -6,39 +6,44 @@ module NavigationItemsHelper
[
NavigationItem.new("Organisations", organisations_path, organisations_current?(path)),
NavigationItem.new("Users", "/users", users_current?(path)),
NavigationItem.new("Logs", lettings_logs_path, logs_current?(path)),
NavigationItem.new("Lettings logs", lettings_logs_path, lettings_logs_current?(path)),
FeatureToggle.sales_log_enabled? ? NavigationItem.new("Sales logs", sales_logs_path, sales_logs_current?(path)) : nil,
NavigationItem.new("Schemes", "/schemes", supported_housing_schemes_current?(path)),
]
].compact
elsif current_user.data_coordinator? && current_user.organisation.holds_own_stock?
[
NavigationItem.new("Logs", lettings_logs_path, logs_current?(path)),
NavigationItem.new("Lettings logs", lettings_logs_path, lettings_logs_current?(path)),
FeatureToggle.sales_log_enabled? ? NavigationItem.new("Sales logs", sales_logs_path, sales_logs_current?(path)) : nil,
NavigationItem.new("Schemes", "/schemes", subnav_supported_housing_schemes_path?(path)),
NavigationItem.new("Users", users_organisation_path(current_user.organisation), subnav_users_path?(path)),
NavigationItem.new("About your organisation", "/organisations/#{current_user.organisation.id}", subnav_details_path?(path)),
]
].compact
else
[
NavigationItem.new("Logs", lettings_logs_path, logs_current?(path)),
NavigationItem.new("Lettings logs", lettings_logs_path, lettings_logs_current?(path)),
FeatureToggle.sales_log_enabled? ? NavigationItem.new("Sales logs", sales_logs_path, sales_logs_current?(path)) : nil,
NavigationItem.new("Users", users_organisation_path(current_user.organisation), subnav_users_path?(path)),
NavigationItem.new("About your organisation", "/organisations/#{current_user.organisation.id}", subnav_details_path?(path)),
]
].compact
end
end
def secondary_items(path, current_organisation_id)
if current_user.organisation.holds_own_stock?
[
NavigationItem.new("Logs", "/organisations/#{current_organisation_id}/logs", subnav_logs_path?(path)),
NavigationItem.new("Lettings logs", "/organisations/#{current_organisation_id}/lettings-logs", subnav_logs_path?(path)),
FeatureToggle.sales_log_enabled? ? NavigationItem.new("Sales logs", "/organisations/#{current_organisation_id}/sales-logs", sales_logs_current?(path)) : nil,
NavigationItem.new("Schemes", "/organisations/#{current_organisation_id}/schemes", subnav_supported_housing_schemes_path?(path)),
NavigationItem.new("Users", "/organisations/#{current_organisation_id}/users", subnav_users_path?(path)),
NavigationItem.new("About this organisation", "/organisations/#{current_organisation_id}", subnav_details_path?(path)),
]
].compact
else
[
NavigationItem.new("Logs", "/organisations/#{current_organisation_id}/logs", subnav_logs_path?(path)),
NavigationItem.new("Lettings logs", "/organisations/#{current_organisation_id}/lettings-logs", subnav_logs_path?(path)),
FeatureToggle.sales_log_enabled? ? NavigationItem.new("Sales logs", "/organisations/#{current_organisation_id}/sales-logs", sales_logs_current?(path)) : nil,
NavigationItem.new("Users", "/organisations/#{current_organisation_id}/users", subnav_users_path?(path)),
NavigationItem.new("About this organisation", "/organisations/#{current_organisation_id}", subnav_details_path?(path)),
]
].compact
end
end
@ -51,8 +56,12 @@ module NavigationItemsHelper
private
def logs_current?(path)
path == "/logs"
def lettings_logs_current?(path)
path == "/lettings-logs"
end
def sales_logs_current?(path)
path == "/sales-logs"
end
def users_current?(path)
@ -76,7 +85,7 @@ private
end
def subnav_logs_path?(path)
path.include?("/organisations") && path.include?("/logs")
path.include?("/organisations") && path.include?("/lettings-logs")
end
def subnav_details_path?(path)

32
app/helpers/tasklist_helper.rb

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

21
app/jobs/email_csv_job.rb

@ -0,0 +1,21 @@
class EmailCsvJob < ApplicationJob
queue_as :default
BYTE_ORDER_MARK = "\uFEFF".freeze # Required to ensure Excel always reads CSV as UTF-8
EXPIRATION_TIME = 3.hours.to_i
def perform(user, search_term = nil, filters = {}, all_orgs = false, organisation = nil) # rubocop:disable Style/OptionalBooleanParameter - sidekiq can't serialise named params
unfiltered_logs = organisation.present? && user.support? ? LettingsLog.where(owning_organisation_id: organisation.id) : user.lettings_logs
filtered_logs = FilterService.filter_logs(unfiltered_logs, search_term, filters, all_orgs, user)
filename = organisation.present? ? "logs-#{organisation.name}-#{Time.zone.now}.csv" : "logs-#{Time.zone.now}.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))
url = storage_service.get_presigned_url(filename, EXPIRATION_TIME)
CsvDownloadMailer.new.send_csv_download_mail(user, url, EXPIRATION_TIME)
end
end

11
app/mailers/csv_download_mailer.rb

@ -0,0 +1,11 @@
class CsvDownloadMailer < NotifyMailer
CSV_DOWNLOAD_TEMPLATE_ID = "7890e3b9-8c0d-4d08-bafe-427fd7cd95bf".freeze
def send_csv_download_mail(user, link, duration)
send_email(
user.email,
CSV_DOWNLOAD_TEMPLATE_ID,
{ name: user.name, link:, duration: ActiveSupport::Duration.build(duration).inspect },
)
end
end

37
app/mailers/notify_mailer.rb

@ -0,0 +1,37 @@
class NotifyMailer
require "notifications/client"
def notify_client
@notify_client ||= ::Notifications::Client.new(ENV["GOVUK_NOTIFY_API_KEY"])
end
def send_email(email, template_id, personalisation)
return true if intercept_send?(email)
notify_client.send_email(
email_address: email,
template_id:,
personalisation:,
)
end
def personalisation(record, token, url, username: false)
{
name: record.name || record.email,
email: username || record.email,
organisation: record.respond_to?(:organisation) ? record.organisation.name : "",
link: "#{url}#{token}",
}
end
def intercept_send?(email)
return false unless email_allowlist
email_domain = email.split("@").last.downcase
!(Rails.env.production? || Rails.env.test?) && email_allowlist.exclude?(email_domain)
end
def email_allowlist
Rails.application.credentials[:email_allowlist]
end
end

158
app/models/form.rb

@ -3,22 +3,38 @@ class Form
:start_date, :end_date, :type, :name, :setup_definition,
:setup_sections, :form_sections
include Form::Setup
def initialize(form_path, name)
raise "No form definition file exists for given year".freeze unless File.exist?(form_path)
@name = name
@setup_sections = [Form::Setup::Sections::Setup.new(nil, nil, self)]
@form_definition = JSON.parse(File.open(form_path).read)
@form_sections = form_definition["sections"].map { |id, s| Form::Section.new(id, s, self) }
@type = form_definition["form_type"]
@sections = setup_sections + form_sections
@subsections = sections.flat_map(&:subsections)
@pages = subsections.flat_map(&:pages)
@questions = pages.flat_map(&:questions)
@start_date = Time.iso8601(form_definition["start_date"])
@end_date = Time.iso8601(form_definition["end_date"])
def initialize(form_path, start_year = "", sections_in_form = [], type = "lettings")
if type == "sales"
@setup_sections = [Form::Sales::Sections::Setup.new(nil, nil, self)]
@form_sections = sections_in_form.map { |sec| sec.new(nil, nil, self) }
@type = "sales"
@sections = setup_sections + form_sections
@subsections = sections.flat_map(&:subsections)
@pages = subsections.flat_map(&:pages)
@questions = pages.flat_map(&:questions)
@start_date = Time.zone.local(start_year, 4, 1)
@end_date = Time.zone.local(start_year + 1, 7, 1)
@form_definition = {
"form_type" => type,
"start_date" => start_date,
"end_date" => end_date,
"sections" => sections,
}
else
raise "No form definition file exists for given year".freeze unless File.exist?(form_path)
@setup_sections = [Form::Lettings::Sections::Setup.new(nil, nil, self)]
@form_definition = JSON.parse(File.open(form_path).read)
@form_sections = form_definition["sections"].map { |id, s| Form::Section.new(id, s, self) }
@type = form_definition["form_type"]
@sections = setup_sections + form_sections
@subsections = sections.flat_map(&:subsections)
@pages = subsections.flat_map(&:pages)
@questions = pages.flat_map(&:questions)
@start_date = Time.iso8601(form_definition["start_date"])
@end_date = Time.iso8601(form_definition["end_date"])
end
@name = "#{start_date.year}_#{end_date.year}_#{type}"
end
def get_subsection(id)
@ -29,9 +45,9 @@ class Form
pages.find { |p| p.id == id.to_s.underscore }
end
def get_question(id, lettings_log, current_user = nil)
def get_question(id, log, current_user = nil)
all_questions = questions.select { |q| q.id == id.to_s.underscore }
routed_question = all_questions.find { |q| q.page.routed_to?(lettings_log, current_user) } if lettings_log
routed_question = all_questions.find { |q| q.page.routed_to?(log, current_user) } if log
routed_question || all_questions[0]
end
@ -39,47 +55,51 @@ class Form
subsections.find { |s| s.pages.find { |p| p.id == page.id } }
end
def next_page(page, lettings_log, current_user)
def next_page(page, log, current_user)
page_ids = subsection_for_page(page).pages.map(&:id)
page_index = page_ids.index(page.id)
page_id = if page.id.include?("value_check") && lettings_log[page.questions[0].id] == 1 && page.routed_to?(lettings_log, current_user)
previous_page(page_ids, page_index, lettings_log, current_user)
page_id = if page.id.include?("value_check") && log[page.questions[0].id] == 1 && page.routed_to?(log, current_user)
previous_page(page_ids, page_index, log, current_user)
else
page_ids[page_index + 1]
end
nxt_page = get_page(page_id)
return :check_answers if nxt_page.nil?
return nxt_page.id if nxt_page.routed_to?(lettings_log, current_user)
return nxt_page.id if nxt_page.routed_to?(log, current_user)
next_page(nxt_page, lettings_log, current_user)
next_page(nxt_page, log, current_user)
end
def next_page_redirect_path(page, lettings_log, current_user)
nxt_page = next_page(page, lettings_log, current_user)
def next_page_redirect_path(page, log, current_user)
nxt_page = next_page(page, log, current_user)
if nxt_page == :check_answers
"lettings_log_#{subsection_for_page(page).id}_check_answers_path"
"#{type}_log_#{subsection_for_page(page).id}_check_answers_path"
else
"lettings_log_#{nxt_page}_path"
"#{type}_log_#{nxt_page}_path"
end
end
def next_incomplete_section_redirect_path(subsection, lettings_log)
def cancel_path(page, log)
"#{log.class.name.underscore}_#{page.subsection.id}_check_answers_path"
end
def next_incomplete_section_redirect_path(subsection, log)
subsection_ids = subsections.map(&:id)
if lettings_log.status == "completed"
if log.status == "completed"
return first_question_in_last_subsection(subsection_ids)
end
next_subsection = next_subsection(subsection, lettings_log, subsection_ids)
next_subsection = next_subsection(subsection, log, subsection_ids)
case next_subsection.status(lettings_log)
case next_subsection.status(log)
when :completed
next_incomplete_section_redirect_path(next_subsection, lettings_log)
next_incomplete_section_redirect_path(next_subsection, log)
when :in_progress
"#{next_subsection.id}/check_answers".dasherize
when :not_started
first_question_in_subsection = next_subsection.pages.find { |page| page.routed_to?(lettings_log, nil) }.id
first_question_in_subsection = next_subsection.pages.find { |page| page.routed_to?(log, nil) }.id
first_question_in_subsection.to_s.dasherize
else
"error"
@ -92,21 +112,21 @@ class Form
first_question_in_subsection.to_s.dasherize
end
def next_subsection(subsection, lettings_log, subsection_ids)
def next_subsection(subsection, log, subsection_ids)
next_subsection_id_index = subsection_ids.index(subsection.id) + 1
next_subsection = get_subsection(subsection_ids[next_subsection_id_index])
if subsection_ids[subsection_ids.length - 1] == subsection.id && lettings_log.status != "completed"
if subsection_ids[subsection_ids.length - 1] == subsection.id && log.status != "completed"
next_subsection = get_subsection(subsection_ids[0])
end
next_subsection
end
def all_subsections_except_declaration_completed?(lettings_log)
def all_subsections_except_declaration_completed?(log)
subsection_ids = subsections.map(&:id)
subsection_ids.delete_at(subsection_ids.length - 1)
return true if subsection_ids.all? { |subsection_id| get_subsection(subsection_id).status(lettings_log) == :completed }
return true if subsection_ids.all? { |subsection_id| get_subsection(subsection_id).status(log) == :completed }
false
end
@ -118,26 +138,50 @@ class Form
}.flatten
end
def invalidated_pages(lettings_log, current_user = nil)
pages.reject { |p| p.routed_to?(lettings_log, current_user) }
def invalidated_pages(log, current_user = nil)
pages.reject { |p| p.routed_to?(log, current_user) }
end
def invalidated_questions(lettings_log)
invalidated_page_questions(lettings_log) + invalidated_conditional_questions(lettings_log)
def invalidated_questions(log)
invalidated_page_questions(log) + invalidated_conditional_questions(log)
end
def invalidated_page_questions(lettings_log, current_user = nil)
# we're already treating these fields as a special case and reset their values upon saving a lettings_log
def invalidated_page_questions(log, current_user = nil)
# we're already treating these fields as a special case and reset their values upon saving a log
callback_questions = %w[postcode_known la ppcodenk previous_la_known prevloc postcode_full ppostcode_full location_id]
questions.reject { |q| q.page.routed_to?(lettings_log, current_user) || q.derived? || callback_questions.include?(q.id) } || []
questions.reject { |q| q.page.routed_to?(log, current_user) || q.derived? || callback_questions.include?(q.id) } || []
end
def reset_not_routed_questions(log)
enabled_questions = enabled_page_questions(log)
enabled_question_ids = enabled_questions.map(&:id)
invalidated_page_questions(log).each do |question|
if %w[radio checkbox].include?(question.type)
enabled_answer_options = enabled_question_ids.include?(question.id) ? enabled_questions.find { |q| q.id == question.id }.answer_options : {}
current_answer_option_valid = enabled_answer_options.present? ? enabled_answer_options.key?(log.public_send(question.id).to_s) : false
if !current_answer_option_valid && log.respond_to?(question.id.to_s)
Rails.logger.debug("Cleared #{question.id} value")
log.public_send("#{question.id}=", nil)
else
(question.answer_options.keys - enabled_answer_options.keys).map do |invalid_answer_option|
Rails.logger.debug("Cleared #{invalid_answer_option} value")
log.public_send("#{invalid_answer_option}=", nil) if log.respond_to?(invalid_answer_option)
end
end
else
Rails.logger.debug("Cleared #{question.id} value")
log.public_send("#{question.id}=", nil) unless enabled_question_ids.include?(question.id)
end
end
end
def enabled_page_questions(lettings_log)
questions - invalidated_page_questions(lettings_log)
def enabled_page_questions(log)
questions - invalidated_page_questions(log)
end
def invalidated_conditional_questions(lettings_log)
questions.reject { |q| q.enabled?(lettings_log) } || []
def invalidated_conditional_questions(log)
questions.reject { |q| q.enabled?(log) } || []
end
def readonly_questions
@ -148,18 +192,18 @@ class Form
questions.select { |q| q.type == "numeric" }
end
def previous_page(page_ids, page_index, lettings_log, current_user)
def previous_page(page_ids, page_index, log, current_user)
prev_page = get_page(page_ids[page_index - 1])
return prev_page.id if prev_page.routed_to?(lettings_log, current_user)
return prev_page.id if prev_page.routed_to?(log, current_user)
previous_page(page_ids, page_index - 1, lettings_log, current_user)
previous_page(page_ids, page_index - 1, log, current_user)
end
def send_chain(arr, lettings_log)
Array(arr).inject(lettings_log) { |o, a| o.public_send(*a) }
def send_chain(arr, log)
Array(arr).inject(log) { |o, a| o.public_send(*a) }
end
def depends_on_met(depends_on, lettings_log)
def depends_on_met(depends_on, log)
return true unless depends_on
depends_on.any? do |conditions_set|
@ -169,12 +213,12 @@ class Form
if value.is_a?(Hash) && value.key?("operator")
operator = value["operator"]
operand = value["operand"]
lettings_log[question]&.send(operator, operand)
log[question]&.send(operator, operand)
else
parts = question.split(".")
lettings_log_value = send_chain(parts, lettings_log)
log_value = send_chain(parts, log)
value.nil? ? lettings_log_value == value : !lettings_log_value.nil? && lettings_log_value == value
value.nil? ? log_value == value : !log_value.nil? && log_value == value
end
end
end

6
app/models/form/setup/pages/created_by.rb → app/models/form/common/pages/created_by.rb

@ -1,4 +1,4 @@
class Form::Setup::Pages::CreatedBy < ::Form::Page
class Form::Common::Pages::CreatedBy < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "created_by"
@ -9,11 +9,11 @@ class Form::Setup::Pages::CreatedBy < ::Form::Page
def questions
@questions ||= [
Form::Setup::Questions::CreatedById.new(nil, nil, self),
Form::Common::Questions::CreatedById.new(nil, nil, self),
]
end
def routed_to?(_lettings_log, current_user)
def routed_to?(_log, current_user)
!!current_user&.support?
end
end

6
app/models/form/setup/pages/organisation.rb → app/models/form/common/pages/organisation.rb

@ -1,4 +1,4 @@
class Form::Setup::Pages::Organisation < ::Form::Page
class Form::Common::Pages::Organisation < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "organisation"
@ -9,11 +9,11 @@ class Form::Setup::Pages::Organisation < ::Form::Page
def questions
@questions ||= [
Form::Setup::Questions::OwningOrganisationId.new(nil, nil, self),
Form::Common::Questions::OwningOrganisationId.new(nil, nil, self),
]
end
def routed_to?(_lettings_log, current_user)
def routed_to?(_log, current_user)
!!current_user&.support?
end
end

12
app/models/form/setup/questions/created_by_id.rb → app/models/form/common/questions/created_by_id.rb

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

8
app/models/form/setup/questions/owning_organisation_id.rb → app/models/form/common/questions/owning_organisation_id.rb

@ -1,4 +1,4 @@
class Form::Setup::Questions::OwningOrganisationId < ::Form::Question
class Form::Common::Questions::OwningOrganisationId < ::Form::Question
def initialize(id, hsh, page)
super
@id = "owning_organisation_id"
@ -19,7 +19,7 @@ class Form::Setup::Questions::OwningOrganisationId < ::Form::Question
end
end
def displayed_answer_options(_lettings_log)
def displayed_answer_options(_log)
answer_options
end
@ -29,7 +29,7 @@ class Form::Setup::Questions::OwningOrganisationId < ::Form::Question
answer_options[value]
end
def hidden_in_check_answers?(_lettings_log, current_user)
def hidden_in_check_answers?(_log, current_user)
!current_user.support?
end
@ -39,7 +39,7 @@ class Form::Setup::Questions::OwningOrganisationId < ::Form::Question
private
def selected_answer_option_is_derived?(_lettings_log)
def selected_answer_option_is_derived?(_log)
false
end
end

4
app/models/form/setup/pages/location.rb → app/models/form/lettings/pages/location.rb

@ -1,4 +1,4 @@
class Form::Setup::Pages::Location < ::Form::Page
class Form::Lettings::Pages::Location < ::Form::Page
def initialize(_id, hsh, subsection)
super("location", hsh, subsection)
@header = ""
@ -11,7 +11,7 @@ class Form::Setup::Pages::Location < ::Form::Page
def questions
@questions ||= [
Form::Setup::Questions::LocationId.new(nil, nil, self),
Form::Lettings::Questions::LocationId.new(nil, nil, self),
]
end
end

4
app/models/form/setup/pages/needs_type.rb → app/models/form/lettings/pages/needs_type.rb

@ -1,4 +1,4 @@
class Form::Setup::Pages::NeedsType < ::Form::Page
class Form::Lettings::Pages::NeedsType < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "needs_type"
@ -9,7 +9,7 @@ class Form::Setup::Pages::NeedsType < ::Form::Page
def questions
@questions ||= [
Form::Setup::Questions::NeedsType.new(nil, nil, self),
Form::Lettings::Questions::NeedsType.new(nil, nil, self),
]
end
end

4
app/models/form/setup/pages/property_reference.rb → app/models/form/lettings/pages/property_reference.rb

@ -1,4 +1,4 @@
class Form::Setup::Pages::PropertyReference < ::Form::Page
class Form::Lettings::Pages::PropertyReference < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "property_reference"
@ -9,7 +9,7 @@ class Form::Setup::Pages::PropertyReference < ::Form::Page
def questions
@questions ||= [
Form::Setup::Questions::PropertyReference.new(nil, nil, self),
Form::Lettings::Questions::PropertyReference.new(nil, nil, self),
]
end
end

4
app/models/form/setup/pages/renewal.rb → app/models/form/lettings/pages/renewal.rb

@ -1,4 +1,4 @@
class Form::Setup::Pages::Renewal < ::Form::Page
class Form::Lettings::Pages::Renewal < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "renewal"
@ -9,7 +9,7 @@ class Form::Setup::Pages::Renewal < ::Form::Page
def questions
@questions ||= [
Form::Setup::Questions::Renewal.new(nil, nil, self),
Form::Lettings::Questions::Renewal.new(nil, nil, self),
]
end
end

6
app/models/form/setup/pages/rent_type.rb → app/models/form/lettings/pages/rent_type.rb

@ -1,4 +1,4 @@
class Form::Setup::Pages::RentType < ::Form::Page
class Form::Lettings::Pages::RentType < ::Form::Page
def initialize(_id, hsh, subsection)
super("rent_type", hsh, subsection)
@header = ""
@ -8,8 +8,8 @@ class Form::Setup::Pages::RentType < ::Form::Page
def questions
@questions ||= [
Form::Setup::Questions::RentType.new(nil, nil, self),
Form::Setup::Questions::IrproductOther.new(nil, nil, self),
Form::Lettings::Questions::RentType.new(nil, nil, self),
Form::Lettings::Questions::IrproductOther.new(nil, nil, self),
]
end
end

4
app/models/form/setup/pages/scheme.rb → app/models/form/lettings/pages/scheme.rb

@ -1,4 +1,4 @@
class Form::Setup::Pages::Scheme < ::Form::Page
class Form::Lettings::Pages::Scheme < ::Form::Page
def initialize(_id, hsh, subsection)
super("scheme", hsh, subsection)
@header = ""
@ -10,7 +10,7 @@ class Form::Setup::Pages::Scheme < ::Form::Page
def questions
@questions ||= [
Form::Setup::Questions::SchemeId.new(nil, nil, self),
Form::Lettings::Questions::SchemeId.new(nil, nil, self),
]
end
end

4
app/models/form/setup/pages/tenancy_start_date.rb → app/models/form/lettings/pages/tenancy_start_date.rb

@ -1,4 +1,4 @@
class Form::Setup::Pages::TenancyStartDate < ::Form::Page
class Form::Lettings::Pages::TenancyStartDate < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "tenancy_start_date"
@ -8,7 +8,7 @@ class Form::Setup::Pages::TenancyStartDate < ::Form::Page
def questions
@questions ||= [
Form::Setup::Questions::TenancyStartDate.new(nil, nil, self),
Form::Lettings::Questions::TenancyStartDate.new(nil, nil, self),
]
end
end

4
app/models/form/setup/pages/tenant_code.rb → app/models/form/lettings/pages/tenant_code.rb

@ -1,4 +1,4 @@
class Form::Setup::Pages::TenantCode < ::Form::Page
class Form::Lettings::Pages::TenantCode < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "tenant_code"
@ -9,7 +9,7 @@ class Form::Setup::Pages::TenantCode < ::Form::Page
def questions
@questions ||= [
Form::Setup::Questions::TenantCode.new(nil, nil, self),
Form::Lettings::Questions::TenantCode.new(nil, nil, self),
]
end
end

2
app/models/form/setup/questions/irproduct_other.rb → app/models/form/lettings/questions/irproduct_other.rb

@ -1,4 +1,4 @@
class Form::Setup::Questions::IrproductOther < ::Form::Question
class Form::Lettings::Questions::IrproductOther < ::Form::Question
def initialize(id, hsh, page)
super
@id = "irproduct_other"

2
app/models/form/setup/questions/location_id.rb → app/models/form/lettings/questions/location_id.rb

@ -1,4 +1,4 @@
class Form::Setup::Questions::LocationId < ::Form::Question
class Form::Lettings::Questions::LocationId < ::Form::Question
def initialize(_id, hsh, page)
super("location_id", hsh, page)
@check_answer_label = "Location"

2
app/models/form/setup/questions/needs_type.rb → app/models/form/lettings/questions/needs_type.rb

@ -1,4 +1,4 @@
class Form::Setup::Questions::NeedsType < ::Form::Question
class Form::Lettings::Questions::NeedsType < ::Form::Question
def initialize(id, hsh, page)
super
@id = "needstype"

2
app/models/form/setup/questions/property_reference.rb → app/models/form/lettings/questions/property_reference.rb

@ -1,4 +1,4 @@
class Form::Setup::Questions::PropertyReference < ::Form::Question
class Form::Lettings::Questions::PropertyReference < ::Form::Question
def initialize(id, hsh, page)
super
@id = "propcode"

2
app/models/form/setup/questions/renewal.rb → app/models/form/lettings/questions/renewal.rb

@ -1,4 +1,4 @@
class Form::Setup::Questions::Renewal < ::Form::Question
class Form::Lettings::Questions::Renewal < ::Form::Question
def initialize(id, hsh, page)
super
@id = "renewal"

2
app/models/form/setup/questions/rent_type.rb → app/models/form/lettings/questions/rent_type.rb

@ -1,4 +1,4 @@
class Form::Setup::Questions::RentType < ::Form::Question
class Form::Lettings::Questions::RentType < ::Form::Question
def initialize(id, hsh, page)
super
@id = "rent_type"

2
app/models/form/setup/questions/scheme_id.rb → app/models/form/lettings/questions/scheme_id.rb

@ -1,4 +1,4 @@
class Form::Setup::Questions::SchemeId < ::Form::Question
class Form::Lettings::Questions::SchemeId < ::Form::Question
def initialize(_id, hsh, page)
super("scheme_id", hsh, page)
@check_answer_label = "Scheme name"

2
app/models/form/setup/questions/tenancy_start_date.rb → app/models/form/lettings/questions/tenancy_start_date.rb

@ -1,4 +1,4 @@
class Form::Setup::Questions::TenancyStartDate < ::Form::Question
class Form::Lettings::Questions::TenancyStartDate < ::Form::Question
def initialize(id, hsh, page)
super
@id = "startdate"

2
app/models/form/setup/questions/tenant_code.rb → app/models/form/lettings/questions/tenant_code.rb

@ -1,4 +1,4 @@
class Form::Setup::Questions::TenantCode < ::Form::Question
class Form::Lettings::Questions::TenantCode < ::Form::Question
def initialize(id, hsh, page)
super
@id = "tenancycode"

4
app/models/form/setup/sections/setup.rb → app/models/form/lettings/sections/setup.rb

@ -1,10 +1,10 @@
class Form::Sections::Setup < ::Form::Section
class Form::Lettings::Sections::Setup < ::Form::Section
def initialize(id, hsh, form)
super
@id = "setup"
@label = "Before you start"
@description = ""
@form = form
@subsections = [Form::Setup::Subsections::Setup.new(nil, nil, self)]
@subsections = [Form::Lettings::Subsections::Setup.new(nil, nil, self)]
end
end

37
app/models/form/lettings/subsections/setup.rb

@ -0,0 +1,37 @@
class Form::Lettings::Subsections::Setup < ::Form::Subsection
def initialize(id, hsh, section)
super
@id = "setup"
@label = "Set up this lettings log"
@section = section
end
def pages
@pages ||= [
Form::Common::Pages::Organisation.new(nil, nil, self),
Form::Common::Pages::CreatedBy.new(nil, nil, self),
Form::Lettings::Pages::NeedsType.new(nil, nil, self),
Form::Lettings::Pages::Scheme.new(nil, nil, self),
Form::Lettings::Pages::Location.new(nil, nil, self),
Form::Lettings::Pages::Renewal.new(nil, nil, self),
Form::Lettings::Pages::TenancyStartDate.new(nil, nil, self),
Form::Lettings::Pages::RentType.new(nil, nil, self),
Form::Lettings::Pages::TenantCode.new(nil, nil, self),
Form::Lettings::Pages::PropertyReference.new(nil, nil, self),
]
end
def applicable_questions(lettings_log)
questions.select { |q| support_only_questions.include?(q.id) } + super
end
def enabled?(_lettings_log)
true
end
private
def support_only_questions
%w[owning_organisation_id created_by_id].freeze
end
end

4
app/models/form/page.rb

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

98
app/models/form/question.rb

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

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

@ -0,0 +1,15 @@
class Form::Sales::Pages::PurchaserCode < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "purchaser_code"
@header = ""
@description = ""
@subsection = subsection
end
def questions
@questions ||= [
Form::Sales::Questions::PurchaserCode.new(nil, nil, self),
]
end
end

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

@ -0,0 +1,15 @@
class Form::Sales::Pages::SaleDate < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "completion_date"
@header = ""
@description = ""
@subsection = subsection
end
def questions
@questions ||= [
Form::Sales::Questions::SaleDate.new(nil, nil, self),
]
end
end

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

@ -0,0 +1,18 @@
class Form::Sales::Pages::SharedOwnershipType < ::Form::Page
def initialize(id, hsh, subsection)
super
@id = "shared_ownership_type"
@header = ""
@description = ""
@subsection = subsection
@depends_on = [{
"ownershipsch" => 1,
}]
end
def questions
@questions ||= [
Form::Sales::Questions::SharedOwnershipType.new(nil, nil, self),
]
end
end

12
app/models/form/sales/questions/purchaser_code.rb

@ -0,0 +1,12 @@
class Form::Sales::Questions::PurchaserCode < ::Form::Question
def initialize(id, hsh, page)
super
@id = "purchid"
@check_answer_label = "Purchaser code"
@header = "What is the purchaser code?"
@hint_text = "This is how you usually refer to the purchaser on your own systems."
@type = "text"
@width = 10
@page = page
end
end

10
app/models/form/sales/questions/sale_date.rb

@ -0,0 +1,10 @@
class Form::Sales::Questions::SaleDate < ::Form::Question
def initialize(id, hsh, page)
super
@id = "saledate"
@check_answer_label = "Sale completion date"
@header = "What is the sale completion date?"
@type = "date"
@page = page
end
end

22
app/models/form/sales/questions/shared_ownership_type.rb

@ -0,0 +1,22 @@
class Form::Sales::Questions::SharedOwnershipType < ::Form::Question
def initialize(id, hsh, page)
super
@id = "type"
@check_answer_label = "Type of shared ownership sale"
@header = "What is the type of shared ownership sale?"
@hint_text = "A shared ownership sale is when the purchaser buys up to 75% of the property value and pays rent to the Private Registered Provider (PRP) on the remaining portion"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@page = page
end
ANSWER_OPTIONS = {
"2" => { "value" => "Shared Ownership" },
"24" => { "value" => "Old Persons Shared Ownership" },
"18" => { "value" => "Social HomeBuy (shared ownership purchase)" },
"16" => { "value" => "Home Ownership for people with Long Term Disabilities (HOLD)" },
"28" => { "value" => "Rent to Buy - Shared Ownership" },
"31" => { "value" => "Right to Shared Ownership" },
"30" => { "value" => "Shared Ownership - 2021 model lease" },
}.freeze
end

10
app/models/form/sales/sections/setup.rb

@ -0,0 +1,10 @@
class Form::Sales::Sections::Setup < ::Form::Section
def initialize(id, hsh, form)
super
@id = "setup"
@label = "Before you start"
@description = ""
@form = form
@subsections = [Form::Sales::Subsections::Setup.new(nil, nil, self)] || []
end
end

18
app/models/form/sales/subsections/setup.rb

@ -0,0 +1,18 @@
class Form::Sales::Subsections::Setup < ::Form::Subsection
def initialize(id, hsh, section)
super
@id = "setup"
@label = "Set up this sales log"
@section = section
end
def pages
@pages ||= [
Form::Common::Pages::Organisation.new(nil, nil, self),
Form::Common::Pages::CreatedBy.new(nil, nil, self),
Form::Sales::Pages::SaleDate.new(nil, nil, self),
Form::Sales::Pages::PurchaserCode.new(nil, nil, self),
Form::Sales::Pages::SharedOwnershipType.new(nil, nil, self),
]
end
end

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

@ -1,37 +0,0 @@
class Form::Subsections::Setup < ::Form::Subsection
def initialize(id, hsh, section)
super
@id = "setup"
@label = "Set up this lettings log"
@section = section
end
def pages
@pages ||= [
Form::Setup::Pages::Organisation.new(nil, nil, self),
Form::Setup::Pages::CreatedBy.new(nil, nil, self),
Form::Setup::Pages::NeedsType.new(nil, nil, self),
Form::Setup::Pages::Scheme.new(nil, nil, self),
Form::Setup::Pages::Location.new(nil, nil, self),
Form::Setup::Pages::Renewal.new(nil, nil, self),
Form::Setup::Pages::TenancyStartDate.new(nil, nil, self),
Form::Setup::Pages::RentType.new(nil, nil, self),
Form::Setup::Pages::TenantCode.new(nil, nil, self),
Form::Setup::Pages::PropertyReference.new(nil, nil, self),
]
end
def applicable_questions(lettings_log)
questions.select { |q| support_only_questions.include?(q.id) } + super
end
def enabled?(_lettings_log)
true
end
private
def support_only_questions
%w[owning_organisation_id created_by_id].freeze
end
end

28
app/models/form/subsection.rb

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

41
app/models/form_handler.rb

@ -10,23 +10,52 @@ class FormHandler
@forms[form]
end
def current_form
forms[forms.keys.max_by(&:to_i)]
def current_lettings_form
forms["current_lettings"]
end
private
def current_sales_form
forms["current_sales"]
end
def get_all_forms
def sales_forms
sales_sections = [] # Add section classes here e.g. Form::Sales::Property::Sections::PropertyInformation
current_form = Form.new(nil, current_collection_start_year, sales_sections, "sales")
previous_form = Form.new(nil, current_collection_start_year - 1, sales_sections, "sales")
{ "current_sales" => current_form,
"previous_sales" => previous_form }
end
def lettings_forms
forms = {}
directories.each do |directory|
Dir.glob("#{directory}/*.json").each do |form_path|
form_name = File.basename(form_path, ".json")
forms[form_name] = Form.new(form_path, form_name)
form = Form.new(form_path)
form_to_set = form_name_from_start_year(form.start_date.year, "lettings")
forms[form_to_set] = form if forms[form_to_set].blank?
end
end
forms
end
def current_collection_start_year
today = Time.zone.now
window_end_date = Time.zone.local(today.year, 4, 1)
today < window_end_date ? today.year - 1 : today.year
end
def form_name_from_start_year(year, type)
form_mappings = { 0 => "current_#{type}", 1 => "previous_#{type}", -1 => "next_#{type}" }
form_mappings[current_collection_start_year - year]
end
private
def get_all_forms
lettings_forms.merge(sales_forms)
end
def directories
Rails.env.test? ? ["spec/fixtures/forms"] : ["config/forms"]
end

94
app/models/lettings_log.rb

@ -14,7 +14,7 @@ class LettingsLogValidator < ActiveModel::Validator
end
end
class LettingsLog < ApplicationRecord
class LettingsLog < Log
include Validations::SoftValidations
include DerivedVariables::LettingsLogVariables
@ -29,31 +29,11 @@ class LettingsLog < ApplicationRecord
before_validation :reset_location_fields!, unless: :postcode_known?
before_validation :reset_previous_location_fields!, unless: :previous_postcode_known?
before_validation :set_derived_fields!
before_save :update_status!
belongs_to :owning_organisation, class_name: "Organisation", optional: true
belongs_to :managing_organisation, class_name: "Organisation", optional: true
belongs_to :created_by, class_name: "User", optional: true
belongs_to :scheme, optional: true
belongs_to :location, optional: true
scope :filter_by_organisation, ->(org, _user = nil) { where(owning_organisation: org).or(where(managing_organisation: org)) }
scope :filter_by_status, ->(status, _user = nil) { where status: }
scope :filter_by_years, lambda { |years, _user = nil|
first_year = years.shift
query = filter_by_year(first_year)
years.each { |year| query = query.or(filter_by_year(year)) }
query.all
}
scope :filter_by_year, ->(year) { where(startdate: Time.zone.local(year.to_i, 4, 1)...Time.zone.local(year.to_i + 1, 4, 1)) }
scope :filter_by_user, lambda { |selected_user, user|
if !selected_user.include?("all") && user.present?
where(created_by: user)
end
}
scope :filter_by_id, ->(id) { where(id:) }
scope :filter_by_tenant_code, ->(tenant_code) { where("tenancycode ILIKE ?", "%#{tenant_code}%") }
scope :filter_by_propcode, ->(propcode) { where("propcode ILIKE ?", "%#{propcode}%") }
scope :filter_by_postcode, ->(postcode_full) { where("REPLACE(postcode_full, ' ', '') ILIKE ?", "%#{postcode_full.delete(' ')}%") }
@ -70,22 +50,12 @@ class LettingsLog < ApplicationRecord
OPTIONAL_FIELDS = %w[first_time_property_let_as_social_housing tenancycode propcode].freeze
RENT_TYPE_MAPPING_LABELS = { 1 => "Social Rent", 2 => "Affordable Rent", 3 => "Intermediate Rent" }.freeze
HAS_BENEFITS_OPTIONS = [1, 6, 8, 7].freeze
STATUS = { "not_started" => 0, "in_progress" => 1, "completed" => 2 }.freeze
NUM_OF_WEEKS_FROM_PERIOD = { 2 => 26, 3 => 13, 4 => 12, 5 => 50, 6 => 49, 7 => 48, 8 => 47, 9 => 46, 1 => 52 }.freeze
SUFFIX_FROM_PERIOD = { 2 => "every 2 weeks", 3 => "every 4 weeks", 4 => "every month" }.freeze
RETIREMENT_AGES = { "M" => 67, "F" => 60, "X" => 67 }.freeze
enum status: STATUS
def form
FormHandler.instance.get_form(form_name) || FormHandler.instance.forms.first.second
end
def collection_start_year
return @start_year if @start_year
return unless startdate
window_end_date = Time.zone.local(startdate.year, 4, 1)
@start_year = startdate < window_end_date ? startdate.year - 1 : startdate.year
FormHandler.instance.get_form(form_name) || FormHandler.instance.current_lettings_form
end
def recalculate_start_year!
@ -96,7 +66,7 @@ class LettingsLog < ApplicationRecord
def form_name
return unless startdate
"#{collection_start_year}_#{collection_start_year + 1}"
FormHandler.instance.form_name_from_start_year(collection_start_year, "lettings")
end
def self.editable_fields
@ -527,44 +497,14 @@ class LettingsLog < ApplicationRecord
location.type_of_unit_before_type_cast if location
end
def lettings?
true
end
private
PIO = PostcodeService.new
def update_status!
self.status = if all_fields_completed? && errors.empty?
"completed"
elsif all_fields_nil?
"not_started"
else
"in_progress"
end
end
def reset_not_routed_questions
enabled_questions = form.enabled_page_questions(self)
enabled_question_ids = enabled_questions.map(&:id)
form.invalidated_page_questions(self).each do |question|
if %w[radio checkbox].include?(question.type)
enabled_answer_options = enabled_question_ids.include?(question.id) ? enabled_questions.find { |q| q.id == question.id }.answer_options : {}
current_answer_option_valid = enabled_answer_options.present? ? enabled_answer_options.key?(public_send(question.id).to_s) : false
if !current_answer_option_valid && respond_to?(question.id.to_s)
Rails.logger.debug("Cleared #{question.id} value")
public_send("#{question.id}=", nil)
else
(question.answer_options.keys - enabled_answer_options.keys).map do |invalid_answer_option|
Rails.logger.debug("Cleared #{invalid_answer_option} value")
public_send("#{invalid_answer_option}=", nil) if respond_to?(invalid_answer_option)
end
end
else
Rails.logger.debug("Cleared #{question.id} value")
public_send("#{question.id}=", nil) unless enabled_question_ids.include?(question.id)
end
end
end
def reset_derived_questions
dependent_questions = { waityear: [{ key: :renewal, value: 0 }],
referral: [{ key: :renewal, value: 0 }],
@ -582,12 +522,6 @@ private
end
end
def reset_created_by
return unless created_by && owning_organisation
self.created_by = nil if created_by.organisation != owning_organisation
end
def reset_scheme
return unless scheme && owning_organisation
@ -595,11 +529,10 @@ private
end
def reset_invalidated_dependent_fields!
return unless form
super
reset_created_by
reset_scheme
reset_not_routed_questions
reset_derived_questions
end
@ -692,17 +625,6 @@ private
end
end
def all_fields_completed?
subsection_statuses = form.subsections.map { |subsection| subsection.status(self) }.uniq
subsection_statuses == [:completed]
end
def all_fields_nil?
not_started_statuses = %i[not_started cannot_start_yet]
subsection_statuses = form.subsections.map { |subsection| subsection.status(self) }.uniq
subsection_statuses.all? { |status| not_started_statuses.include?(status) }
end
def age_refused?
[age1_known, age2_known, age3_known, age4_known, age5_known, age6_known, age7_known, age8_known].any?(1)
end

73
app/models/log.rb

@ -0,0 +1,73 @@
class Log < ApplicationRecord
self.abstract_class = true
belongs_to :owning_organisation, class_name: "Organisation", optional: true
belongs_to :managing_organisation, class_name: "Organisation", optional: true
belongs_to :created_by, class_name: "User", optional: true
before_save :update_status!
STATUS = { "not_started" => 0, "in_progress" => 1, "completed" => 2 }.freeze
enum status: STATUS
scope :filter_by_organisation, ->(org, _user = nil) { where(owning_organisation: org).or(where(managing_organisation: org)) }
scope :filter_by_status, ->(status, _user = nil) { where status: }
scope :filter_by_years, lambda { |years, _user = nil|
first_year = years.shift
query = filter_by_year(first_year)
years.each { |year| query = query.or(filter_by_year(year)) }
query.all
}
scope :filter_by_id, ->(id) { where(id:) }
scope :filter_by_user, lambda { |selected_user, user|
if !selected_user.include?("all") && user.present?
where(created_by: user)
end
}
def collection_start_year
return @start_year if @start_year
return unless startdate
window_end_date = Time.zone.local(startdate.year, 4, 1)
@start_year = startdate < window_end_date ? startdate.year - 1 : startdate.year
end
def lettings?
false
end
private
def update_status!
self.status = if all_fields_completed? && errors.empty?
"completed"
elsif all_fields_nil?
"not_started"
else
"in_progress"
end
end
def all_fields_completed?
subsection_statuses = form.subsections.map { |subsection| subsection.status(self) }.uniq
subsection_statuses == [:completed]
end
def all_fields_nil?
not_started_statuses = %i[not_started cannot_start_yet]
subsection_statuses = form.subsections.map { |subsection| subsection.status(self) }.uniq
subsection_statuses.all? { |status| not_started_statuses.include?(status) }
end
def reset_created_by
return unless created_by && owning_organisation
self.created_by = nil if created_by.organisation != owning_organisation
end
def reset_invalidated_dependent_fields!
return unless form
form.reset_not_routed_questions(self)
end
end

6
app/models/organisation.rb

@ -2,6 +2,8 @@ class Organisation < ApplicationRecord
has_many :users, dependent: :delete_all
has_many :owned_lettings_logs, class_name: "LettingsLog", foreign_key: "owning_organisation_id", dependent: :delete_all
has_many :managed_lettings_logs, class_name: "LettingsLog", foreign_key: "managing_organisation_id"
has_many :owned_sales_logs, class_name: "SalesLog", foreign_key: "owning_organisation_id", dependent: :delete_all
has_many :managed_sales_logs, class_name: "SalesLog", foreign_key: "managing_organisation_id"
has_many :data_protection_confirmations
has_many :organisation_rent_periods
has_many :owned_schemes, class_name: "Scheme", foreign_key: "owning_organisation_id", dependent: :delete_all
@ -32,6 +34,10 @@ class Organisation < ApplicationRecord
LettingsLog.filter_by_organisation(self)
end
def sales_logs
SalesLog.filter_by_organisation(self)
end
def completed_lettings_logs
lettings_logs.completed
end

2
app/models/rent_period.rb

@ -1,5 +1,5 @@
class RentPeriod
def self.rent_period_mappings
FormHandler.instance.current_form.get_question("period", nil).answer_options
FormHandler.instance.current_lettings_form.get_question("period", nil).answer_options
end
end

45
app/models/sales_log.rb

@ -0,0 +1,45 @@
class SalesLogValidator < ActiveModel::Validator
def validate(record); end
end
class SalesLog < Log
self.inheritance_column = :_type_disabled
has_paper_trail
validates_with SalesLogValidator
scope :filter_by_year, ->(year) { where(saledate: Time.zone.local(year.to_i, 4, 1)...Time.zone.local(year.to_i + 1, 4, 1)) }
scope :search_by, ->(param) { filter_by_id(param) }
OPTIONAL_FIELDS = [].freeze
def startdate
saledate
end
def self.editable_fields
attribute_names
end
def form_name
return unless saledate
FormHandler.instance.form_name_from_start_year(collection_start_year, "sales")
end
def form
FormHandler.instance.get_form(form_name) || FormHandler.instance.current_sales_form
end
def optional_fields
[]
end
def not_started?
status == "not_started"
end
def completed?
status == "completed"
end
end

14
app/models/user.rb

@ -7,8 +7,10 @@ class User < ApplicationRecord
# Marked as optional because we validate organisation_id below instead so that
# the error message is linked to the right field on the form
belongs_to :organisation, optional: true
has_many :owned_lettings_logs, through: :organisation, dependent: :delete_all
has_many :owned_lettings_logs, through: :organisation
has_many :managed_lettings_logs, through: :organisation
has_many :owned_sales_logs, through: :organisation
has_many :managed_sales_logs, through: :organisation
validates :name, presence: true
validates :email, presence: true
@ -58,6 +60,14 @@ class User < ApplicationRecord
end
end
def sales_logs
if support?
SalesLog.all
else
SalesLog.filter_by_organisation(organisation)
end
end
def completed_lettings_logs
lettings_logs.completed
end
@ -131,7 +141,7 @@ class User < ApplicationRecord
ROLES.except(:support)
end
def lettings_logs_filters(specific_org: false)
def logs_filters(specific_org: false)
if support? && !specific_org
%w[status years user organisation]
else

8
app/models/validations/date_validations.rb

@ -60,19 +60,19 @@ module Validations::DateValidations
private
def first_collection_start_date
@first_collection_start_date ||= FormHandler.instance.forms.map { |form| form.second.start_date }.compact.min
@first_collection_start_date ||= FormHandler.instance.forms.map { |_name, form| form.start_date }.compact.min
end
def first_collection_end_date
@first_collection_end_date ||= FormHandler.instance.forms.map { |form| form.second.end_date }.compact.min
@first_collection_end_date ||= FormHandler.instance.forms.map { |_name, form| form.end_date }.compact.min
end
def second_collection_start_date
@second_collection_start_date ||= FormHandler.instance.forms.map { |form| form.second.start_date }.compact.max
@second_collection_start_date ||= FormHandler.instance.forms.map { |_name, form| form.start_date }.compact.max
end
def second_collection_end_date
@second_collection_end_date ||= FormHandler.instance.forms.map { |form| form.second.end_date }.compact.max
@second_collection_end_date ||= FormHandler.instance.forms.map { |_name, form| form.end_date }.compact.max
end
def date_valid?(question, record)

8
app/services/csv/lettings_log_csv_service.rb

@ -51,7 +51,13 @@ module Csv
def ordered_form_questions
downloaded_form_years = LettingsLog.all.map(&:collection_start_year).uniq.compact
downloaded_form_fields = downloaded_form_years.count == 1 && downloaded_form_years[0].present? ? FormHandler.instance.get_form("#{downloaded_form_years[0]}_#{downloaded_form_years[0] + 1}").questions : FormHandler.instance.forms.first.second.questions
if downloaded_form_years.count == 1 && downloaded_form_years[0].present?
form_name = FormHandler.instance.form_name_from_start_year(downloaded_form_years[0], "lettings")
downloaded_form_fields = FormHandler.instance.get_form(form_name).questions
else
downloaded_form_fields = FormHandler.instance.current_lettings_form.questions
end
move_checkbox_answer_options(downloaded_form_fields)
end

22
app/services/filter_service.rb

@ -0,0 +1,22 @@
class FilterService
def self.filter_by_search(base_collection, search_term = nil)
if search_term.present?
base_collection.search_by(search_term)
else
base_collection
end
end
def self.filter_logs(logs, search_term, filters, all_orgs, user)
logs = filter_by_search(logs, search_term)
filters.each do |category, values|
next if Array(values).reject(&:empty?).blank?
next if category == "organisation" && all_orgs
logs = logs.public_send("filter_by_#{category}", values, user)
end
logs = logs.order(created_at: :desc)
user.support? ? logs.all.includes(:owning_organisation, :managing_organisation) : logs
end
end

6
app/services/storage/s3_service.rb

@ -20,6 +20,12 @@ module Storage
response.key_count == 1
end
def get_presigned_url(file_name, duration)
Aws::S3::Presigner
.new({ client: @client })
.presigned_url(:get_object, bucket: @configuration.bucket_name, key: file_name, expires_in: duration)
end
def get_file_io(file_name)
@client.get_object(bucket: @configuration.bucket_name, key: file_name)
.body

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="en" class="govuk-template">
<head>
<title><%= browser_title(yield(:title), @pagy, @admin_user, @user, @organisation, @lettings_log, @resource) %></title>
<title><%= browser_title(yield(:title), @pagy, @admin_user, @user, @organisation, @log, @resource) %></title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= tag.meta name: "viewport", content: "width=device-width, initial-scale=1" %>
@ -34,7 +34,7 @@
</script>
<% end %>
<% if Rails.env.production? %>
<% if Rails.env.production? && ENV["APP_HOST"].present? %>
<script defer data-domain="<%= ENV["APP_HOST"].split("https://")[1] %>" src="https://plausible.io/js/plausible.js"></script>
<% end %>
</head>

2
app/views/lettings_logs/_log_filters.erb → app/views/logs/_log_filters.erb

@ -10,7 +10,7 @@
<%= render partial: "filters/checkbox_filter", locals: { f: f, options: years, label: "Collection year", category: "years" } %>
<%= render partial: "filters/checkbox_filter", locals: { f: f, options: status_filters, label: "Status", category: "status" } %>
<%= render partial: "filters/radio_filter", locals: { f: f, options: all_or_yours, label: "Logs", category: "user", } %>
<% if @current_user.support? && request.path == "/logs" %>
<% if @current_user.support? && request.path == "/lettings-logs" %>
<%= render partial: "filters/radio_filter", locals: {
f: f,
options: {

5
app/views/lettings_logs/_log_list.html.erb → app/views/logs/_log_list.html.erb

@ -1,8 +1,7 @@
<h2 class="govuk-body">
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "logs", path: request.path)) %>
<%= govuk_link_to "Download (CSV)", "#{request.path}.csv", type: "text/csv" %>
<%= govuk_link_to "Download (CSV)", csv_download_url, type: "text/csv" %>
</h2>
<% lettings_logs.map do |log| %>
<% logs.map do |log| %>
<%= render(LogSummaryComponent.new(current_user:, log:)) %>
<% end %>

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

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

0
app/views/lettings_logs/bulk_upload.html.erb → app/views/logs/bulk_upload.html.erb

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

@ -0,0 +1,15 @@
<% content_for :title, "We’re sending you an email" %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<%= govuk_panel(title_text: "We’re sending you an email") %>
<p class="govuk-body">It should arrive in a few minutes, but it could take longer.</p>
<h2 class="govuk-heading-m">What happens next</h2>
<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 %>
</p>
</div>
</div>

16
app/views/logs/download_csv.html.erb

@ -0,0 +1,16 @@
<% content_for :title, "Download CSV" %>
<% content_for :before_content do %>
<%= govuk_back_link(href: :back) %>
<% end %>
<div class="govuk-grid-row">
<div class="govuk-grid-column-two-thirds">
<h1 class="govuk-heading-l">Download CSV</h2>
<p class="govuk-body">We'll send a secure download link to your email address <strong><%= @current_user.email %></strong>.</p>
<p class="govuk-body">You've selected <%= count %> logs.</p>
<%= govuk_button_to "Send email", post_path, method: :post, params: { search: search_term } %>
</div>
</div>

18
app/views/lettings_logs/edit.html.erb → app/views/logs/edit.html.erb

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

9
app/views/lettings_logs/index.html.erb → app/views/logs/index.html.erb

@ -7,7 +7,12 @@
<div class="app-filter-layout" data-controller="filter-layout">
<div class="govuk-button-group app-filter-toggle">
<%= govuk_button_to "Create a new lettings log", lettings_logs_path %>
<% if current_page?(:controller => 'lettings_logs', :action => 'index') %>
<%= govuk_button_to "Create a new lettings log", lettings_logs_path %>
<% end %>
<% if FeatureToggle.sales_log_enabled? && current_page?(:controller => 'sales_logs', :action => 'index') %>
<%= govuk_button_to "Create a new sales log", sales_logs_path %>
<% end %>
<%#= govuk_link_to "Upload logs", bulk_upload_lettings_logs_path %>
</div>
@ -15,7 +20,7 @@
<div class="app-filter-layout__content">
<%= render SearchComponent.new(current_user:, search_label: "Search by log ID, tenant code, property reference or postcode", value: @searched) %>
<%= govuk_section_break(visible: true, size: "m") %>
<%= render partial: "log_list", locals: { lettings_logs: @lettings_logs, title: "Logs", pagy: @pagy, searched: @searched, item_label:, total_count: @total_count } %>
<%= render partial: "log_list", locals: { logs: @logs, title: "Logs", pagy: @pagy, searched: @searched, item_label:, total_count: @total_count, csv_download_url: csv_download_lettings_logs_path(search: @search_term) } %>
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "logs" } %>
</div>
</div>

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

@ -22,7 +22,7 @@
<% row.cell(header: true, html_attributes: {
scope: "row",
}) do %>
<%= govuk_link_to(organisation.name, "organisations/#{organisation.id}/logs") %>
<%= govuk_link_to(organisation.name, "organisations/#{organisation.id}/lettings-logs") %>
<% end %>
<% row.cell(text: organisation.housing_registration_no) %>
<% row.cell(text: organisation.display_provider_type) %>

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

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

2
config/application.rb

@ -33,5 +33,7 @@ module DataCollector
# config.eager_load_paths << Rails.root.join("extras")
config.exceptions_app = routes
config.active_job.queue_adapter = :sidekiq
end
end

6
config/initializers/feature_toggle.rb

@ -2,4 +2,10 @@ class FeatureToggle
def self.startdate_two_week_validation_enabled?
true
end
def self.sales_log_enabled?
return true unless Rails.env.production?
false
end
end

2
config/initializers/rack_attack.rb

@ -5,7 +5,7 @@ if Rails.env.development? || Rails.env.test?
Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new
Rack::Attack.enabled = false
else
redis_url = Configuration::PaasConfigurationService.new.redis_uris[:"dluhc-core-#{Rails.env}-redis-rate-limit"]
redis_url = Configuration::PaasConfigurationService.new.redis_uris[:"dluhc-core-#{Rails.env}-redis"]
Rack::Attack.cache.store = ActiveSupport::Cache::RedisCacheStore.new(url: redis_url)
end

11
config/initializers/sidekiq.rb

@ -0,0 +1,11 @@
if Rails.env.staging? || Rails.env.production?
redis_url = Configuration::PaasConfigurationService.new.redis_uris[:"dluhc-core-#{Rails.env}-redis"]
Sidekiq.configure_server do |config|
config.redis = { url: redis_url }
end
Sidekiq.configure_client do |config|
config.redis = { url: redis_url }
end
end

40
config/locales/en.yml

@ -136,8 +136,8 @@ en:
first_let_not_social: "Enter a reason for vacancy that is not 'first let' if unit has been previously let as social housing"
first_let_social: "Reason for vacancy must be first let if unit has been previously let as social housing"
previous_let_social: "Property cannot have a previous let type if being let as social housing for the first time"
non_temp_accommodation: "Answer cannot be re-let to tenant who occupied the same property as temporary accommodation as you already told us this accommodation is not temporary"
referral_invalid: "Answer cannot be re-let to tenant who occupied the same property as temporary accommodation as you already told us a different source of referral for this letting"
non_temp_accommodation: "Answer cannot be re-let to tenant who occupied the same property as temporary accommodation as this accommodation is not temporary"
referral_invalid: "Answer cannot be re-let to tenant who occupied the same property as temporary accommodation as a different source of referral for this letting"
unittype_gn:
one_bedroom_bedsit: "A bedsit can only have one bedroom"
one_seven_bedroom_shared: "A shared house must have 1 to 7 bedrooms"
@ -207,7 +207,7 @@ en:
not_provided: "Enter how much rent and other charges the household pays %{period}"
household:
reasonpref:
not_homeless: "Answer cannot be ‘homeless or about to lose their home’ as you already told us the tenant was not homeless immediately prior to this letting"
not_homeless: "Answer cannot be ‘homeless or about to lose their home’ as the tenant was not homeless immediately prior to this letting"
reasonable_preference_reason:
reason_required: "Enter a reason if you've answered 'yes' to reasonable preference"
reason_not_required: "Do not enter a reason if you've answered 'no' to reasonable preference"
@ -245,31 +245,31 @@ en:
housingneeds_a:
one_or_two_choices: "You can only select one option or ‘other disabled access needs’ plus ‘wheelchair-accessible housing’, ‘wheelchair access to essential rooms’ or ‘level access housing’"
prevten:
non_temp_accommodation: "Answer cannot be non-temporary accommodation as you already told us this is a re-let to a tenant who occupied the same property as temporary accommodation"
non_temp_accommodation: "Answer cannot be non-temporary accommodation as this is a re-let to a tenant who occupied the same property as temporary accommodation"
over_20_foster_care: "Answer cannot be a children’s home or foster care as the lead tenant is 20 or older"
male_refuge: "Answer cannot be a refuge as the lead tenant identifies as male"
internal_transfer: "Answer cannot be %{prevten} as you already told us this tenancy is an internal transfer"
internal_transfer: "Answer cannot be %{prevten} as this tenancy is an internal transfer"
la_general_needs:
internal_transfer: "Answer cannot be a fixed-term or lifetime local authority general needs tenancy as you already told us it’s an internal transfer and a private registered provider is on the tenancy agreement"
internal_transfer: "Answer cannot be a fixed-term or lifetime local authority general needs tenancy as it’s an internal transfer and a private registered provider is on the tenancy agreement"
referral:
secure_tenancy: "Answer must be internal transfer as you already told us this is a secure tenancy"
rsnvac_non_temp: "Answer cannot be this source of referral as you already told us this is a re-let to tenant who occupied the same property as temporary accommodation"
cannot_be_secure_tenancy: "Answer cannot be secure tenancy as you already told us this is not an internal transfer"
assessed_homeless: "Answer cannot be internal transfer as you already told us the tenant was assessed as homeless"
other_homeless: "Answer cannot be internal transfer as you already told us the tenant was considered homeless by their landlord"
prevten_invalid: "Answer cannot be internal transfer as you already told us the household situation immediately before this letting was %{prevten}"
reason_permanently_decanted: "Answer must be internal transfer as you already told us the tenant was permanently decanted from another property owned by this landlord"
secure_tenancy: "Answer must be internal transfer as this is a secure tenancy"
rsnvac_non_temp: "Answer cannot be this source of referral as this is a re-let to tenant who occupied the same property as temporary accommodation"
cannot_be_secure_tenancy: "Answer cannot be secure tenancy as this is not an internal transfer"
assessed_homeless: "Answer cannot be internal transfer as the tenant was assessed as homeless"
other_homeless: "Answer cannot be internal transfer as the tenant was considered homeless by their landlord"
prevten_invalid: "Answer cannot be internal transfer as the household situation immediately before this letting was %{prevten}"
reason_permanently_decanted: "Answer must be internal transfer as the tenant was permanently decanted from another property owned by this landlord"
la_general_needs:
internal_transfer: "Answer cannot be internal transfer as you already told us it’s the same landlord on the tenancy agreement and the household had either a fixed-term or lifetime local authority general needs tenancy immediately before this letting"
internal_transfer: "Answer cannot be internal transfer as it’s the same landlord on the tenancy agreement and the household had either a fixed-term or lifetime local authority general needs tenancy immediately before this letting"
prp:
local_housing_referral: "Answer cannot be ‘nominated by a local housing authority’ as you already told us a local authority is on the tenancy agreement"
local_housing_referral: "Answer cannot be ‘nominated by a local housing authority’ as a local authority is on the tenancy agreement"
homeless:
assessed:
internal_transfer: "Answer cannot be 'assessed as homeless' as you already told us this tenancy is an internal transfer"
internal_transfer: "Answer cannot be 'assessed as homeless' as this tenancy is an internal transfer"
other:
internal_transfer: "Answer cannot be 'other homelessness' as you already told us this tenancy was an internal transfer"
internal_transfer: "Answer cannot be 'other homelessness' as this tenancy was an internal transfer"
reasonpref:
not_homeless: "Answer cannot be ‘no’ as you already told us the tenant was homeless or about to lose their home"
not_homeless: "Answer cannot be ‘no’ as the tenant was homeless or about to lose their home"
previous_la_known: "Enter name of local authority"
gender:
retired_male: "Answer cannot be ‘male’ as tenant is under 65 and retired"
@ -285,8 +285,8 @@ en:
fixed_term_not_required: "You must only answer the length of the tenancy if it's fixed-term"
shorthold: "Enter a tenancy length between 2 and 99 years for a Fixed Term – Assured Shorthold Tenancy (AST)"
secure: "Enter a tenancy length between 2 and 99 years (or don't specify the length) for a Secure (including flexible) tenancy"
internal_transfer: "Answer must be secure tenancy as you already told us this tenancy is an internal transfer"
cannot_be_internal_transfer: "Answer cannot be internal transfer as you already told us this is not a secure tenancy"
internal_transfer: "Answer must be secure tenancy as this tenancy is an internal transfer"
cannot_be_internal_transfer: "Answer cannot be internal transfer as this is not a secure tenancy"
not_joint: "This cannot be a joint tenancy as you've told us there's only one person in the household"
joint_more_than_one_member: "There must be more than one person in the household as you've told us this is a joint tenancy"

28
config/routes.rb

@ -27,6 +27,7 @@ Rails.application.routes.draw do
root to: "start#index"
get "/logs", to: redirect("/lettings-logs")
get "/accessibility-statement", to: "content#accessibility_statement"
get "/privacy-notice", to: "content#privacy_notice"
get "/data-sharing-agreement", to: "content#data_sharing_agreement"
@ -68,15 +69,22 @@ Rails.application.routes.draw do
get "details", to: "organisations#details"
get "users", to: "organisations#users"
get "users/invite", to: "users/account#new"
get "logs", to: "organisations#logs"
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 "schemes", to: "organisations#schemes"
end
end
resources :lettings_logs, path: "/logs" do
resources :lettings_logs, path: "/lettings-logs" do
collection do
post "bulk-upload", to: "bulk_upload#bulk_upload"
get "bulk-upload", to: "bulk_upload#show"
get "csv-download", to: "lettings_logs#download_csv"
post "email-csv", to: "lettings_logs#email_csv"
get "csv-confirmation", to: "lettings_logs#csv_confirmation"
end
member do
@ -84,9 +92,21 @@ Rails.application.routes.draw do
get "review", to: "form#review"
end
FormHandler.instance.forms.each do |_key, form|
FormHandler.instance.lettings_forms.each do |_key, form|
form.pages.map do |page|
get page.id.to_s.dasherize, to: "form##{page.id}"
get page.id.to_s.dasherize, to: "form#show_page"
end
form.subsections.map do |subsection|
get "#{subsection.id.to_s.dasherize}/check-answers", to: "form#check_answers"
end
end
end
resources :sales_logs, path: "/sales-logs" do
FormHandler.instance.sales_forms.each do |_key, form|
form.pages.map do |page|
get page.id.to_s.dasherize, to: "form#show_page"
end
form.subsections.map do |subsection|

12
db/migrate/20220826093411_add_sales_log.rb

@ -0,0 +1,12 @@
class AddSalesLog < ActiveRecord::Migration[7.0]
def change
create_table :sales_logs do |t|
t.integer :status, default: 0
t.datetime :saledate
t.timestamps
t.references :owning_organisation, class_name: "Organisation", foreign_key: { to_table: :organisations, on_delete: :cascade }
t.references :managing_organisation, class_name: "Organisation"
t.references :created_by, class_name: "User"
end
end
end

5
db/migrate/20220915093559_add_purchid.rb

@ -0,0 +1,5 @@
class AddPurchid < ActiveRecord::Migration[7.0]
def change
add_column :sales_logs, :purchid, :string
end
end

5
db/migrate/20220916110129_add_shared_ownership_type.rb

@ -0,0 +1,5 @@
class AddSharedOwnershipType < ActiveRecord::Migration[7.0]
def change
add_column :sales_logs, :type, :integer
end
end

5
db/migrate/20220916125704_add_ownership_scheme_to_sales_log.rb

@ -0,0 +1,5 @@
class AddOwnershipSchemeToSalesLog < ActiveRecord::Migration[7.0]
def change
add_column :sales_logs, :ownershipsch, :integer
end
end

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

Loading…
Cancel
Save