Browse Source

Merge branch 'main' into CLDC-4071-show-telephone-extensions-and-export

CLDC-4071-show-telephone-extensions-and-export
Nat Dean-Lewis 5 days ago
parent
commit
e227eff66a
  1. 8
      Gemfile
  2. 103
      Gemfile.lock
  3. 4
      app/components/bulk_upload_error_row_component.html.erb
  4. 9
      app/components/bulk_upload_error_row_component.rb
  5. 4
      app/components/bulk_upload_error_summary_table_component.html.erb
  6. 3
      app/components/bulk_upload_error_summary_table_component.rb
  7. 16
      app/components/bulk_upload_summary_component.rb
  8. 6
      app/components/check_answers_summary_list_card_component.html.erb
  9. 11
      app/components/check_answers_summary_list_card_component.rb
  10. 18
      app/components/create_log_actions_component.html.erb
  11. 19
      app/components/create_log_actions_component.rb
  12. 2
      app/components/data_protection_confirmation_banner_component.html.erb
  13. 5
      app/components/data_protection_confirmation_banner_component.rb
  14. 2
      app/components/document_list_component.html.erb
  15. 2
      app/components/document_list_component.rb
  16. 2
      app/components/lettings_log_summary_component.html.erb
  17. 2
      app/components/lettings_log_summary_component.rb
  18. 2
      app/components/missing_stock_owners_banner_component.html.erb
  19. 9
      app/components/missing_stock_owners_banner_component.rb
  20. 2
      app/components/primary_navigation_component.html.erb
  21. 2
      app/components/primary_navigation_component.rb
  22. 2
      app/components/sales_log_summary_component.html.erb
  23. 2
      app/components/sales_log_summary_component.rb
  24. 4
      app/components/search_component.html.erb
  25. 2
      app/components/search_component.rb
  26. 2
      app/components/search_result_caption_component.rb
  27. 4
      app/components/sub_navigation_component.html.erb
  28. 2
      app/components/sub_navigation_component.rb
  29. 10
      app/controllers/users_controller.rb
  30. 2
      app/frontend/styles/_filter.scss
  31. 11
      app/frontend/styles/_header.scss
  32. 2
      app/frontend/styles/_related-navigation.scss
  33. 2
      app/frontend/styles/_tag.scss
  34. 2
      app/frontend/styles/_testing-tools.scss
  35. 12
      app/frontend/styles/application.scss
  36. 13
      app/helpers/application_helper.rb
  37. 6
      app/models/forms/bulk_upload_resume/confirm.rb
  38. 6
      app/models/forms/bulk_upload_resume/fix_choice.rb
  39. 17
      app/models/user.rb
  40. 6
      app/services/feature_toggle.rb
  41. 4
      app/views/bulk_upload_lettings_results/show.html.erb
  42. 8
      app/views/bulk_upload_lettings_results/summary.html.erb
  43. 2
      app/views/bulk_upload_lettings_resume/confirm.html.erb
  44. 2
      app/views/bulk_upload_lettings_resume/fix_choice.html.erb
  45. 4
      app/views/bulk_upload_sales_results/show.html.erb
  46. 8
      app/views/bulk_upload_sales_results/summary.html.erb
  47. 2
      app/views/bulk_upload_sales_resume/confirm.html.erb
  48. 2
      app/views/bulk_upload_sales_resume/fix_choice.html.erb
  49. 30
      app/views/layouts/application.html.erb
  50. 18
      app/views/layouts/rails_admin/_navigation.html.erb
  51. 4
      app/views/users/_user_list.html.erb
  52. 4
      config/locales/en.yml
  53. 12
      lib/tasks/fix_sales_logs_with_invalid_initialpurchase_lasttransaction.rake
  54. 4
      package.json
  55. 46
      spec/helpers/application_helper_spec.rb
  56. 47
      spec/models/user_spec.rb
  57. 36
      spec/requests/bulk_upload_lettings_results_controller_spec.rb
  58. 36
      spec/requests/bulk_upload_sales_results_controller_spec.rb
  59. 54
      spec/requests/users_controller_spec.rb
  60. 4
      webpack.config.js
  61. 1377
      yarn.lock

8
Gemfile

@ -10,7 +10,7 @@ gem "rails", "~> 7.2.2"
# Use postgresql as the database for Active Record # Use postgresql as the database for Active Record
gem "pg", "~> 1.1" gem "pg", "~> 1.1"
# Use Puma as the app server # Use Puma as the app server
gem "puma", "~> 6.4" gem "puma", "~> 7.2.1"
# The modern asset pipeline for Rails [https://github.com/rails/propshaft] # The modern asset pipeline for Rails [https://github.com/rails/propshaft]
gem "propshaft" gem "propshaft"
# Bundle and transpile JavaScript [https://github.com/rails/jsbundling-rails] # Bundle and transpile JavaScript [https://github.com/rails/jsbundling-rails]
@ -18,7 +18,7 @@ gem "jsbundling-rails"
# Reduces boot times through caching; required in config/boot.rb # Reduces boot times through caching; required in config/boot.rb
gem "bootsnap", ">= 1.4.4", require: false gem "bootsnap", ">= 1.4.4", require: false
# GOV UK frontend components # GOV UK frontend components
gem "govuk-components", "~> 5.7" gem "govuk-components", "~> 6.2"
# GOV UK component form builder DSL # GOV UK component form builder DSL
gem "govuk_design_system_formbuilder", "~> 5.7" gem "govuk_design_system_formbuilder", "~> 5.7"
# Convert Markdown into GOV.UK frontend-styled HTML # Convert Markdown into GOV.UK frontend-styled HTML
@ -40,7 +40,7 @@ gem "devise_two_factor_authentication"
gem "uk_postcode" gem "uk_postcode"
# Get rich data from postcode lookups. Wraps postcodes.io # Get rich data from postcode lookups. Wraps postcodes.io
# Use Ruby objects to build reusable markup. A React inspired evolution of the presenter pattern # Use Ruby objects to build reusable markup. A React inspired evolution of the presenter pattern
gem "view_component", "~> 3.9" gem "view_component", "~> 4.9"
# Use the AWS S3 SDK as storage mechanism # Use the AWS S3 SDK as storage mechanism
gem "aws-sdk-s3" gem "aws-sdk-s3"
# Track changes to models for auditing or versioning. # Track changes to models for auditing or versioning.
@ -67,7 +67,7 @@ gem "faker"
gem "method_source", "~> 1.1" gem "method_source", "~> 1.1"
gem "rails_admin", "~> 3.1" gem "rails_admin", "~> 3.1"
gem "ruby-openai" gem "ruby-openai"
gem "sidekiq" gem "sidekiq", "~> 7.2.4"
gem "sidekiq-cron" gem "sidekiq-cron"
gem "unread" gem "unread"

103
Gemfile.lock

