Browse Source

Merge branch 'main' into CLDC-1222-improve-case-log-import-performance

CLDC-122-experimental-background-job-lettings-log-import-with-report
Mo Seedat 3 years ago
parent
commit
69c15f1e0a
  1. 4
      .github/workflows/production_pipeline.yml
  2. 4
      .github/workflows/staging_pipeline.yml
  3. 2
      .gitignore
  4. 7
      .rubocop.yml
  5. 4
      Gemfile
  6. 175
      Gemfile.lock
  7. 8
      app/components/check_answers_summary_list_card_component.html.erb
  8. 10
      app/components/check_answers_summary_list_card_component.rb
  9. 6
      app/components/log_summary_component.html.erb
  10. 4
      app/controllers/bulk_upload_controller.rb
  11. 99
      app/controllers/form_controller.rb
  12. 113
      app/controllers/lettings_logs_controller.rb
  13. 73
      app/controllers/logs_controller.rb
  14. 23
      app/controllers/modules/lettings_logs_filter.rb
  15. 21
      app/controllers/modules/logs_filter.rb
  16. 8
      app/controllers/modules/search_filter.rb
  17. 62
      app/controllers/organisations_controller.rb
  18. 46
      app/controllers/sales_logs_controller.rb
  19. 4
      app/helpers/check_answers_helper.rb
  20. 8
      app/helpers/filters_helper.rb
  21. 35
      app/helpers/navigation_items_helper.rb
  22. 32
      app/helpers/tasklist_helper.rb
  23. 21
      app/jobs/email_csv_job.rb
  24. 11
      app/mailers/csv_download_mailer.rb
  25. 37
      app/mailers/notify_mailer.rb
  26. 1
      app/models/bulk_upload.rb
  27. 12
      app/models/derived_variables/lettings_log_variables.rb
  28. 158
      app/models/form.rb
  29. 6
      app/models/form/common/pages/created_by.rb
  30. 6
      app/models/form/common/pages/organisation.rb
  31. 12
      app/models/form/common/questions/created_by_id.rb
  32. 8
      app/models/form/common/questions/owning_organisation_id.rb
  33. 4
      app/models/form/lettings/pages/location.rb
  34. 4
      app/models/form/lettings/pages/needs_type.rb
  35. 4
      app/models/form/lettings/pages/property_reference.rb
  36. 4
      app/models/form/lettings/pages/renewal.rb
  37. 6
      app/models/form/lettings/pages/rent_type.rb
  38. 4
      app/models/form/lettings/pages/scheme.rb
  39. 4
      app/models/form/lettings/pages/tenancy_start_date.rb
  40. 4
      app/models/form/lettings/pages/tenant_code.rb
  41. 2
      app/models/form/lettings/questions/irproduct_other.rb
  42. 2
      app/models/form/lettings/questions/location_id.rb
  43. 2
      app/models/form/lettings/questions/needs_type.rb
  44. 2
      app/models/form/lettings/questions/property_reference.rb
  45. 2
      app/models/form/lettings/questions/renewal.rb
  46. 2
      app/models/form/lettings/questions/rent_type.rb
  47. 2
      app/models/form/lettings/questions/scheme_id.rb
  48. 2
      app/models/form/lettings/questions/tenancy_start_date.rb
  49. 2
      app/models/form/lettings/questions/tenant_code.rb
  50. 4
      app/models/form/lettings/sections/setup.rb
  51. 37
      app/models/form/lettings/subsections/setup.rb
  52. 4
      app/models/form/page.rb
  53. 98
      app/models/form/question.rb
  54. 16
      app/models/form/sales/pages/age1.rb
  55. 15
      app/models/form/sales/pages/buyer1_live_in_property.rb
  56. 18
      app/models/form/sales/pages/buyer_live.rb
  57. 18
      app/models/form/sales/pages/discounted_ownership_type.rb
  58. 15
      app/models/form/sales/pages/gender_identity1.rb
  59. 15
      app/models/form/sales/pages/joint_purchase.rb
  60. 18
      app/models/form/sales/pages/number_joint_buyers.rb
  61. 19
      app/models/form/sales/pages/outright_ownership_type.rb
  62. 15
      app/models/form/sales/pages/ownership_scheme.rb
  63. 15
      app/models/form/sales/pages/property_number_of_bedrooms.rb
  64. 15
      app/models/form/sales/pages/purchaser_code.rb
  65. 15
      app/models/form/sales/pages/sale_date.rb
  66. 18
      app/models/form/sales/pages/shared_ownership_type.rb
  67. 11
      app/models/form/sales/questions/age1.rb
  68. 21
      app/models/form/sales/questions/buyer1_age_known.rb
  69. 17
      app/models/form/sales/questions/buyer1_live_in_property.rb
  70. 16
      app/models/form/sales/questions/buyer_live.rb
  71. 21
      app/models/form/sales/questions/discounted_ownership_type.rb
  72. 19
      app/models/form/sales/questions/gender_identity1.rb
  73. 17
      app/models/form/sales/questions/joint_purchase.rb
  74. 18
      app/models/form/sales/questions/number_joint_buyers.rb
  75. 11
      app/models/form/sales/questions/other_ownership_type.rb
  76. 19
      app/models/form/sales/questions/outright_ownership_type.rb
  77. 17
      app/models/form/sales/questions/ownership_scheme.rb
  78. 12
      app/models/form/sales/questions/property_number_of_bedrooms.rb
  79. 12
      app/models/form/sales/questions/purchaser_code.rb
  80. 10
      app/models/form/sales/questions/sale_date.rb
  81. 22
      app/models/form/sales/questions/shared_ownership_type.rb
  82. 10
      app/models/form/sales/sections/household.rb
  83. 10
      app/models/form/sales/sections/property_information.rb
  84. 10
      app/models/form/sales/sections/setup.rb
  85. 17
      app/models/form/sales/subsections/household_characteristics.rb
  86. 15
      app/models/form/sales/subsections/property_information.rb
  87. 24
      app/models/form/sales/subsections/setup.rb
  88. 37
      app/models/form/setup/subsections/setup.rb
  89. 28
      app/models/form/subsection.rb
  90. 44
      app/models/form_handler.rb
  91. 5
      app/models/legacy_user.rb
  92. 98
      app/models/lettings_log.rb
  93. 73
      app/models/log.rb
  94. 6
      app/models/organisation.rb
  95. 2
      app/models/rent_period.rb
  96. 45
      app/models/sales_log.rb
  97. 17
      app/models/user.rb
  98. 12
      app/models/validations/date_validations.rb
  99. 40
      app/services/csv/lettings_log_csv_service.rb
  100. 22
      app/services/filter_service.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