@ -78,8 +78,8 @@ GEM
minitest (>= 5.1, < 6) minitest (>= 5.1, < 6)
securerandom (>= 0.3) securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5) tzinfo (~> 2.0, >= 2.0.5)
addressable (2.8.6) addressable (2.9.0)
public_suffix (>= 2.0.2, < 6.0) public_suffix (>= 2.0.2, < 8.0)
ast (2.4.3) ast (2.4.3)
auto_strip_attributes (2.6.0) auto_strip_attributes (2.6.0)
activerecord (>= 4.0) activerecord (>= 4.0)
@ -123,7 +123,7 @@ GEM
erubi (~> 1.4) erubi (~> 1.4)
parser (>= 2.4) parser (>= 2.4)
smart_properties smart_properties
bigdecimal (4.0.1) bigdecimal (4.1.2)
bindex (0.8.1) bindex (0.8.1)
bootsnap (1.18.3) bootsnap (1.18.3)
msgpack (~> 1.2) msgpack (~> 1.2)
@ -155,18 +155,21 @@ GEM
coercible (1.0.0) coercible (1.0.0)
descendants_tracker (~> 0.0.1) descendants_tracker (~> 0.0.1)
concurrent-ruby (1.3.6) concurrent-ruby (1.3.6)
connection_pool (2.5.3) connection_pool (2.5.5)
crack (1.0.0) crack (1.0.0)
bigdecimal bigdecimal
rexml rexml
crass (1.0.6) crass (1.0.6)
cronex (0.15.0)
tzinfo
unicode (>= 0.4.4.5)
cssbundling-rails (1.4.0) cssbundling-rails (1.4.0)
railties (>= 6.0.0) railties (>= 6.0.0)
csv (3.3.2) csv (3.3.2)
date (3.5.1) date (3.5.1)
descendants_tracker (0.0.4) descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
devise (5.0.3) devise (5.0.4)
bcrypt (~> 3.0) bcrypt (~> 3.0)
orm_adapter (~> 0.1) orm_adapter (~> 0.1)
railties (>= 7.0) railties (>= 7.0)
@ -187,7 +190,7 @@ GEM
drb (2.2.3) drb (2.2.3)
dumb_delegator (1.0.0) dumb_delegator (1.0.0)
encryptor (3.0.0) encryptor (3.0.0)
erb (6.0.2) erb (6.0.4)
erb_lint (0.9.0) erb_lint (0.9.0)
activesupport activesupport
better_html (>= 2.0.1) better_html (>= 2.0.1)
@ -196,7 +199,7 @@ GEM
rubocop (>= 1) rubocop (>= 1)
smart_properties smart_properties
erubi (1.13.1) erubi (1.13.1)
et-orbi (1.2.11) et-orbi (1.4.0)
tzinfo tzinfo
event_stream_parser (1.0.0) event_stream_parser (1.0.0)
excon (0.111.0) excon (0.111.0)
@ -207,24 +210,24 @@ GEM
railties (>= 5.0.0) railties (>= 5.0.0)
faker (3.2.3) faker (3.2.3)
i18n (>= 1.8.11, < 2) i18n (>= 1.8.11, < 2)
faraday (2.14.1) faraday (2.14.2)
faraday-net_http (>= 2.0, < 3.5) faraday-net_http (>= 2.0, < 3.5)
json json
logger logger
faraday-multipart (1.0.4) faraday-multipart (1.0.4)
multipart-post (~> 2) multipart-post (~> 2)
faraday-net_http (3.1.0) faraday-net_http (3.4.3)
net-http net-http (~> 0.5)
ffi (1.16.3) ffi (1.16.3)
fugit (1.11.1) fugit (1.12.2)
et-orbi (~> 1, >= 1.2.11) et-orbi (~> 1.4)
raabro (~> 1.4) raabro (~> 1.4)
globalid (1.2.1) globalid (1.3.0)
activesupport (>= 6.1) activesupport (>= 6.1)
govuk-components (5.7.0) govuk-components (6.2.0)
html-attributes-utils (~> 1.0.0, >= 1.0.0) html-attributes-utils (~> 1.0.0, >= 1.0.0)
pagy (>= 6, < 10) pagy (>= 6, < 10)
view_component (>= 3.9, < 3.17) view_component (>= 4.9, < 4.10)
govuk_design_system_formbuilder (5.7.1) govuk_design_system_formbuilder (5.7.1)
actionview (>= 6.1) actionview (>= 6.1)
activemodel (>= 6.1) activemodel (>= 6.1)
@ -241,7 +244,7 @@ GEM
ice_nine (0.11.2) ice_nine (0.11.2)
iniparse (1.5.0) iniparse (1.5.0)
io-console (0.8.2) io-console (0.8.2)
irb (1.17.0) irb (1.18.0)
pp (>= 0.6.0) pp (>= 0.6.0)
prism (>= 1.3.0) prism (>= 1.3.0)
rdoc (>= 4.0.0) rdoc (>= 4.0.0)
@ -249,10 +252,10 @@ GEM
jmespath (1.6.2) jmespath (1.6.2)
jsbundling-rails (1.3.0) jsbundling-rails (1.3.0)
railties (>= 6.0.0) railties (>= 6.0.0)
json (2.19.2) json (2.19.8)
json-schema (4.1.1) json-schema (4.1.1)
addressable (>= 2.8) addressable (>= 2.8)
jwt (2.8.0) jwt (3.2.0)
base64 base64
kaminari (1.2.2) kaminari (1.2.2)
activesupport (>= 4.1.0) activesupport (>= 4.1.0)
@ -290,9 +293,9 @@ GEM
msgpack (1.7.2) msgpack (1.7.2)
multipart-post (2.4.1) multipart-post (2.4.1)
nested_form (0.3.2) nested_form (0.3.2)
net-http (0.4.1) net-http (0.9.1)
uri uri (>= 0.11.1)
net-imap (0.5.7) net-imap (0.6.4)
date date
net-protocol net-protocol
net-pop (0.1.2) net-pop (0.1.2)
@ -301,23 +304,23 @@ GEM
timeout timeout
net-smtp (0.5.1) net-smtp (0.5.1)
net-protocol net-protocol
nio4r (2.7.4) nio4r (2.7.5)
nokogiri (1.19.1-arm64-darwin) nokogiri (1.19.3-arm64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.19.1-x86_64-darwin) nokogiri (1.19.3-x86_64-darwin)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.19.1-x86_64-linux-gnu) nokogiri (1.19.3-x86_64-linux-gnu)
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.19.1-x86_64-linux-musl) nokogiri (1.19.3-x86_64-linux-musl)
racc (~> 1.4) racc (~> 1.4)
notifications-ruby-client (6.0.0) notifications-ruby-client (6.4.0)
jwt (>= 1.5, < 3) jwt (>= 1.5, < 4)
orm_adapter (0.5.0) orm_adapter (0.5.0)
overcommit (0.63.0) overcommit (0.63.0)
childprocess (>= 0.6.3, < 6) childprocess (>= 0.6.3, < 6)
iniparse (~> 1.4) iniparse (~> 1.4)
rexml (~> 3.2) rexml (~> 3.2)
pagy (9.3.2) pagy (9.4.0)
paper_trail (15.2.0) paper_trail (15.2.0)
activerecord (>= 6.1) activerecord (>= 6.1)
request_store (~> 1.4) request_store (~> 1.4)
@ -350,19 +353,19 @@ GEM
psych (5.3.1) psych (5.3.1)
date date
stringio stringio
public_suffix (5.0.4) public_suffix (7.0.5)
puma (6.5.0) puma (7.2.1)
nio4r (~> 2.0) nio4r (~> 2.0)
pundit (2.3.1) pundit (2.3.1)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
raabro (1.4.0) raabro (1.4.0)
racc (1.8.1) racc (1.8.1)
rack (3.1.20) rack (3.1.21)
rack-attack (6.7.0) rack-attack (6.7.0)
rack (>= 1.0, < 4) rack (>= 1.0, < 4)
rack-mini-profiler (3.3.1) rack-mini-profiler (3.3.1)
rack (>= 1.2.0) rack (>= 1.2.0)
rack-session (2.1.1) rack-session (2.1.2)
base64 (>= 0.1.0) base64 (>= 0.1.0)
rack (>= 3.0.0) rack (>= 3.0.0)
rack-test (2.2.0) rack-test (2.2.0)
@ -408,7 +411,7 @@ GEM
tsort (>= 0.2) tsort (>= 0.2)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
rainbow (3.1.1) rainbow (3.1.1)
rake (13.3.1) rake (13.4.2)
randexp (0.1.7) randexp (0.1.7)
rb-fsevent (0.11.2) rb-fsevent (0.11.2)
rb-inotify (0.10.1) rb-inotify (0.10.1)
@ -419,7 +422,7 @@ GEM
tsort tsort
redcarpet (3.6.0) redcarpet (3.6.0)
redis (4.8.1) redis (4.8.1)
redis-client (0.22.1) redis-client (0.29.0)
connection_pool connection_pool
regexp_parser (2.11.3) regexp_parser (2.11.3)
reline (0.6.3) reline (0.6.3)
@ -513,10 +516,11 @@ GEM
connection_pool (>= 2.3.0) connection_pool (>= 2.3.0)
rack (>= 2.2.4) rack (>= 2.2.4)
redis-client (>= 0.19.0) redis-client (>= 0.19.0)
sidekiq-cron (1.12.0) sidekiq-cron (2.4.0)
fugit (~> 1.8) cronex (>= 0.13.0)
fugit (~> 1.8, >= 1.11.1)
globalid (>= 1.0.1) globalid (>= 1.0.1)
sidekiq (>= 6) sidekiq (>= 6.5.0)
simplecov (0.22.0) simplecov (0.22.0)
docile (~> 1.1) docile (~> 1.1)
simplecov-html (~> 0.11) simplecov-html (~> 0.11)
@ -530,7 +534,7 @@ GEM
thor (1.4.0) thor (1.4.0)
thread_safe (0.3.6) thread_safe (0.3.6)
timecop (0.9.8) timecop (0.9.8)
timeout (0.4.3) timeout (0.6.1)
tsort (0.2.0) tsort (0.2.0)
turbo-rails (2.0.13) turbo-rails (2.0.13)
actionpack (>= 7.1.0) actionpack (>= 7.1.0)
@ -538,17 +542,18 @@ GEM
tzinfo (2.0.6) tzinfo (2.0.6)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
uk_postcode (2.1.8) uk_postcode (2.1.8)
unicode (0.4.4.5)
unicode-display_width (3.2.0) unicode-display_width (3.2.0)
unicode-emoji (~> 4.1) unicode-emoji (~> 4.1)
unicode-emoji (4.2.0) unicode-emoji (4.2.0)
unread (0.14.0) unread (0.14.0)
activerecord (>= 6.1) activerecord (>= 6.1)
uri (1.0.4) uri (1.1.1)
useragent (0.16.11) useragent (0.16.11)
view_component (3.10.0) view_component (4.9.0)
activesupport (>= 5.2.0, < 8.0) actionview (>= 7.1.0)
concurrent-ruby (~> 1.0) activesupport (>= 7.1.0)
method_source (~> 1.0) concurrent-ruby (~> 1)
virtus (2.0.0) virtus (2.0.0)
axiom-types (~> 0.1) axiom-types (~> 0.1)
coercible (~> 1.0) coercible (~> 1.0)
@ -571,7 +576,7 @@ GEM
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
xpath (3.2.0) xpath (3.2.0)
nokogiri (~> 1.8) nokogiri (~> 1.8)
zeitwerk (2.7.5) zeitwerk (2.8.2)
PLATFORMS PLATFORMS
arm64-darwin arm64-darwin
@ -599,7 +604,7 @@ DEPENDENCIES
factory_bot_rails factory_bot_rails
faker faker
faraday (>= 2.14.1) faraday (>= 2.14.1)
govuk-components (~> 5.7) govuk-components (~> 6.2)
govuk_design_system_formbuilder (~> 5.7) govuk_design_system_formbuilder (~> 5.7)
govuk_markdown govuk_markdown
jsbundling-rails jsbundling-rails
@ -616,7 +621,7 @@ DEPENDENCIES
possessive possessive
propshaft propshaft
pry-byebug pry-byebug
puma (~> 6.4) puma (~> 7.2.1)
pundit pundit
rack (~> 3.1.20) rack (~> 3.1.20)
rack-attack rack-attack
@ -634,7 +639,7 @@ DEPENDENCIES
selenium-webdriver selenium-webdriver
sentry-rails sentry-rails
sentry-ruby sentry-ruby
sidekiq sidekiq (~> 7.2.4)
sidekiq-cron sidekiq-cron
simplecov simplecov
stimulus-rails stimulus-rails
@ -643,7 +648,7 @@ DEPENDENCIES
tzinfo-data tzinfo-data
uk_postcode uk_postcode
unread unread
view_component (~> 3.9) view_component (~> 4.9)
web-console (>= 4.1.0) web-console (>= 4.1.0)
webmock webmock

4
app/components/bulk_upload_error_row_component.html.erb

@ -13,7 +13,7 @@
<% if critical_errors.any? %> <% if critical_errors.any? %>
<h2 class="govuk-heading-m">Critical errors</h2> <h2 class="govuk-heading-m">Critical errors</h2>
<p class="govuk-body">These errors must be fixed to complete your logs.</p> <p class="govuk-body">These errors must be fixed to complete your logs.</p>
<%= govuk_table(html_attributes: { class: potential_errors.any? ? "" : "no-bottom-border" }) do |table| %> <%= helpers.govuk_table(html_attributes: { class: potential_errors.any? ? "" : "no-bottom-border" }) do |table| %>
<%= table.with_head do |head| %> <%= table.with_head do |head| %>
<% head.with_row do |row| %> <% head.with_row do |row| %>
<% row.with_cell(header: true, text: "Cell") %> <% row.with_cell(header: true, text: "Cell") %>
@ -39,7 +39,7 @@
<% if potential_errors.any? %> <% if potential_errors.any? %>
<h2 class="govuk-heading-m">Confirmation needed</h2> <h2 class="govuk-heading-m">Confirmation needed</h2>
<p class="govuk-body">Potential data discrepancies exist in the following cells.<br><br>Please resolve all critical errors and review the cells with data discrepancies before re-uploading the file. Bulk confirmation of potential discrepancies is accessible only after all critical errors have been resolved.</p> <p class="govuk-body">Potential data discrepancies exist in the following cells.<br><br>Please resolve all critical errors and review the cells with data discrepancies before re-uploading the file. Bulk confirmation of potential discrepancies is accessible only after all critical errors have been resolved.</p>
<%= govuk_table(html_attributes: { class: "no-bottom-border" }) do |table| %> <%= helpers.govuk_table(html_attributes: { class: "no-bottom-border" }) do |table| %>
<%= table.with_head do |head| %> <%= table.with_head do |head| %>
<% head.with_row do |row| %> <% head.with_row do |row| %>
<% row.with_cell(header: true, text: "Cell") %> <% row.with_cell(header: true, text: "Cell") %>

9
app/components/bulk_upload_error_row_component.rb

@ -2,9 +2,8 @@ class BulkUploadErrorRowComponent < ViewComponent::Base
attr_reader :bulk_upload_errors attr_reader :bulk_upload_errors
def initialize(bulk_upload_errors:) def initialize(bulk_upload_errors:)
super()
@bulk_upload_errors = bulk_upload_errors @bulk_upload_errors = bulk_upload_errors
super
end end
def row def row
@ -18,7 +17,7 @@ class BulkUploadErrorRowComponent < ViewComponent::Base
def tenant_code_html def tenant_code_html
return if tenant_code.blank? return if tenant_code.blank?
content_tag :span, class: "govuk-!-margin-left-3" do helpers.content_tag :span, class: "govuk-!-margin-left-3" do
"Tenant code: #{tenant_code}" "Tenant code: #{tenant_code}"
end end
end end
@ -30,7 +29,7 @@ class BulkUploadErrorRowComponent < ViewComponent::Base
def purchaser_code_html def purchaser_code_html
return if purchaser_code.blank? return if purchaser_code.blank?
content_tag :span, class: "govuk-!-margin-left-3" do helpers.content_tag :span, class: "govuk-!-margin-left-3" do
"Purchaser code: #{purchaser_code}" "Purchaser code: #{purchaser_code}"
end end
end end
@ -42,7 +41,7 @@ class BulkUploadErrorRowComponent < ViewComponent::Base
def property_ref_html def property_ref_html
return if property_ref.blank? return if property_ref.blank?
content_tag :span, class: "govuk-!-margin-left-3" do helpers.content_tag :span, class: "govuk-!-margin-left-3" do
"Property reference: #{property_ref}" "Property reference: #{property_ref}"
end end
end end

4
app/components/bulk_upload_error_summary_table_component.html.erb

@ -3,7 +3,7 @@
</p> </p>
<% sorted_errors.each do |error| %> <% sorted_errors.each do |error| %>
<%= govuk_table do |table| %> <%= helpers.govuk_table do |table| %>
<%= table.with_head do |head| %> <%= table.with_head do |head| %>
<% head.with_row do |row| %> <% head.with_row do |row| %>
<% row.with_cell(text: question_for_field(error[0][1].to_sym), header: true) %> <% row.with_cell(text: question_for_field(error[0][1].to_sym), header: true) %>
@ -13,7 +13,7 @@
<%= table.with_body do |body| %> <%= table.with_body do |body| %>
<% body.with_row do |row| %> <% body.with_row do |row| %>
<% row.with_cell(text: error[0][2].html_safe) %> <% row.with_cell(text: error[0][2].html_safe) %>
<% row.with_cell(text: pluralize(error[1], "error"), numeric: true) %> <% row.with_cell(text: helpers.pluralize(error[1], "error"), numeric: true) %>
<% end %> <% end %>
<% end %> <% end %>
<% end %> <% end %>

3
app/components/bulk_upload_error_summary_table_component.rb

@ -6,9 +6,8 @@ class BulkUploadErrorSummaryTableComponent < ViewComponent::Base
delegate :question_for_field, to: :row_parser_class delegate :question_for_field, to: :row_parser_class
def initialize(bulk_upload:) def initialize(bulk_upload:)
super()
@bulk_upload = bulk_upload @bulk_upload = bulk_upload
super
end end
def sorted_errors def sorted_errors

16
app/components/bulk_upload_summary_component.rb

@ -2,9 +2,9 @@ class BulkUploadSummaryComponent < ViewComponent::Base
attr_reader :bulk_upload attr_reader :bulk_upload
def initialize(bulk_upload:) def initialize(bulk_upload:)
super()
@bulk_upload = bulk_upload @bulk_upload = bulk_upload
@bulk_upload_errors = bulk_upload.bulk_upload_errors @bulk_upload_errors = bulk_upload.bulk_upload_errors
super
end end
def upload_status def upload_status
@ -27,9 +27,9 @@ class BulkUploadSummaryComponent < ViewComponent::Base
return if count.nil? || count <= 0 return if count.nil? || count <= 0
text = count > 1 ? (plural_text || singular_text.pluralize(count)) : singular_text text = count > 1 ? (plural_text || singular_text.pluralize(count)) : singular_text
content_tag(:p, class: "govuk-!-font-size-16 govuk-!-margin-bottom-1") do helpers.content_tag(:p, class: "govuk-!-font-size-16 govuk-!-margin-bottom-1") do
concat(content_tag(:strong, count)) helpers.concat(helpers.content_tag(:strong, count))
concat(" #{text}") helpers.concat(" #{text}")
end end
end end
@ -44,11 +44,11 @@ class BulkUploadSummaryComponent < ViewComponent::Base
end end
def download_lettings_file_link(bulk_upload) def download_lettings_file_link(bulk_upload)
govuk_link_to "Download file", download_lettings_bulk_upload_path(bulk_upload), class: "govuk-link govuk-!-margin-right-2" helpers.govuk_link_to "Download file", download_lettings_bulk_upload_path(bulk_upload), class: "govuk-link govuk-!-margin-right-2"
end end
def download_sales_file_link(bulk_upload) def download_sales_file_link(bulk_upload)
govuk_link_to "Download file", download_sales_bulk_upload_path(bulk_upload), class: "govuk-link govuk-!-margin-right-2" helpers.govuk_link_to "Download file", download_sales_bulk_upload_path(bulk_upload), class: "govuk-link govuk-!-margin-right-2"
end end
def view_error_report_link(bulk_upload) def view_error_report_link(bulk_upload)
@ -61,12 +61,12 @@ class BulkUploadSummaryComponent < ViewComponent::Base
"bulk_upload_#{bulk_upload.log_type}_result_path" "bulk_upload_#{bulk_upload.log_type}_result_path"
end end
govuk_link_to "View error report", send(path, bulk_upload), class: "govuk-link" helpers.govuk_link_to "View error report", helpers.send(path, bulk_upload), class: "govuk-link"
end end
def view_logs_link(bulk_upload) def view_logs_link(bulk_upload)
return unless bulk_upload.status.to_s == "logs_uploaded_with_errors" return unless bulk_upload.status.to_s == "logs_uploaded_with_errors"
govuk_link_to "View logs with errors", send("#{bulk_upload.log_type}_logs_path", bulk_upload_id: [bulk_upload.id]), class: "govuk-link" helpers.govuk_link_to "View logs with errors", helpers.send("#{bulk_upload.log_type}_logs_path", bulk_upload_id: [bulk_upload.id]), class: "govuk-link"
end end
end end

6
app/components/check_answers_summary_list_card_component.html.erb