2
.gitignore vendored

@ -55,3 +55,5 @@ yarn-debug.log*
/app/assets/builds/*
!/app/assets/builds/.keep
spec/examples.txt

7
.rubocop.yml

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

4
Gemfile

@ -58,8 +58,8 @@ gem "sentry-ruby"
gem "possessive"
# Strip whitespace from active record attributes
gem "auto_strip_attributes"
# Background job processing
gem 'resque', '~> 2.4'
# Use sidekiq for background processing
gem "sidekiq"
gem 'wisper', '~> 2.0'
group :development, :test do

175
Gemfile.lock

@ -13,67 +13,67 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (7.0.3.1)
actionpack (= 7.0.3.1)
activesupport (= 7.0.3.1)
actioncable (7.0.4)
actionpack (= 7.0.4)
activesupport (= 7.0.4)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
actionmailbox (7.0.3.1)
actionpack (= 7.0.3.1)
activejob (= 7.0.3.1)
activerecord (= 7.0.3.1)
activestorage (= 7.0.3.1)
activesupport (= 7.0.3.1)
actionmailbox (7.0.4)
actionpack (= 7.0.4)
activejob (= 7.0.4)
activerecord (= 7.0.4)
activestorage (= 7.0.4)
activesupport (= 7.0.4)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.0.3.1)
actionpack (= 7.0.3.1)
actionview (= 7.0.3.1)
activejob (= 7.0.3.1)
activesupport (= 7.0.3.1)
actionmailer (7.0.4)
actionpack (= 7.0.4)
actionview (= 7.0.4)
activejob (= 7.0.4)
activesupport (= 7.0.4)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.0)
actionpack (7.0.3.1)
actionview (= 7.0.3.1)
activesupport (= 7.0.3.1)
actionpack (7.0.4)
actionview (= 7.0.4)
activesupport (= 7.0.4)
rack (~> 2.0, >= 2.2.0)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (7.0.3.1)
actionpack (= 7.0.3.1)
activerecord (= 7.0.3.1)
activestorage (= 7.0.3.1)
activesupport (= 7.0.3.1)
actiontext (7.0.4)
actionpack (= 7.0.4)
activerecord (= 7.0.4)
activestorage (= 7.0.4)
activesupport (= 7.0.4)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.0.3.1)
activesupport (= 7.0.3.1)
actionview (7.0.4)
activesupport (= 7.0.4)
builder (~> 3.1)
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (7.0.3.1)
activesupport (= 7.0.3.1)
activejob (7.0.4)
activesupport (= 7.0.4)
globalid (>= 0.3.6)
activemodel (7.0.3.1)
activesupport (= 7.0.3.1)
activerecord (7.0.3.1)
activemodel (= 7.0.3.1)
activesupport (= 7.0.3.1)
activestorage (7.0.3.1)
actionpack (= 7.0.3.1)
activejob (= 7.0.3.1)
activerecord (= 7.0.3.1)
activesupport (= 7.0.3.1)
activemodel (7.0.4)
activesupport (= 7.0.4)
activerecord (7.0.4)
activemodel (= 7.0.4)
activesupport (= 7.0.4)
activestorage (7.0.4)
actionpack (= 7.0.4)
activejob (= 7.0.4)
activerecord (= 7.0.4)
activesupport (= 7.0.4)
marcel (~> 1.0)
mini_mime (>= 1.1.0)
activesupport (7.0.3.1)
activesupport (7.0.4)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
@ -84,8 +84,8 @@ GEM
auto_strip_attributes (2.6.0)
activerecord (>= 4.0)
aws-eventstream (1.2.0)
aws-partitions (1.627.0)
aws-sdk-core (3.143.0)
aws-partitions (1.638.0)
aws-sdk-core (3.156.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1)
@ -127,7 +127,7 @@ GEM
rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
capybara-lockstep (1.1.1)
capybara-lockstep (1.2.1)
activesupport (>= 3.2)
capybara (>= 2.0)
ruby2_keywords
@ -135,7 +135,7 @@ GEM
childprocess (4.1.0)
coderay (1.1.3)
concurrent-ruby (1.1.10)
connection_pool (2.2.5)
connection_pool (2.3.0)
crack (0.4.5)
rexml
crass (1.0.6)
@ -161,7 +161,7 @@ GEM
rubocop
smart_properties
erubi (1.11.0)
excon (0.92.4)
excon (0.92.5)
factory_bot (6.2.1)
activesupport (>= 5.0.0)
factory_bot_rails (6.2.0)
@ -203,7 +203,7 @@ GEM
listen (3.7.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
loofah (2.18.0)
loofah (2.19.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
@ -214,11 +214,7 @@ GEM
method_source (1.0.0)
mini_mime (1.1.2)
minitest (5.16.3)
mono_logger (1.1.1)
msgpack (1.5.6)
multi_json (1.15.0)
mustermann (3.0.0)
ruby2_keywords (~> 0.0.1)
net-imap (0.2.3)
digest
net-protocol
@ -229,17 +225,11 @@ GEM
timeout
net-protocol (0.1.3)
timeout
net-smtp (0.3.1)
digest
net-smtp (0.3.2)
net-protocol
timeout
nio4r (2.5.8)
nokogiri (1.13.8-arm64-darwin)
racc (~> 1.4)
nokogiri (1.13.8-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.13.8-x86_64-linux)
racc (~> 1.4)
notifications-ruby-client (5.3.0)
jwt (>= 1.5, < 3)
orm_adapter (0.5.0)
@ -280,34 +270,32 @@ GEM
rack (2.2.4)
rack-attack (6.6.1)
rack (>= 1.0, < 3)
rack-mini-profiler (2.3.4)
rack-mini-profiler (3.0.0)
rack (>= 1.2.0)
rack-protection (3.0.1)
rack
rack-test (2.0.2)
rack (>= 1.3)
rails (7.0.3.1)
actioncable (= 7.0.3.1)
actionmailbox (= 7.0.3.1)
actionmailer (= 7.0.3.1)
actionpack (= 7.0.3.1)
actiontext (= 7.0.3.1)
actionview (= 7.0.3.1)
activejob (= 7.0.3.1)
activemodel (= 7.0.3.1)
activerecord (= 7.0.3.1)
activestorage (= 7.0.3.1)
activesupport (= 7.0.3.1)
rails (7.0.4)
actioncable (= 7.0.4)
actionmailbox (= 7.0.4)
actionmailer (= 7.0.4)
actionpack (= 7.0.4)
actiontext (= 7.0.4)
actionview (= 7.0.4)
activejob (= 7.0.4)
activemodel (= 7.0.4)
activerecord (= 7.0.4)
activestorage (= 7.0.4)
activesupport (= 7.0.4)
bundler (>= 1.15.0)
railties (= 7.0.3.1)
railties (= 7.0.4)
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
rails-html-sanitizer (1.4.3)
loofah (~> 2.3)
railties (7.0.3.1)
actionpack (= 7.0.3.1)
activesupport (= 7.0.3.1)
railties (7.0.4)
actionpack (= 7.0.4)
activesupport (= 7.0.4)
method_source
rake (>= 12.2)
thor (~> 1.0)
@ -319,23 +307,16 @@ GEM
rb-inotify (0.10.1)
ffi (~> 1.0)
redcarpet (3.5.1)
redis (5.0.3)
redis-client (>= 0.7.4)
redis-client (0.8.0)
redis (5.0.5)
redis-client (>= 0.9.0)
redis-client (0.9.0)
connection_pool
redis-namespace (1.9.0)
redis (>= 4)
regexp_parser (2.5.0)
regexp_parser (2.6.0)
request_store (1.5.1)
rack (>= 1.4)
responders (3.0.1)
actionpack (>= 5.0)
railties (>= 5.0)
resque (2.4.0)
mono_logger (~> 1.0)
multi_json (~> 1.0)
redis-namespace (~> 1.6)
sinatra (>= 0.9.2)
rexml (3.2.5)
roo (2.9.0)
nokogiri (~> 1)
@ -343,7 +324,7 @@ GEM
rotp (6.2.0)
rspec-core (3.11.0)
rspec-support (~> 3.11.0)
rspec-expectations (3.11.0)
rspec-expectations (3.11.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.11.0)
rspec-mocks (3.11.1)
@ -357,7 +338,7 @@ GEM
rspec-expectations (~> 3.10)
rspec-mocks (~> 3.10)
rspec-support (~> 3.10)
rspec-support (3.11.0)
rspec-support (3.11.1)
rubocop (1.25.0)
parallel (~> 1.10)
parser (>= 3.1.0.0)
@ -375,7 +356,7 @@ GEM
rubocop-rails (= 2.13.2)
rubocop-rake (= 0.6.0)
rubocop-rspec (= 2.7.0)
rubocop-performance (1.14.3)
rubocop-performance (1.15.0)
rubocop (>= 1.7.0, < 2.0)
rubocop-ast (>= 0.4.0)
rubocop-rails (2.13.2)
@ -399,30 +380,28 @@ GEM
sentry-ruby (~> 5.4.2)
sentry-ruby (5.4.2)
concurrent-ruby (~> 1.0, >= 1.0.2)
sidekiq (6.5.5)
connection_pool (>= 2.2.2)
rack (~> 2.0)
redis (>= 4.5.0)
simplecov (0.21.2)
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3)
simplecov_json_formatter (0.1.4)
sinatra (3.0.1)
mustermann (~> 3.0)
rack (~> 2.2, >= 2.2.4)
rack-protection (= 3.0.1)
tilt (~> 2.0)
smart_properties (1.17.0)
stackprof (0.2.21)
stimulus-rails (1.1.0)
railties (>= 6.0.0)
strscan (3.0.4)
thor (1.2.1)
tilt (2.0.11)
timecop (0.9.5)
timeout (0.3.0)
tzinfo (2.0.5)
concurrent-ruby (~> 1.0)
uk_postcode (2.1.8)
unicode-display_width (2.2.0)
unicode-display_width (2.3.0)
uniform_notifier (1.16.0)
view_component (2.69.0)
activesupport (>= 5.0.0, < 8.0)
@ -450,11 +429,7 @@ GEM
zeitwerk (2.6.0)
PLATFORMS
arm64-darwin-21
x86_64-darwin-19
x86_64-darwin-20
x86_64-darwin-21
x86_64-linux
DEPENDENCIES
auto_strip_attributes
@ -493,7 +468,6 @@ DEPENDENCIES
rack-mini-profiler
rails (~> 7.0.2)
redis
resque (~> 2.4)
roo
rspec-rails
rubocop-govuk (= 4.3.0)
@ -502,6 +476,7 @@ DEPENDENCIES
selenium-webdriver
sentry-rails
sentry-ruby
sidekiq
simplecov
stackprof
stimulus-rails
@ -518,4 +493,4 @@ RUBY VERSION
ruby 3.1.2p20
BUNDLED WITH
2.3.14
2.3.22

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

1
app/models/bulk_upload.rb

@ -184,7 +184,6 @@ class BulkUpload
rent_type: row[130],
irproduct_other: row[131],
# data_protection: row[132],
sale_or_letting: "letting",
declaration: 1,
}
end

12
app/models/derived_variables/lettings_log_variables.rb

@ -58,16 +58,12 @@ module DerivedVariables::LettingsLogVariables
self.hhtype = household_type
self.new_old = new_or_existing_tenant
self.vacdays = property_vacant_days
if is_supported_housing?
if location
self.wchair = location.mobility_type_before_type_cast == "W" ? 1 : 2
end
if is_renewal?
self.voiddate = startdate
end
if is_supported_housing? && location
self.wchair = location.mobility_type_before_type_cast == "W" ? 1 : 2
end
self.voiddate = startdate if is_renewal?
self.vacdays = property_vacant_days
set_housingneeds_fields if housingneeds?
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 = {

16
app/models/form/sales/pages/age1.rb

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

11
app/models/form/sales/questions/age1.rb

@ -0,0 +1,11 @@
class Form::Sales::Questions::Age1 < ::Form::Question
def initialize(id, hsh, page)
super
@id = "age1"
@check_answer_label = "Lead buyer’s age"
@header = "Age"
@type = "numeric"
@page = page
@width = 2
end
end

21
app/models/form/sales/questions/buyer1_age_known.rb

@ -0,0 +1,21 @@
class Form::Sales::Questions::Buyer1AgeKnown < ::Form::Question
def initialize(id, hsh, page)
super
@id = "age1_known"
@check_answer_label = "Buyer 1’s age"
@header = "Do you know buyer 1’s age?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@page = page
@hint_text = "Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest."
@conditional_for = {
"age1" => [0],
}
end
ANSWER_OPTIONS = {
"0" => { "value" => "Yes" },
"1" => { "value" => "No" },
"2" => { "value" => "Buyer prefers not to say" },
}.freeze
end

17
app/models/form/sales/questions/buyer1_live_in_property.rb

@ -0,0 +1,17 @@
class Form::Sales::Questions::Buyer1LiveInProperty < ::Form::Question
def initialize(id, hsh, page)
super
@id = "buy1livein"
@check_answer_label = "Will buyer 1 live in the property?"
@header = "Will buyer 1 live in the property?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@page = page
@hint_text = "Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest."
end
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
}.freeze
end

16
app/models/form/sales/questions/buyer_live.rb

@ -0,0 +1,16 @@
class Form::Sales::Questions::BuyerLive < ::Form::Question
def initialize(id, hsh, page)
super
@id = "buylivein"
@check_answer_label = "Buyers living in property"
@header = "Will the buyers live in the property?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@page = page
end
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
}.freeze
end

21
app/models/form/sales/questions/discounted_ownership_type.rb

@ -0,0 +1,21 @@
class Form::Sales::Questions::DiscountedOwnershipType < ::Form::Question
def initialize(id, hsh, page)
super
@id = "type"
@check_answer_label = "Type of discounted ownership sale"
@header = "What is the type of discounted ownership sale?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@page = page
end
ANSWER_OPTIONS = {
"8" => { "value" => "Right to Aquire (RTA)" },
"14" => { "value" => "Preserved Right to Buy (PRTB)" },
"27" => { "value" => "Voluntary Right to Buy (VRTB)" },
"9" => { "value" => "Right to Buy (RTB)" },
"29" => { "value" => "Rent to Buy - Full Ownership" },
"21" => { "value" => "Social HomeBuy for outright purchase" },
"22" => { "value" => "Any other equity loan scheme" },
}.freeze
end

19
app/models/form/sales/questions/gender_identity1.rb

@ -0,0 +1,19 @@
class Form::Sales::Questions::GenderIdentity1 < ::Form::Question
def initialize(id, hsh, page)
super
@id = "sex1"
@check_answer_label = "Buyer 1’s gender identity"
@header = "Which of these best describes buyer 1’s gender identity?"
@type = "radio"
@hint_text = "Buyer 1 is the person in the household who does the most paid work. If it’s a joint purchase and the buyers do the same amount of paid work, buyer 1 is whoever is the oldest."
@page = page
@answer_options = ANSWER_OPTIONS
end
ANSWER_OPTIONS = {
"F" => { "value" => "Female" },
"M" => { "value" => "Male" },
"X" => { "value" => "Non-binary" },
"R" => { "value" => "Prefers not to say " },
}.freeze
end

17
app/models/form/sales/questions/joint_purchase.rb

@ -0,0 +1,17 @@
class Form::Sales::Questions::JointPurchase < ::Form::Question
def initialize(id, hsh, page)
super
@id = "jointpur"
@check_answer_label = "Joint purchase"
@header = "Is this a joint purchase?"
@hint_text = ""
@type = "radio"
@answer_options = ANSWER_OPTIONS
@page = page
end
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
}.freeze
end

18
app/models/form/sales/questions/number_joint_buyers.rb

@ -0,0 +1,18 @@
class Form::Sales::Questions::NumberJointBuyers < ::Form::Question
def initialize(id, hsh, page)
super
@id = "jointmore"
@check_answer_label = "More than 2 joint buyers"
@header = "Are there more than 2 joint buyers of this property?"
@hint_text = "You should still try to answer all questions even if the buyer wasn't interviewed in person"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@page = page
end
ANSWER_OPTIONS = {
"1" => { "value" => "Yes" },
"2" => { "value" => "No" },
"3" => { "value" => "Don’t know" },
}.freeze
end

11
app/models/form/sales/questions/other_ownership_type.rb

@ -0,0 +1,11 @@
class Form::Sales::Questions::OtherOwnershipType < ::Form::Question
def initialize(id, hsh, page)
super
@id = "othtype"
@check_answer_label = "Type of other sale"
@header = "What type of sale is it?"
@type = "text"
@width = 10
@page = page
end
end

19
app/models/form/sales/questions/outright_ownership_type.rb

@ -0,0 +1,19 @@
class Form::Sales::Questions::OutrightOwnershipType < ::Form::Question
def initialize(id, hsh, page)
super
@id = "type"
@check_answer_label = "Type of outright sale"
@header = "What is the type of outright sale?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@page = page
@conditional_for = {
"othtype" => [12],
}
end
ANSWER_OPTIONS = {
"10" => { "value" => "Outright" },
"12" => { "value" => "Other sale" },
}.freeze
end

17
app/models/form/sales/questions/ownership_scheme.rb

@ -0,0 +1,17 @@
class Form::Sales::Questions::OwnershipScheme < ::Form::Question
def initialize(id, hsh, page)
super
@id = "ownershipsch"
@check_answer_label = "Purchase made under ownership scheme"
@header = "Was this purchase made through an ownership scheme?"
@type = "radio"
@answer_options = ANSWER_OPTIONS
@page = page
end
ANSWER_OPTIONS = {
"1" => { "value" => "Yes - a shared ownership scheme" },
"2" => { "value" => "Yes - a discounted ownership scheme" },
"3" => { "value" => "No - this is an outright or other sale" },
}.freeze
end

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

@ -0,0 +1,12 @@
class Form::Sales::Questions::PropertyNumberOfBedrooms < ::Form::Question
def initialize(id, hsh, page)
super
@id = "beds"
@check_answer_label = "Number of bedrooms"
@header = "How many bedrooms does the property have?"
@hint_text = "A bedsit has 1 bedroom"
@type = "text"
@width = 10
@page = page
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/household.rb

@ -0,0 +1,10 @@
class Form::Sales::Sections::Household < ::Form::Section
def initialize(id, hsh, form)
super
@id = "household"
@label = "About the household"
@description = ""
@form = form
@subsections = [Form::Sales::Subsections::HouseholdCharacteristics.new(nil, nil, self)] || []
end
end

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

@ -0,0 +1,10 @@
class Form::Sales::Sections::PropertyInformation < ::Form::Section
def initialize(id, hsh, form)
super
@id = "property_information"
@label = "Property information"
@description = ""
@form = form
@subsections = [Form::Sales::Subsections::PropertyInformation.new(nil, nil, self)] || []
end
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

17
app/models/form/sales/subsections/household_characteristics.rb

@ -0,0 +1,17 @@
class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection
def initialize(id, hsh, section)
super
@id = "household_characteristics"
@label = "Household characteristics"
@section = section
@depends_on = [{ "setup" => "completed" }]
end
def pages
@pages ||= [
Form::Sales::Pages::Age1.new(nil, nil, self),
Form::Sales::Pages::GenderIdentity1.new(nil, nil, self),
Form::Sales::Pages::Buyer1LiveInProperty.new(nil, nil, self),
]
end
end

15
app/models/form/sales/subsections/property_information.rb

@ -0,0 +1,15 @@
class Form::Sales::Subsections::PropertyInformation < ::Form::Subsection
def initialize(id, hsh, section)
super
@id = "property_information"
@label = "Property information"
@section = section
@depends_on = [{ "setup" => "completed" }]
end
def pages
@pages ||= [
Form::Sales::Pages::PropertyNumberOfBedrooms.new(nil, nil, self),
]
end
end

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

@ -0,0 +1,24 @@
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::OwnershipScheme.new(nil, nil, self),
Form::Sales::Pages::SharedOwnershipType.new(nil, nil, self),
Form::Sales::Pages::DiscountedOwnershipType.new(nil, nil, self),
Form::Sales::Pages::OutrightOwnershipType.new(nil, nil, self),
Form::Sales::Pages::BuyerLive.new(nil, nil, self),
Form::Sales::Pages::JointPurchase.new(nil, nil, self),
Form::Sales::Pages::NumberJointBuyers.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

44
app/models/form_handler.rb

@ -10,23 +10,55 @@ 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 = [
Form::Sales::Sections::PropertyInformation,
Form::Sales::Sections::Household,
]
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

5
app/models/legacy_user.rb

@ -0,0 +1,5 @@
class LegacyUser < ApplicationRecord
belongs_to :user
validates :old_user_id, uniqueness: true
end

98
app/models/lettings_log.rb

@ -15,7 +15,7 @@ class LettingsLogValidator < ActiveModel::Validator
end
end
class LettingsLog < ApplicationRecord
class LettingsLog < Log
include Validations::SoftValidations
include DerivedVariables::LettingsLogVariables
@ -32,31 +32,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(' ')}%") }
@ -73,22 +53,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!
@ -99,7 +69,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
@ -530,44 +500,18 @@ class LettingsLog < ApplicationRecord
location.type_of_unit_before_type_cast if location
end
def lettings?
true
end
def rent_type_detail
form.get_question("rent_type", self)&.label_from_value(rent_type)
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 }],
@ -585,12 +529,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
@ -598,11 +536,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
@ -695,17 +632,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 = %w[purchid].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

17
app/models/user.rb

@ -7,8 +7,11 @@ 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
has_many :legacy_users
validates :name, presence: true
validates :email, presence: true
@ -58,6 +61,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
@ -103,7 +114,7 @@ class User < ApplicationRecord
end
def was_migrated_from_softwire?
old_user_id.present?
legacy_users.any? || old_user_id.present?
end
def send_confirmation_instructions
@ -131,7 +142,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

12
app/models/validations/date_validations.rb

@ -53,26 +53,22 @@ module Validations::DateValidations
end
end
def validate_sale_completion_date(record)
date_valid?("sale_completion_date", record)
end
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)

40
app/services/csv/lettings_log_csv_service.rb

@ -1,6 +1,6 @@
module Csv
class LettingsLogCsvService
CSV_FIELDS_TO_OMIT = %w[hhmemb net_income_value_check sale_or_letting first_time_property_let_as_social_housing renttype needstype postcode_known is_la_inferred totchild totelder totadult net_income_known is_carehome previous_la_known is_previous_la_inferred age1_known age2_known age3_known age4_known age5_known age6_known age7_known age8_known letting_allocation_unknown details_known_2 details_known_3 details_known_4 details_known_5 details_known_6 details_known_7 details_known_8 rent_type wrent wscharge wpschrge wsupchrg wtcharge wtshortfall rent_value_check old_form_id old_id retirement_value_check tshortfall_known pregnancy_value_check hhtype new_old vacdays].freeze
CSV_FIELDS_TO_OMIT = %w[hhmemb net_income_value_check first_time_property_let_as_social_housing renttype needstype postcode_known is_la_inferred totchild totelder totadult net_income_known is_carehome previous_la_known is_previous_la_inferred age1_known age2_known age3_known age4_known age5_known age6_known age7_known age8_known letting_allocation_unknown details_known_2 details_known_3 details_known_4 details_known_5 details_known_6 details_known_7 details_known_8 rent_type_detail wrent wscharge wpschrge wsupchrg wtcharge wtshortfall rent_value_check old_form_id old_id retirement_value_check tshortfall_known pregnancy_value_check hhtype new_old vacdays la prevloc].freeze
def initialize(user)
@user = user
@ -13,13 +13,7 @@ module Csv
LettingsLog.all.find_each do |record|
csv << @attributes.map do |att|
if %w[la prevloc].include? att
label_from_value(record.send(att))
elsif %w[la_label prevloc_label].include? att
record.form.get_question(att.remove("_label"), record)&.label_from_value(record.send(att.remove("_label"))) || label_from_value(record.send(att.remove("_label")))
else
record.form.get_question(att, record)&.label_from_value(record.send(att)) || label_from_value(record.send(att))
end
label_from_value(record, att)
end
end
end
@ -27,7 +21,17 @@ module Csv
private
def label_from_value(value)
def label_from_value(record, att)
if %w[la prevloc].include? att
label_from_boolean_value(record.send(att))
elsif %w[mrcdate startdate voiddate].include? att
record.send(att)&.to_formatted_s(:govuk_date)
else
record.form.get_question(att.remove("_label"), record)&.label_from_value(record.send(att.remove("_label"))) || label_from_boolean_value(record.send(att.remove("_label")))
end
end
def label_from_boolean_value(value)
return "Yes" if value == true
return "No" if value == false
@ -35,7 +39,7 @@ module Csv
end
def set_csv_attributes
metadata_fields = %w[id status created_at updated_at created_by_name is_dpo owning_organisation_name managing_organisation_name]
metadata_fields = %w[id status created_at updated_at created_by_name is_dpo owning_organisation_name managing_organisation_name collection_start_year]
metadata_id_fields = %w[managing_organisation_id owning_organisation_id created_by_id]
scheme_and_location_ids = %w[scheme_id location_id]
scheme_attributes = %w[scheme_code scheme_service_name scheme_sensitive scheme_type scheme_registered_under_care_act scheme_owning_organisation_name scheme_managing_organisation_name scheme_primary_client_group scheme_has_other_client_group scheme_secondary_client_group scheme_support_type scheme_intended_stay scheme_created_at]
@ -45,13 +49,20 @@ module Csv
@attributes = (metadata_fields + intersecting_attributes + remaining_attributes - metadata_id_fields + %w[unittype_sh] + scheme_attributes + location_attributes).uniq
move_la_fields
rename_attributes
@attributes -= CSV_FIELDS_TO_OMIT if @user.present? && !@user.support?
end
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
@ -75,5 +86,12 @@ module Csv
end
end
end
def rename_attributes
{ "rent_type" => "rent_type_detail" }.each do |original_field, new_field|
@attributes.insert(@attributes.find_index(original_field), new_field)
@attributes.delete(original_field)
end
end
end
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

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

Loading…
Cancel
Save