@ -7,12 +7,12 @@
<% end %> <% end %>
<div class="govuk-summary-card__content"> <div class="govuk-summary-card__content">
<%= govuk_summary_list do |summary_list| %> <%= helpers.govuk_summary_list do |summary_list| %>
<% applicable_questions.each do |question| %> <% applicable_questions.each do |question| %>
<% summary_list.with_row do |row| %> <% summary_list.with_row do |row| %>
<% row.with_key { get_question_label(question) } %> <% row.with_key { get_question_label(question) } %>
<% row.with_value do %> <% row.with_value do %>
<%= simple_format( <%= helpers.simple_format(
get_answer_label(question), get_answer_label(question),
wrapper_tag: "span", wrapper_tag: "span",
class: "govuk-!-margin-right-4", class: "govuk-!-margin-right-4",
@ -21,7 +21,7 @@
<% extra_value = question.get_extra_check_answer_value(log) %> <% extra_value = question.get_extra_check_answer_value(log) %>
<% if extra_value && question.answer_label(log).present? %> <% if extra_value && question.answer_label(log).present? %>
<%= simple_format( <%= helpers.simple_format(
extra_value, extra_value,
wrapper_tag: "span", wrapper_tag: "span",
class: "govuk-!-font-weight-regular app-!-colour-muted", class: "govuk-!-font-weight-regular app-!-colour-muted",

11
app/components/check_answers_summary_list_card_component.rb

@ -2,12 +2,11 @@ class CheckAnswersSummaryListCardComponent < ViewComponent::Base
attr_reader :questions, :log, :user attr_reader :questions, :log, :user
def initialize(questions:, log:, user:, correcting_hard_validation: false) def initialize(questions:, log:, user:, correcting_hard_validation: false)
super()
@questions = questions @questions = questions
@log = log @log = log
@user = user @user = user
@correcting_hard_validation = correcting_hard_validation @correcting_hard_validation = correcting_hard_validation
super
end end
def applicable_questions def applicable_questions
@ -34,16 +33,16 @@ class CheckAnswersSummaryListCardComponent < ViewComponent::Base
def action_href(question, log) def action_href(question, log)
referrer = question.displayed_as_answered?(log) ? "check_answers" : "check_answers_new_answer" referrer = question.displayed_as_answered?(log) ? "check_answers" : "check_answers_new_answer"
send("#{log.log_type}_#{question.page.id}_path", log, referrer:) helpers.send("#{log.log_type}_#{question.page.id}_path", log, referrer:)
end end
def correct_validation_action_href(question, log, _related_question_ids, correcting_hard_validation) def correct_validation_action_href(question, log, _related_question_ids, correcting_hard_validation)
return action_href(question, log) unless correcting_hard_validation return action_href(question, log) unless correcting_hard_validation
if question.displayed_as_answered?(log) if question.displayed_as_answered?(log)
send("#{log.log_type}_confirm_clear_answer_path", log, question_id: question.id) helpers.send("#{log.log_type}_confirm_clear_answer_path", log, question_id: question.id)
else else
send("#{log.log_type}_#{question.page.id}_path", log, referrer: "check_errors", related_question_ids: request.query_parameters["related_question_ids"], original_page_id: request.query_parameters["original_page_id"]) helpers.send("#{log.log_type}_#{question.page.id}_path", log, referrer: "check_errors", related_question_ids: request.query_parameters["related_question_ids"], original_page_id: request.query_parameters["original_page_id"])
end end
end end
@ -56,7 +55,7 @@ private
"govuk-link govuk-link--no-visited-state" "govuk-link govuk-link--no-visited-state"
end end
govuk_link_to question.check_answer_prompt, correct_validation_action_href(question, log, nil, @correcting_hard_validation), class: link_class helpers.govuk_link_to question.check_answer_prompt, correct_validation_action_href(question, log, nil, @correcting_hard_validation), class: link_class
end end
def number_of_buyers def number_of_buyers

18
app/components/create_log_actions_component.html.erb

@ -1,11 +1,11 @@
<div class="govuk-button-group app-filter-toggle <%= "govuk-!-margin-bottom-6" if display_actions? %>"> <div class="govuk-button-group app-filter-toggle <%= "govuk-!-margin-bottom-6" if display_actions? %>">
<% if display_actions? %> <% if display_actions? %>
<%= govuk_button_to create_button_copy, create_button_href, class: "govuk-!-margin-right-3" %> <%= helpers.govuk_button_to create_button_copy, create_button_href, class: "govuk-!-margin-right-3" %>
<% unless user.support? %> <% unless user.support? %>
<%= govuk_button_link_to upload_button_copy, upload_button_href, secondary: true %> <%= helpers.govuk_button_link_to upload_button_copy, upload_button_href, secondary: true %>
<% end %> <% end %>
<% if user.support? %> <% if user.support? %>
<%= govuk_button_link_to view_uploads_button_copy, view_uploads_button_href, secondary: true %> <%= helpers.govuk_button_link_to view_uploads_button_copy, view_uploads_button_href, secondary: true %>
<% end %> <% end %>
<% if FeatureToggle.create_test_logs_enabled? %> <% if FeatureToggle.create_test_logs_enabled? %>
@ -13,42 +13,42 @@
<span class="govuk-tag app-testing-tools__tag">Testing tools</span> <span class="govuk-tag app-testing-tools__tag">Testing tools</span>
<span class="govuk-body govuk-body-s">These tools can only be seen and used in testing environments.</span> <span class="govuk-body govuk-body-s">These tools can only be seen and used in testing environments.</span>
<div> <div>
<%= govuk_button_link_to create_test_log_href, class: "govuk-button" do %> <%= helpers.govuk_button_link_to create_test_log_href, class: "govuk-button" do %>
New <%= current_collection_year_label %> test log New <%= current_collection_year_label %> test log
<svg class="govuk-button__start-icon" xmlns="http://www.w3.org/2000/svg" width="17.5" height="19" viewBox="0 0 33 40" aria-hidden="true" focusable="false"> <svg class="govuk-button__start-icon" xmlns="http://www.w3.org/2000/svg" width="17.5" height="19" viewBox="0 0 33 40" aria-hidden="true" focusable="false">
<path fill="currentColor" d="M0 0h13l20 20-20 20H0l20-20z"></path> <path fill="currentColor" d="M0 0h13l20 20-20 20H0l20-20z"></path>
</svg> </svg>
<% end %> <% end %>
<% if FeatureToggle.allow_future_form_use? %> <% if FeatureToggle.allow_future_form_use? %>
<%= govuk_button_link_to create_next_year_test_log_href, class: "govuk-button" do %> <%= helpers.govuk_button_link_to create_next_year_test_log_href, class: "govuk-button" do %>
New <%= next_collection_year_label %> test log New <%= next_collection_year_label %> test log
<svg class="govuk-button__start-icon" xmlns="http://www.w3.org/2000/svg" width="17.5" height="19" viewBox="0 0 33 40" aria-hidden="true" focusable="false"> <svg class="govuk-button__start-icon" xmlns="http://www.w3.org/2000/svg" width="17.5" height="19" viewBox="0 0 33 40" aria-hidden="true" focusable="false">
<path fill="currentColor" d="M0 0h13l20 20-20 20H0l20-20z"></path> <path fill="currentColor" d="M0 0h13l20 20-20 20H0l20-20z"></path>
</svg> </svg>
<% end %> <% end %>
<% end %> <% end %>
<%= govuk_button_link_to create_setup_test_log_href, class: "govuk-button" do %> <%= helpers.govuk_button_link_to create_setup_test_log_href, class: "govuk-button" do %>
New <%= current_collection_year_label %> test log (setup only) New <%= current_collection_year_label %> test log (setup only)
<svg class="govuk-button__start-icon" xmlns="http://www.w3.org/2000/svg" width="17.5" height="19" viewBox="0 0 33 40" aria-hidden="true" focusable="false"> <svg class="govuk-button__start-icon" xmlns="http://www.w3.org/2000/svg" width="17.5" height="19" viewBox="0 0 33 40" aria-hidden="true" focusable="false">
<path fill="currentColor" d="M0 0h13l20 20-20 20H0l20-20z"></path> <path fill="currentColor" d="M0 0h13l20 20-20 20H0l20-20z"></path>
</svg> </svg>
<% end %> <% end %>
<% if FeatureToggle.allow_future_form_use? %> <% if FeatureToggle.allow_future_form_use? %>
<%= govuk_button_link_to create_next_year_setup_test_log_href, class: "govuk-button" do %> <%= helpers.govuk_button_link_to create_next_year_setup_test_log_href, class: "govuk-button" do %>
New <%= next_collection_year_label %> test log (setup only) New <%= next_collection_year_label %> test log (setup only)
<svg class="govuk-button__start-icon" xmlns="http://www.w3.org/2000/svg" width="17.5" height="19" viewBox="0 0 33 40" aria-hidden="true" focusable="false"> <svg class="govuk-button__start-icon" xmlns="http://www.w3.org/2000/svg" width="17.5" height="19" viewBox="0 0 33 40" aria-hidden="true" focusable="false">
<path fill="currentColor" d="M0 0h13l20 20-20 20H0l20-20z"></path> <path fill="currentColor" d="M0 0h13l20 20-20 20H0l20-20z"></path>
</svg> </svg>
<% end %> <% end %>
<% end %> <% end %>
<%= govuk_button_link_to create_test_bulk_upload_href(2025), class: "govuk-button govuk-button--secondary" do %> <%= helpers.govuk_button_link_to create_test_bulk_upload_href(2025), class: "govuk-button govuk-button--secondary" do %>
25/26 BU test file 25/26 BU test file
<svg class="govuk-button__start-icon bi bi-download" xmlns="http://www.w3.org/2000/svg" width="18" height="19" fill="currentColor" viewBox="0 0 16 16" stroke="currentColor" stroke-width="1.4"> <svg class="govuk-button__start-icon bi bi-download" xmlns="http://www.w3.org/2000/svg" width="18" height="19" fill="currentColor" viewBox="0 0 16 16" stroke="currentColor" stroke-width="1.4">
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5" /> <path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5" />
<path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708z" /> <path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708z" />
</svg> </svg>
<% end %> <% end %>
<%= govuk_button_link_to create_test_bulk_upload_href(2026), class: "govuk-button govuk-button--secondary" do %> <%= helpers.govuk_button_link_to create_test_bulk_upload_href(2026), class: "govuk-button govuk-button--secondary" do %>
26/27 BU test file 26/27 BU test file
<svg class="govuk-button__start-icon bi bi-download" xmlns="http://www.w3.org/2000/svg" width="18" height="19" fill="currentColor" viewBox="0 0 16 16" stroke="currentColor" stroke-width="1.4"> <svg class="govuk-button__start-icon bi bi-download" xmlns="http://www.w3.org/2000/svg" width="18" height="19" fill="currentColor" viewBox="0 0 16 16" stroke="currentColor" stroke-width="1.4">
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5" /> <path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5" />

19
app/components/create_log_actions_component.rb

@ -5,11 +5,10 @@ class CreateLogActionsComponent < ViewComponent::Base
attr_reader :bulk_upload, :user, :log_type attr_reader :bulk_upload, :user, :log_type
def initialize(user:, log_type:, bulk_upload: nil) def initialize(user:, log_type:, bulk_upload: nil)
super()
@bulk_upload = bulk_upload @bulk_upload = bulk_upload
@user = user @user = user
@log_type = log_type @log_type = log_type
super
end end
def display_actions? def display_actions?
@ -24,7 +23,7 @@ class CreateLogActionsComponent < ViewComponent::Base
end end
def create_button_href def create_button_href
send("#{log_type}_logs_path") helpers.send("#{log_type}_logs_path")
end end
def upload_button_copy def upload_button_copy
@ -32,23 +31,23 @@ class CreateLogActionsComponent < ViewComponent::Base
end end
def upload_button_href def upload_button_href
send("bulk_upload_#{log_type}_log_path", id: "start") helpers.send("bulk_upload_#{log_type}_log_path", id: "start")
end end
def create_test_log_href def create_test_log_href
send("create_test_#{log_type}_log_path") helpers.send("create_test_#{log_type}_log_path")
end end
def create_next_year_test_log_href def create_next_year_test_log_href
send("create_next_year_test_#{log_type}_log_path") helpers.send("create_next_year_test_#{log_type}_log_path")
end end
def create_setup_test_log_href def create_setup_test_log_href
send("create_setup_test_#{log_type}_log_path") helpers.send("create_setup_test_#{log_type}_log_path")
end end
def create_next_year_setup_test_log_href def create_next_year_setup_test_log_href
send("create_next_year_setup_test_#{log_type}_log_path") helpers.send("create_next_year_setup_test_#{log_type}_log_path")
end end
def current_collection_year_label def current_collection_year_label
@ -60,7 +59,7 @@ class CreateLogActionsComponent < ViewComponent::Base
end end
def create_test_bulk_upload_href(year) def create_test_bulk_upload_href(year)
send("create_#{year}_test_#{log_type}_bulk_upload_path") helpers.send("create_#{year}_test_#{log_type}_bulk_upload_path")
end end
def view_uploads_button_copy def view_uploads_button_copy
@ -68,6 +67,6 @@ class CreateLogActionsComponent < ViewComponent::Base
end end
def view_uploads_button_href def view_uploads_button_href
send("bulk_uploads_#{log_type}_logs_path") helpers.send("bulk_uploads_#{log_type}_logs_path")
end end
end end

2
app/components/data_protection_confirmation_banner_component.html.erb

@ -1,5 +1,5 @@
<% if display_banner? %> <% if display_banner? %>
<%= govuk_notification_banner(title_text: "Important") do %> <%= helpers.govuk_notification_banner(title_text: "Important") do %>
<p class="govuk-notification-banner__heading govuk-!-width-full" style="max-width: fit-content"> <p class="govuk-notification-banner__heading govuk-!-width-full" style="max-width: fit-content">
<%= header_text %> <%= header_text %>
<p> <p>

5
app/components/data_protection_confirmation_banner_component.rb

@ -4,10 +4,9 @@ class DataProtectionConfirmationBannerComponent < ViewComponent::Base
attr_reader :user, :organisation attr_reader :user, :organisation
def initialize(user:, organisation: nil) def initialize(user:, organisation: nil)
super()
@user = user @user = user
@organisation = organisation @organisation = organisation
super
end end
def display_banner? def display_banner?
@ -32,7 +31,7 @@ class DataProtectionConfirmationBannerComponent < ViewComponent::Base
def banner_text def banner_text
if show_no_dpo_message? || user.is_dpo? || !org_or_user_org.holds_own_stock? if show_no_dpo_message? || user.is_dpo? || !org_or_user_org.holds_own_stock?
govuk_link_to( helpers.govuk_link_to(
link_text, link_text,
link_href, link_href,
class: "govuk-notification-banner__link govuk-!-font-weight-bold", class: "govuk-notification-banner__link govuk-!-font-weight-bold",

2
app/components/document_list_component.html.erb

@ -5,7 +5,7 @@
<% items.each do |item| %> <% items.each do |item| %>
<div class="app-document-list__item"> <div class="app-document-list__item">
<dt class="app-document-list__item-title"> <dt class="app-document-list__item-title">
<%= govuk_link_to item[:name], item[:href] %> <%= helpers.govuk_link_to item[:name], item[:href] %>
</dt> </dt>
<% if item[:description] %> <% if item[:description] %>
<dd class="app-document-list__item-description"><%= item[:description] %></dd> <dd class="app-document-list__item-description"><%= item[:description] %></dd>

2
app/components/document_list_component.rb

@ -2,8 +2,8 @@ class DocumentListComponent < ViewComponent::Base
attr_reader :items, :label attr_reader :items, :label
def initialize(items:, label:) def initialize(items:, label:)
super()
@items = items @items = items
@label = label @label = label
super
end end
end end

2
app/components/lettings_log_summary_component.html.erb

@ -3,7 +3,7 @@
<div class="govuk-grid-column-two-thirds"> <div class="govuk-grid-column-two-thirds">
<header class="app-log-summary__header"> <header class="app-log-summary__header">
<h2 class="app-log-summary__title"> <h2 class="app-log-summary__title">
<%= govuk_link_to lettings_log_path(log) do %> <%= helpers.govuk_link_to lettings_log_path(log) do %>
Log <%= log.id %> Log <%= log.id %>
<% end %> <% end %>
</h2> </h2>

2
app/components/lettings_log_summary_component.rb

@ -2,9 +2,9 @@ class LettingsLogSummaryComponent < ViewComponent::Base
attr_reader :current_user, :log attr_reader :current_user, :log
def initialize(current_user:, log:) def initialize(current_user:, log:)
super()
@current_user = current_user @current_user = current_user
@log = log @log = log
super
end end
def log_status def log_status

2
app/components/missing_stock_owners_banner_component.html.erb

@ -1,5 +1,5 @@
<% if display_banner? %> <% if display_banner? %>
<%= govuk_notification_banner(title_text: "Important") do %> <%= helpers.govuk_notification_banner(title_text: "Important") do %>
<p class="govuk-notification-banner__heading govuk-!-width-full" style="max-width: fit-content"> <p class="govuk-notification-banner__heading govuk-!-width-full" style="max-width: fit-content">
<%= header_text %> <%= header_text %>
<p> <p>

9
app/components/missing_stock_owners_banner_component.rb

@ -4,10 +4,9 @@ class MissingStockOwnersBannerComponent < ViewComponent::Base
attr_reader :user, :organisation attr_reader :user, :organisation
def initialize(user:, organisation: nil) def initialize(user:, organisation: nil)
super()
@user = user @user = user
@organisation = organisation || user.organisation @organisation = organisation || user.organisation
super
end end
def display_banner? def display_banner?
@ -36,7 +35,7 @@ class MissingStockOwnersBannerComponent < ViewComponent::Base
private private
def add_stock_owner_link def add_stock_owner_link
govuk_link_to( helpers.govuk_link_to(
"add a stock owner", "add a stock owner",
stock_owners_add_organisation_path(id: organisation.id), stock_owners_add_organisation_path(id: organisation.id),
class: "govuk-notification-banner__link govuk-!-font-weight-bold", class: "govuk-notification-banner__link govuk-!-font-weight-bold",
@ -44,7 +43,7 @@ private
end end
def contact_helpdesk_link def contact_helpdesk_link
govuk_link_to( helpers.govuk_link_to(
"contact the helpdesk", "contact the helpdesk",
GlobalConstants::HELPDESK_URL, GlobalConstants::HELPDESK_URL,
class: "govuk-notification-banner__link govuk-!-font-weight-bold", class: "govuk-notification-banner__link govuk-!-font-weight-bold",
@ -52,7 +51,7 @@ private
end end
def users_link def users_link
govuk_link_to( helpers.govuk_link_to(
"users page", "users page",
users_path, users_path,
class: "govuk-notification-banner__link govuk-!-font-weight-bold", class: "govuk-notification-banner__link govuk-!-font-weight-bold",

2
app/components/primary_navigation_component.html.erb

@ -1,4 +1,4 @@
<%= govuk_service_navigation(navigation_id: "primary-navigation", classes: "app-service-navigation") do |sn| <%= helpers.govuk_service_navigation(navigation_id: "primary-navigation", classes: "app-service-navigation") do |sn|
items.each do |item| items.each do |item|
sn.with_navigation_item(text: item[:text], href: item[:href], classes: "", current: item[:current]) sn.with_navigation_item(text: item[:text], href: item[:href], classes: "", current: item[:current])
end end

2
app/components/primary_navigation_component.rb

@ -2,8 +2,8 @@ class PrimaryNavigationComponent < ViewComponent::Base
attr_reader :items attr_reader :items
def initialize(items:) def initialize(items:)
super()
@items = items @items = items
super
end end
def highlighted_item?(item, _path) def highlighted_item?(item, _path)

2
app/components/sales_log_summary_component.html.erb

@ -3,7 +3,7 @@
<div class="govuk-grid-column-two-thirds"> <div class="govuk-grid-column-two-thirds">
<header class="app-log-summary__header"> <header class="app-log-summary__header">
<h2 class="app-log-summary__title"> <h2 class="app-log-summary__title">
<%= govuk_link_to sales_log_path(log) do %> <%= helpers.govuk_link_to sales_log_path(log) do %>
Log <%= log.id %> Log <%= log.id %>
<% end %> <% end %>
</h2> </h2>

2
app/components/sales_log_summary_component.rb

@ -2,9 +2,9 @@ class SalesLogSummaryComponent < ViewComponent::Base
attr_reader :current_user, :log attr_reader :current_user, :log
def initialize(current_user:, log:) def initialize(current_user:, log:)
super()
@current_user = current_user @current_user = current_user
@log = log @log = log
super
end end
def log_status def log_status

4
app/components/search_component.html.erb

@ -1,4 +1,4 @@
<%= form_with url: path(current_user), method: "get", local: true do |f| %> <%= helpers.form_with url: path(current_user), method: "get", local: true do |f| %>
<div class="app-search govuk-!-margin-bottom-4"> <div class="app-search govuk-!-margin-bottom-4">
<%= f.govuk_text_field :search, <%= f.govuk_text_field :search,
form_group: { form_group: {
@ -11,6 +11,6 @@
class: "app-search__input" %> class: "app-search__input" %>
<%= f.govuk_submit "Search", class: "app-search__button" %> <%= f.govuk_submit "Search", class: "app-search__button" %>
<%= govuk_button_link_to "Clear search", path(current_user), secondary: true, class: "app-search__button" %> <%= helpers.govuk_button_link_to "Clear search", path(current_user), secondary: true, class: "app-search__button" %>
</div> </div>
<% end %> <% end %>

2
app/components/search_component.rb

@ -2,10 +2,10 @@ class SearchComponent < ViewComponent::Base
attr_reader :current_user, :search_label, :value attr_reader :current_user, :search_label, :value
def initialize(current_user:, search_label:, value: nil) def initialize(current_user:, search_label:, value: nil)
super()
@current_user = current_user @current_user = current_user
@search_label = search_label @search_label = search_label
@value = value @value = value
super
end end
def path(current_user) def path(current_user)

2
app/components/search_result_caption_component.rb

@ -2,12 +2,12 @@ class SearchResultCaptionComponent < ViewComponent::Base
attr_reader :searched, :count, :item_label, :total_count, :item, :filters_count attr_reader :searched, :count, :item_label, :total_count, :item, :filters_count
def initialize(searched:, count:, item_label:, total_count:, item:, filters_count:) def initialize(searched:, count:, item_label:, total_count:, item:, filters_count:)
super()
@searched = searched @searched = searched
@count = count @count = count
@item_label = item_label @item_label = item_label
@total_count = total_count @total_count = total_count
@item = item @item = item
@filters_count = filters_count @filters_count = filters_count
super
end end
end end

4
app/components/sub_navigation_component.html.erb

@ -3,11 +3,11 @@
<% items.each do |item| %> <% items.each do |item| %>
<% if item.current %> <% if item.current %>
<li class="app-sub-navigation__item app-sub-navigation__item--current"> <li class="app-sub-navigation__item app-sub-navigation__item--current">
<%= govuk_link_to item[:text], item[:href], class: "app-sub-navigation__link", aria: { current: "page" } %> <%= helpers.govuk_link_to item[:text], item[:href], class: "app-sub-navigation__link", aria: { current: "page" } %>
</li> </li>
<% else %> <% else %>
<li class="app-sub-navigation__item"> <li class="app-sub-navigation__item">
<%= govuk_link_to item[:text], item[:href], class: "app-sub-navigation__link" %> <%= helpers.govuk_link_to item[:text], item[:href], class: "app-sub-navigation__link" %>
</li> </li>
<% end %> <% end %>
<% end %> <% end %>

2
app/components/sub_navigation_component.rb

@ -2,8 +2,8 @@ class SubNavigationComponent < ViewComponent::Base
attr_reader :items attr_reader :items
def initialize(items:) def initialize(items:)
super()
@items = items @items = items
super
end end
def highlighted_item?(item, _path) def highlighted_item?(item, _path)

10
app/controllers/users_controller.rb

@ -221,8 +221,14 @@ private
@user.errors.add :phone @user.errors.add :phone
end end
if user_params.key?(:organisation_id) && user_params[:organisation_id].blank? if user_params.key?(:organisation_id)
@user.errors.add :organisation_id, :blank if user_params[:organisation_id].blank?
@user.errors.add :organisation_id, :blank
elsif !@user.role_is_allowed_to_be_in_organisation?(override_organisation_id: user_params[:organisation_id].to_i) && @user.id.present?
# this will also be flagged by the validation in user.rb.
# for convenience we show the error early before they go through the change org flow (involves reassigning logs).
@user.errors.add :organisation_id, I18n.t("validations.user.support_user_in_wrong_organisation.change_organisation")
end
end end
end end

2
app/frontend/styles/_filter.scss

@ -109,7 +109,7 @@
} }
.autocomplete__option__hint { .autocomplete__option__hint {
@include govuk-font(14); @include govuk-font(16);
word-break: break-all; word-break: break-all;
} }
} }

11
app/frontend/styles/_header.scss

@ -1,6 +1,4 @@
.app-header { .app-header {
border-bottom: govuk-spacing(2) solid $govuk-brand-colour;
.govuk-header__logo { .govuk-header__logo {
@include govuk-media-query($from: desktop) { @include govuk-media-query($from: desktop) {
width: 60%; width: 60%;
@ -21,12 +19,3 @@
} }
} }
} }
.app-header--orange,
.app-header--orange .govuk-header__container {
border-bottom-color: govuk-colour("orange");
}
.app-header__no-border-bottom {
border-bottom: 0;
}

2
app/frontend/styles/_related-navigation.scss

@ -11,7 +11,7 @@
.app-related-navigation__sub-heading { .app-related-navigation__sub-heading {
@include govuk-font(16); @include govuk-font(16);
border-top: 1px solid govuk-colour("mid-grey", $legacy: "grey-2"); border-top: 1px solid govuk-colour("mid-grey");
margin: 0; margin: 0;
padding-top: govuk-spacing(3); padding-top: govuk-spacing(3);
} }

2
app/frontend/styles/_tag.scss

@ -1,5 +1,5 @@
.app-tag--small { .app-tag--small {
@include govuk-font(14, $weight: bold); @include govuk-font(16, $weight: bold);
padding-top: 2px; padding-top: 2px;
padding-right: 6px; padding-right: 6px;
padding-bottom: 2px; padding-bottom: 2px;

2
app/frontend/styles/_testing-tools.scss

@ -12,7 +12,7 @@
} }
.app-testing-tools__tag { .app-testing-tools__tag {
@include govuk-font(14); @include govuk-font(16);
background-color: #fcd6c3; background-color: #fcd6c3;
margin-top: 0; margin-top: 0;
margin-bottom: 10px; margin-bottom: 10px;

12
app/frontend/styles/application.scss

@ -130,3 +130,15 @@ $govuk-breakpoints: (
left: -15px; left: -15px;
position: relative; position: relative;
} }
.app-main-service-navigation {
border-bottom: govuk-spacing(2) solid $govuk-brand-colour;
}
.app-service-navigation--orange {
border-bottom-color: govuk-colour("orange");
}
.app-service-navigation--no-border {
border-bottom: 0;
}

13
app/helpers/application_helper.rb

@ -10,14 +10,11 @@ module ApplicationHelper
end end
end end
def govuk_header_classes(current_user) def govuk_service_navigation_classes(current_user)
if current_user&.support? return "app-service-navigation--orange" if current_user&.support?
"app-header app-header--orange" return "app-service-navigation--no-border" if notifications_to_display?
elsif notifications_to_display?
"app-header app-header__no-border-bottom" ""
else
"app-header"
end
end end
def govuk_phase_banner_tag(current_user) def govuk_phase_banner_tag(current_user)

6
app/models/forms/bulk_upload_resume/confirm.rb

@ -20,11 +20,11 @@ module Forms
send("resume_bulk_upload_#{log_type}_result_path", bulk_upload) send("resume_bulk_upload_#{log_type}_result_path", bulk_upload)
end end
def error_report_path def error_report_path(read_only: false)
if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors? if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors?
send("summary_bulk_upload_#{log_type}_result_path", bulk_upload) send("summary_bulk_upload_#{log_type}_result_path", bulk_upload, hide_upload_button: read_only ? "true" : nil)
else else
send("bulk_upload_#{log_type}_result_path", bulk_upload) send("bulk_upload_#{log_type}_result_path", bulk_upload, hide_upload_button: read_only ? "true" : nil)
end end
end end

6
app/models/forms/bulk_upload_resume/fix_choice.rb

@ -34,11 +34,11 @@ module Forms
end end
end end
def error_report_path def error_report_path(read_only: false)
if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors? if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors?
send("summary_bulk_upload_#{log_type}_result_path", bulk_upload) send("summary_bulk_upload_#{log_type}_result_path", bulk_upload, hide_upload_button: read_only ? "true" : nil)
else else
send("bulk_upload_#{log_type}_result_path", bulk_upload) send("bulk_upload_#{log_type}_result_path", bulk_upload, hide_upload_button: read_only ? "true" : nil)
end end
end end

17
app/models/user.rb

@ -25,6 +25,7 @@ class User < ApplicationRecord
validates :organisation_id, presence: true validates :organisation_id, presence: true
validate :organisation_not_merged validate :organisation_not_merged
validate :support_user_is_in_correct_organisation
has_paper_trail ignore: %w[last_sign_in_at has_paper_trail ignore: %w[last_sign_in_at
current_sign_in_at current_sign_in_at
@ -384,6 +385,12 @@ class User < ApplicationRecord
end end
end end
def role_is_allowed_to_be_in_organisation?(override_organisation_id: nil)
return true unless support? && FeatureToggle.support_organisation_allow_list.present?
FeatureToggle.support_organisation_allow_list.include?(override_organisation_id || organisation_id)
end
protected protected
# Checks whether a password is needed or not. For validations only. # Checks whether a password is needed or not. For validations only.
@ -401,6 +408,16 @@ private
end end
end end
def support_user_is_in_correct_organisation
return if role_is_allowed_to_be_in_organisation?
if role_changed?
errors.add :role, I18n.t("validations.user.support_user_in_wrong_organisation.change_role")
else
errors.add :organisation_id, I18n.t("validations.user.support_user_in_wrong_organisation.change_organisation")
end
end
def send_data_protection_confirmation_reminder def send_data_protection_confirmation_reminder
return unless persisted? return unless persisted?
return unless is_dpo? return unless is_dpo?

6
app/services/feature_toggle.rb

@ -34,4 +34,10 @@ class FeatureToggle
def self.sales_export_enabled? def self.sales_export_enabled?
Time.zone.now >= Time.zone.local(2025, 4, 1) || (Rails.env.review? || Rails.env.staging?) Time.zone.now >= Time.zone.local(2025, 4, 1) || (Rails.env.review? || Rails.env.staging?)
end end
# IDs of organisations a user must be in to be allowed the support role
# if nil this feature will be disabled
def self.support_organisation_allow_list
[1] if Rails.env.production?
end
end end

4
app/views/bulk_upload_lettings_results/show.html.erb

@ -33,4 +33,6 @@
</div> </div>
</div> </div>
<%= govuk_button_link_to "Upload your file again", start_bulk_upload_lettings_logs_path(organisation_id: @bulk_upload.organisation_id) %> <% if params[:hide_upload_button] != "true" %>
<%= govuk_button_link_to "Upload your file again", start_bulk_upload_lettings_logs_path(organisation_id: @bulk_upload.organisation_id) %>
<% end %>

8
app/views/bulk_upload_lettings_results/summary.html.erb

@ -1,3 +1,7 @@
<% content_for :before_content do %>
<%= govuk_back_link(href: :back) %>
<% end %>
<%= render partial: "bulk_upload_shared/moved_user_banner" %> <%= render partial: "bulk_upload_shared/moved_user_banner" %>
<div class="govuk-grid-row"> <div class="govuk-grid-row">
@ -34,4 +38,6 @@
<% end %> <% end %>
</div> </div>
<%= govuk_button_link_to "Upload your file again", start_bulk_upload_lettings_logs_path(organisation_id: @bulk_upload.organisation_id) %> <% if params[:hide_upload_button] != "true" %>
<%= govuk_button_link_to "Upload your file again", start_bulk_upload_lettings_logs_path(organisation_id: @bulk_upload.organisation_id) %>
<% end %>

2
app/views/bulk_upload_lettings_resume/confirm.html.erb

@ -9,7 +9,7 @@
<p class="govuk-body"> <p class="govuk-body">
<%= logs_and_errors_warning(@bulk_upload) %> <%= logs_and_errors_warning(@bulk_upload) %>
<%= govuk_link_to "View the error report", @form.error_report_path %> <%= govuk_link_to "View the error report", @form.error_report_path(read_only: true) %>
</p> </p>
<% if unique_answers_to_be_cleared(@bulk_upload).present? %> <% if unique_answers_to_be_cleared(@bulk_upload).present? %>

2
app/views/bulk_upload_lettings_resume/fix_choice.html.erb

@ -19,7 +19,7 @@
</div> </div>
<div class="govuk-body"> <div class="govuk-body">
<%= govuk_link_to "View the error report", @form.error_report_path %> <%= govuk_link_to "View the error report", @form.error_report_path(read_only: true) %>
</div> </div>
<%= govuk_details(summary_text: "How to choose between fixing errors on the CORE site or in the CSV") do %> <%= govuk_details(summary_text: "How to choose between fixing errors on the CORE site or in the CSV") do %>

4
app/views/bulk_upload_sales_results/show.html.erb

@ -33,4 +33,6 @@
</div> </div>
</div> </div>
<%= govuk_button_link_to "Upload your file again", start_bulk_upload_sales_logs_path(organisation_id: @bulk_upload.organisation_id) %> <% if params[:hide_upload_button] != "true" %>
<%= govuk_button_link_to "Upload your file again", start_bulk_upload_sales_logs_path(organisation_id: @bulk_upload.organisation_id) %>
<% end %>

8
app/views/bulk_upload_sales_results/summary.html.erb

@ -1,3 +1,7 @@
<% content_for :before_content do %>
<%= govuk_back_link(href: :back) %>
<% end %>
<%= render partial: "bulk_upload_shared/moved_user_banner" %> <%= render partial: "bulk_upload_shared/moved_user_banner" %>
<div class="govuk-grid-row"> <div class="govuk-grid-row">
@ -34,4 +38,6 @@
<% end %> <% end %>
</div> </div>
<%= govuk_button_link_to "Upload your file again", start_bulk_upload_sales_logs_path(organisation_id: @bulk_upload.organisation_id) %> <% if params[:hide_upload_button] != "true" %>
<%= govuk_button_link_to "Upload your file again", start_bulk_upload_sales_logs_path(organisation_id: @bulk_upload.organisation_id) %>
<% end %>

2
app/views/bulk_upload_sales_resume/confirm.html.erb

@ -9,7 +9,7 @@
<p class="govuk-body"> <p class="govuk-body">
<%= logs_and_errors_warning(@bulk_upload) %> <%= logs_and_errors_warning(@bulk_upload) %>
<%= govuk_link_to "View the error report", @form.error_report_path %> <%= govuk_link_to "View the error report", @form.error_report_path(read_only: true) %>
</p> </p>
<% if unique_answers_to_be_cleared(@bulk_upload).present? %> <% if unique_answers_to_be_cleared(@bulk_upload).present? %>

2
app/views/bulk_upload_sales_resume/fix_choice.html.erb

@ -19,7 +19,7 @@
</div> </div>
<div class="govuk-body"> <div class="govuk-body">
<%= govuk_link_to "View the error report", @form.error_report_path %> <%= govuk_link_to "View the error report", @form.error_report_path(read_only: true) %>
</div> </div>
<%= govuk_details(summary_text: "How to choose between fixing errors on the CORE site or in the CSV") do %> <%= govuk_details(summary_text: "How to choose between fixing errors on the CORE site or in the CSV") do %>

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

@ -81,20 +81,24 @@
<%= govuk_skip_link %> <%= govuk_skip_link %>
<%= govuk_header( <%= govuk_header(
classes: govuk_header_classes(current_user), classes: "app-header",
homepage_url: root_path, homepage_url: root_path,
navigation_classes: "govuk-header__navigation--end", ) %>
) do |component|
component.with_product_name(name: t("service_name")) <%= govuk_service_navigation(
unless FeatureToggle.service_moved? || FeatureToggle.service_unavailable? service_name: t("service_name"),
if current_user.nil? service_url: root_path,
component.with_navigation_item(text: "Sign in", href: user_session_path) classes: "app-main-service-navigation #{govuk_service_navigation_classes(current_user)}",
else ) do |component| %>
component.with_navigation_item(text: "Your account", href: account_path) <% unless FeatureToggle.service_moved? || FeatureToggle.service_unavailable? %>
component.with_navigation_item(text: "Sign out", href: destroy_user_session_path) <% if current_user.nil? %>
end <%= component.with_navigation_item(text: "Sign in", href: user_session_path) %>
end <% else %>
end %> <%= component.with_navigation_item(text: "Your account", href: account_path) %>
<%= component.with_navigation_item(text: "Sign out", href: destroy_user_session_path) %>
<% end %>
<% end %>
<% end %>
<% if notifications_to_display? %> <% if notifications_to_display? %>
<%= render "notifications/notification_banner" %> <%= render "notifications/notification_banner" %>

18
app/views/layouts/rails_admin/_navigation.html.erb

@ -1,9 +1,13 @@
<%= govuk_header( <%= govuk_header(
classes: "app-header app-header--orange", classes: "app-header",
homepage_url: Rails.application.routes.url_helpers.root_path, homepage_url: Rails.application.routes.url_helpers.root_path,
navigation_classes: "govuk-header__navigation--end", ) %>
) do |component|
component.with_product_name(name: t("service_name")) <%= govuk_service_navigation(
component.with_navigation_item(text: "Your account", href: Rails.application.routes.url_helpers.account_path) service_name: t("service_name"),
component.with_navigation_item(text: "Sign out", href: Rails.application.routes.url_helpers.destroy_user_session_path) service_url: Rails.application.routes.url_helpers.root_path,
end %> classes: "app-main-service-navigation app-service-navigation--orange",
) do |component| %>
<%= component.with_navigation_item(text: "Your account", href: Rails.application.routes.url_helpers.account_path) %>
<%= component.with_navigation_item(text: "Sign out", href: Rails.application.routes.url_helpers.destroy_user_session_path) %>
<% end %>

4
app/views/users/_user_list.html.erb

@ -36,7 +36,7 @@
<% if user.is_data_protection_officer? %> <% if user.is_data_protection_officer? %>
<%= govuk_tag( <%= govuk_tag(
classes: "app-tag--small", classes: "app-tag--small",
colour: "turquoise", colour: "teal",
text: "Data protection officer", text: "Data protection officer",
) %> ) %>
<% else %> <% else %>
@ -45,7 +45,7 @@
<% if user.is_key_contact? %> <% if user.is_key_contact? %>
<%= govuk_tag( <%= govuk_tag(
classes: "app-tag--small", classes: "app-tag--small",
colour: "turquoise", colour: "teal",
text: "Key contact", text: "Key contact",
) %> ) %>
<% else %> <% else %>

4
config/locales/en.yml

@ -260,6 +260,10 @@ en:
blank: "Enter an email address." blank: "Enter an email address."
role: role:
invalid: "Role must be data accessor, data provider or data coordinator." invalid: "Role must be data accessor, data provider or data coordinator."
user:
support_user_in_wrong_organisation:
change_role: "You cannot create a support account type for a user in this organisation. Support accounts should only be created for MHCLG and contractor staff as they are administrator level accounts with access to all organisations' data. Any support accounts for housing organisations would be a data protection breach."
change_organisation: "You cannot move a user with a support account to a non-MHCLG organisation. If you need to move the user, change their role type to data coordinator or data provider."
setup: setup:
saledate: saledate:

12
lib/tasks/fix_sales_logs_with_invalid_initialpurchase_lasttransaction.rake

@ -1,13 +1,15 @@
desc "We tightened the validation in 2026 between initial purchase date, last transaction date and sale date so that no two can be equal and initial purchase date < last transaction date < sale date. To avoid invalid logs we clear lasttransaction if it equals saledate and if initialpurchase = lasttransaction we clear both" desc "We tightened the validation between initial purchase date in 2026, last transaction date and sale date so the two can no longer be equal. To avoid invalid logs we clear initialpurchase if it equals saledate and if initialpurchase = lasttransaction we clear both"
task fix_sales_logs_with_invalid_initialpurchase_lasttransaction: :environment do task fix_sales_logs_with_invalid_initialpurchase_lasttransaction: :environment do
lasttransaction_equal_saledate_logs = SalesLog.filter_by_year_or_later(2026).where("lasttransaction = saledate")
initial_purchase_equal_lasttransaction_logs = SalesLog.filter_by_year_or_later(2026).where("initialpurchase = lasttransaction") initial_purchase_equal_lasttransaction_logs = SalesLog.filter_by_year_or_later(2026).where("initialpurchase = lasttransaction")
lasttransaction_equal_saledate_logs = SalesLog.filter_by_year_or_later(2026).where("lasttransaction = saledate")
puts "Updating #{lasttransaction_equal_saledate_logs.count} logs where lasttransaction = saledate, #{lasttransaction_equal_saledate_logs.map(&:id)}" # this one must happen first since this will always result in a log that passes date validations
lasttransaction_equal_saledate_logs.update!(lasttransaction: nil)
puts "Updating #{initial_purchase_equal_lasttransaction_logs.count} logs where initialpurchase = lasttransaction, #{initial_purchase_equal_lasttransaction_logs.map(&:id)}" puts "Updating #{initial_purchase_equal_lasttransaction_logs.count} logs where initialpurchase = lasttransaction, #{initial_purchase_equal_lasttransaction_logs.map(&:id)}"
initial_purchase_equal_lasttransaction_logs.update!(initialpurchase: nil, lasttransaction: nil) initial_purchase_equal_lasttransaction_logs.update!(initialpurchase: nil, lasttransaction: nil)
# this one could fail if lasttransaction == saledate == initialpurchase, but the above case will have already reset these logs
puts "Updating #{lasttransaction_equal_saledate_logs.count} logs where lasttransaction = saledate, #{lasttransaction_equal_saledate_logs.map(&:id)}"
lasttransaction_equal_saledate_logs.update!(lasttransaction: nil)
puts "Done" puts "Done"
end end

4
package.json

@ -12,7 +12,7 @@
"@ministryofjustice/frontend": "^3.3.0", "@ministryofjustice/frontend": "^3.3.0",
"@stimulus/polyfills": "^2.0.0", "@stimulus/polyfills": "^2.0.0",
"@webcomponents/webcomponentsjs": "^2.6.0", "@webcomponents/webcomponentsjs": "^2.6.0",
"@x-govuk/govuk-prototype-components": "^3.0.9", "@x-govuk/govuk-prototype-components": "^6.0.0",
"accessible-autocomplete": "^2.0.3", "accessible-autocomplete": "^2.0.3",
"babel-loader": "^8.2.3", "babel-loader": "^8.2.3",
"babel-plugin-macros": "^3.1.0", "babel-plugin-macros": "^3.1.0",
@ -21,7 +21,7 @@
"css-loader": "^6.7.1", "css-loader": "^6.7.1",
"custom-event-polyfill": "^1.0.7", "custom-event-polyfill": "^1.0.7",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"govuk-frontend": "5.7.1", "govuk-frontend": "6.0.0",
"html5shiv": "^3.7.3", "html5shiv": "^3.7.3",
"intersection-observer": "^0.12.0", "intersection-observer": "^0.12.0",
"jquery": "^3.7.1", "jquery": "^3.7.1",

46
spec/helpers/application_helper_spec.rb

@ -8,18 +8,50 @@ RSpec.describe ApplicationHelper do
let(:pagy) { nil } let(:pagy) { nil }
let(:current_user) { FactoryBot.create(:user) } let(:current_user) { FactoryBot.create(:user) }
describe "govuk_header_classes" do describe "govuk_service_navigation_classes" do
context "with external user" do context "with non-support user" do
it "shows the standard app header" do context "when no notifications are displayed" do
expect(govuk_header_classes(current_user)).to eq("app-header") before do
allow(helper).to receive(:notifications_to_display?).and_return(false)
end
it "returns empty string for blue border (default)" do
expect(helper.govuk_service_navigation_classes(current_user)).to eq("")
end
end
context "when notifications are displayed" do
before do
allow(helper).to receive(:notifications_to_display?).and_return(true)
end
it "returns no-border class to hide the border (notification banner shows instead)" do
expect(helper.govuk_service_navigation_classes(current_user)).to eq("app-service-navigation--no-border")
end
end end
end end
context "with internal support user" do context "with support user" do
let(:current_user) { FactoryBot.create(:user, :support) } let(:current_user) { FactoryBot.create(:user, :support) }
it "shows an orange header" do context "when no notifications are displayed" do
expect(govuk_header_classes(current_user)).to eq("app-header app-header--orange") before do
allow(helper).to receive(:notifications_to_display?).and_return(false)
end
it "always returns orange class for orange border" do
expect(helper.govuk_service_navigation_classes(current_user)).to eq("app-service-navigation--orange")
end
end
context "when notifications are displayed" do
before do
allow(helper).to receive(:notifications_to_display?).and_return(true)
end
it "still returns orange class (support users always see orange border)" do
expect(helper.govuk_service_navigation_classes(current_user)).to eq("app-service-navigation--orange")
end
end end
end end
end end

47
spec/models/user_spec.rb

@ -540,6 +540,53 @@ RSpec.describe User, type: :model do
.to raise_error(ActiveRecord::RecordInvalid, error_message) .to raise_error(ActiveRecord::RecordInvalid, error_message)
end end
end end
describe "#support_user_is_in_correct_organisation" do
let(:organisation) { create(:organisation) }
context "when the user is not a support user" do
let(:user) { build(:user, :data_coordinator, organisation:) }
it "is valid regardless of the allow list" do
allow(FeatureToggle).to receive(:support_organisation_allow_list).and_return([999])
expect(user).to be_valid
end
end
context "when the user is a support user" do
let(:user) { build(:user, :support, organisation:) }
context "and the allow list is nil" do
before do
allow(FeatureToggle).to receive(:support_organisation_allow_list).and_return(nil)
end
it "is valid" do
expect(user).to be_valid
end
end
context "and the organisation is in the allow list" do
before do
allow(FeatureToggle).to receive(:support_organisation_allow_list).and_return([organisation.id])
end
it "is valid" do
expect(user).to be_valid
end
end
context "and the organisation is not in the allow list" do
before do
allow(FeatureToggle).to receive(:support_organisation_allow_list).and_return([organisation.id + 1])
end
it "is not valid" do
expect(user).not_to be_valid
end
end
end
end
end end
describe "delete" do describe "delete" do

36
spec/requests/bulk_upload_lettings_results_controller_spec.rb

@ -82,6 +82,24 @@ RSpec.describe BulkUploadLettingsResultsController, type: :request do
expect(response.body).to include("You moved to a different organisation since this file was uploaded. Upload the file again to get an accurate error report.") expect(response.body).to include("You moved to a different organisation since this file was uploaded. Upload the file again to get an accurate error report.")
end end
end end
context "and user has upload button shown" do
it "displays a link to reupload file" do
get "/lettings-logs/bulk-upload-results/#{bulk_upload.id}/summary"
expect(response.body).to include("Upload your file again")
expect(response.body).to include("/lettings-logs/bulk-upload-logs/start")
end
end
context "and user has upload button hidden" do
it "does not display a link to reupload file" do
get "/lettings-logs/bulk-upload-results/#{bulk_upload.id}/summary?hide_upload_button=true"
expect(response.body).not_to include("Upload your file again")
expect(response.body).not_to include("/lettings-logs/bulk-upload-logs/start")
end
end
end end
end end
@ -152,5 +170,23 @@ RSpec.describe BulkUploadLettingsResultsController, type: :request do
expect(response.body).to include("You moved to a different organisation since this file was uploaded. Upload the file again to get an accurate error report.") expect(response.body).to include("You moved to a different organisation since this file was uploaded. Upload the file again to get an accurate error report.")
end end
end end
context "and user has upload button shown" do
it "displays a link to reupload file" do
get "/lettings-logs/bulk-upload-results/#{bulk_upload.id}"
expect(response.body).to include("Upload your file again")
expect(response.body).to include("/lettings-logs/bulk-upload-logs/start")
end
end
context "and user has upload button hidden" do
it "does not display a link to reupload file" do
get "/lettings-logs/bulk-upload-results/#{bulk_upload.id}?hide_upload_button=true"
expect(response.body).not_to include("Upload your file again")
expect(response.body).not_to include("/lettings-logs/bulk-upload-logs/start")
end
end
end end
end end

36
spec/requests/bulk_upload_sales_results_controller_spec.rb

@ -44,6 +44,24 @@ RSpec.describe BulkUploadSalesResultsController, type: :request do
expect(response.body).to include("You moved to a different organisation since this file was uploaded. Upload the file again to get an accurate error report.") expect(response.body).to include("You moved to a different organisation since this file was uploaded. Upload the file again to get an accurate error report.")
end end
end end
context "and user has upload button shown" do
it "displays a link to reupload file" do
get "/sales-logs/bulk-upload-results/#{bulk_upload.id}/summary"
expect(response.body).to include("Upload your file again")
expect(response.body).to include("/sales-logs/bulk-upload-logs/start")
end
end
context "and user has upload button hidden" do
it "does not display a link to reupload file" do
get "/sales-logs/bulk-upload-results/#{bulk_upload.id}/summary?hide_upload_button=true"
expect(response.body).not_to include("Upload your file again")
expect(response.body).not_to include("/sales-logs/bulk-upload-logs/start")
end
end
end end
end end
@ -127,5 +145,23 @@ RSpec.describe BulkUploadSalesResultsController, type: :request do
expect(response.body).to include("You moved to a different organisation since this file was uploaded. Upload the file again to get an accurate error report.") expect(response.body).to include("You moved to a different organisation since this file was uploaded. Upload the file again to get an accurate error report.")
end end
end end
context "and user has upload button shown" do
it "displays a link to reupload file" do
get "/sales-logs/bulk-upload-results/#{bulk_upload.id}"
expect(response.body).to include("Upload your file again")
expect(response.body).to include("/sales-logs/bulk-upload-logs/start")
end
end
context "and user has upload button hidden" do
it "does not display a link to reupload file" do
get "/sales-logs/bulk-upload-results/#{bulk_upload.id}?hide_upload_button=true"
expect(response.body).not_to include("Upload your file again")
expect(response.body).not_to include("/sales-logs/bulk-upload-logs/start")
end
end
end end
end end

54
spec/requests/users_controller_spec.rb

@ -76,13 +76,39 @@ RSpec.describe UsersController, type: :request do
end end
describe "title link" do describe "title link" do
it "routes user to the home page" do context "with a non-support user" do
sign_in user before do
get "/", headers:, params: {} sign_in user
expect(path).to eq("/") end
expect(page).to have_content("Welcome back")
expected_link = "<a class=\"govuk-header__link govuk-header__link--homepage\" href=\"/\">" it "has GOV.UK header and service navigation both linking to home page" do
expect(CGI.unescape_html(response.body)).to include(expected_link) get "/", headers:, params: {}
expect(path).to eq("/")
expect(page).to have_content("Welcome back")
govuk_header_link = '<a class="govuk-header__link govuk-header__homepage-link" href="/">'
expect(CGI.unescape_html(response.body)).to include(govuk_header_link)
expect(page).to have_css(".govuk-service-navigation__link[href='/']", text: "Submit social housing lettings and sales data (CORE)")
end
end
context "with a support user" do
let(:support_user) { create(:user, :support) }
before do
sign_in support_user
end
it "has GOV.UK header and service navigation both linking to home page" do
get "/", headers:, params: {}
follow_redirect!
govuk_header_link = '<a class="govuk-header__link govuk-header__homepage-link" href="/">'
expect(CGI.unescape_html(response.body)).to include(govuk_header_link)
expect(page).to have_css(".govuk-service-navigation__link[href='/']", text: "Submit social housing lettings and sales data (CORE)")
end
end end
end end
@ -2597,18 +2623,4 @@ RSpec.describe UsersController, type: :request do
end end
end end
end end
describe "title link" do
before do
sign_in user
end
it "routes user to the home page" do
get "/", headers:, params: {}
expect(path).to eq("/")
expect(page).to have_content("Welcome back")
expected_link = "<a class=\"govuk-header__link govuk-header__link--homepage\" href=\"/\">"
expect(CGI.unescape_html(response.body)).to include(expected_link)
end
end
end end

4
webpack.config.js

@ -40,8 +40,8 @@ module.exports = {
}, },
resolve: { resolve: {
alias: { alias: {
'govuk-frontend-styles': path.resolve(__dirname, 'node_modules/govuk-frontend/dist/govuk/all.scss'), 'govuk-frontend-styles': path.resolve(__dirname, 'node_modules/govuk-frontend/dist/govuk/index.scss'),
'govuk-prototype-styles': path.resolve(__dirname, 'node_modules/@x-govuk/govuk-prototype-components/x-govuk/all.scss'), 'govuk-prototype-styles': path.resolve(__dirname, 'node_modules/@x-govuk/govuk-prototype-components/dist/govuk-prototype-components.scss'),
'moj-frontend': path.resolve(__dirname, 'node_modules/@ministryofjustice/frontend/moj/all.js') 'moj-frontend': path.resolve(__dirname, 'node_modules/@ministryofjustice/frontend/moj/all.js')
}, },
modules: ['node_modules', 'node_modules/govuk-frontend/dist/govuk'] modules: ['node_modules', 'node_modules/govuk-frontend/dist/govuk']

1377
yarn.lock

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save