From 4157a7bb8f0cdd50e4b4743114b9f90e1edc1214 Mon Sep 17 00:00:00 2001 From: Rachael Booth Date: Wed, 27 Nov 2024 16:08:28 +0000 Subject: [PATCH 01/36] CLDC-3732: Update review apps seeds (#2834) * Update seeds * Fix linting * Update dsa bits * Fix duplicate user/org variable names * Fix create_list * Set faker locale for development and review * Update tests that relied on user factory default name * Update more tests * Add additional randomness to log factories * Also create older logs * Update tests and log randomness * Ensure sufficient gap between child and parent ages * More test fixes * Specify names for users * Fix more tests * Fix lint * More fixes * More randomness in organisation generation * Add some extra organisations * Create future logs if future form use is enabled * Update tests that expect fixed default org name * Update more tests * Fix typo * Another tweak * More test fixes * More test fixes * Another fix * Update more tests * More fixes * Only create outright sale logs for relevant years * Reduce max random age to avoid soft validations * Don't allow sales logs to use no longer valid relat value * Include fixed locations --- config/environments/development.rb | 2 + config/environments/review.rb | 2 + db/seeds.rb | 526 +++++------------- ...nswers_summary_list_card_component_spec.rb | 2 +- .../lettings_log_summary_component_spec.rb | 4 +- .../sales_log_summary_component_spec.rb | 2 +- spec/factories/lettings_log.rb | 10 +- spec/factories/organisation.rb | 20 +- spec/factories/sales_log.rb | 44 +- spec/factories/scheme.rb | 3 + spec/factories/user.rb | 6 +- spec/features/lettings_log_spec.rb | 4 +- spec/features/sales_log_spec.rb | 4 +- spec/features/schemes_spec.rb | 9 +- spec/helpers/organisations_helper_spec.rb | 2 +- spec/helpers/tab_nav_helper_spec.rb | 2 +- spec/models/form_spec.rb | 2 +- spec/models/organisation_spec.rb | 2 +- spec/models/user_spec.rb | 2 +- .../merge_requests_controller_spec.rb | 12 +- .../requests/organisations_controller_spec.rb | 11 +- spec/requests/users_controller_spec.rb | 22 +- .../csv/lettings_log_csv_service_spec.rb | 4 +- .../csv/sales_log_csv_service_spec.rb | 10 +- spec/services/csv/scheme_csv_service_spec.rb | 2 +- .../lettings_log_export_service_spec.rb | 17 +- .../organisation_export_service_spec.rb | 2 +- .../exports/user_export_service_spec.rb | 4 +- .../merge/merge_organisations_service_spec.rb | 28 +- 29 files changed, 282 insertions(+), 478 deletions(-) diff --git a/config/environments/development.rb b/config/environments/development.rb index 7e1890b02..f75643524 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -81,6 +81,8 @@ Rails.application.configure do # Uncomment if you wish to allow Action Cable access from any origin. # config.action_cable.disable_request_forgery_protection = true + Faker::Config.locale = "en-GB" + # see https://discuss.rubyonrails.org/t/cve-2022-32224-possible-rce-escalation-bug-with-serialized-columns-in-active-record/81017 config.active_record.yaml_column_permitted_classes = [Time, BigDecimal] diff --git a/config/environments/review.rb b/config/environments/review.rb index f7438fdb6..471879b27 100644 --- a/config/environments/review.rb +++ b/config/environments/review.rb @@ -125,6 +125,8 @@ Rails.application.configure do # config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session + Faker::Config.locale = "en-GB" + # see https://discuss.rubyonrails.org/t/cve-2022-32224-possible-rce-escalation-bug-with-serialized-columns-in-active-record/81017 config.active_record.yaml_column_permitted_classes = [Time, BigDecimal] end diff --git a/db/seeds.rb b/db/seeds.rb index 9654b2b78..2f018be91 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,436 +1,175 @@ # This file should contain all the record creation needed to seed the database with its default values. # The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). -# -# Examples: -# -# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) -# Character.create(name: 'Luke', movie: movies.first) -# rubocop:disable Rails/Output -def create_data_protection_confirmation(user) - DataProtectionConfirmation.find_or_create_by!( - organisation: user.organisation, - confirmed: true, - data_protection_officer: user, - signed_at: Time.zone.local(2019, 1, 1), - data_protection_officer_email: user.email, - data_protection_officer_name: user.name, - organisation_name: user.organisation.name, - organisation_address: user.organisation.address_row, - ) +def find_or_create_user(organisation, email, name, role) + case role + when :data_provider + FactoryBot.create(:user, :if_unique, :data_provider, organisation:, email:, name:, password: ENV["REVIEW_APP_USER_PASSWORD"]) + when :data_coordinator + FactoryBot.create(:user, :if_unique, :data_coordinator, organisation:, email:, name:, password: ENV["REVIEW_APP_USER_PASSWORD"]) + when :support + FactoryBot.create(:user, :if_unique, :support, organisation:, email:, name:, password: ENV["REVIEW_APP_USER_PASSWORD"]) + end end unless Rails.env.test? - stock_owner1 = Organisation.find_or_create_by!( - name: "Stock Owner 1", - address_line1: "2 Marsham Street", - address_line2: "London", - postcode: "SW1P 4DF", - holds_own_stock: true, - other_stock_owners: "None", - managing_agents_label: "None", - provider_type: "LA", - ) - stock_owner2 = Organisation.find_or_create_by!( - name: "Stock Owner 2", - address_line1: "2 Marsham Street", - address_line2: "London", - postcode: "SW1P 4DF", - holds_own_stock: true, - other_stock_owners: "None", - managing_agents_label: "None", - provider_type: "LA", - ) - managing_agent1 = Organisation.find_or_create_by!( - name: "Managing Agent 1 (PRP)", - address_line1: "2 Marsham Street", - address_line2: "London", - postcode: "SW1P 4DF", - holds_own_stock: true, - other_stock_owners: "None", - managing_agents_label: "None", - provider_type: "PRP", - ) - managing_agent2 = Organisation.find_or_create_by!( - name: "Managing Agent 2", - address_line1: "2 Marsham Street", - address_line2: "London", - postcode: "SW1P 4DF", - holds_own_stock: true, - other_stock_owners: "None", - managing_agents_label: "None", - provider_type: "LA", - ) + if LocalAuthority.count.zero? + la_path = "config/local_authorities_data/initial_local_authorities.csv" + service = Imports::LocalAuthoritiesService.new(path: la_path) + service.call + end - org = Organisation.find_or_create_by!( - name: "MHCLG", - address_line1: "2 Marsham Street", - address_line2: "London", - postcode: "SW1P 4DF", - holds_own_stock: true, - other_stock_owners: "None", - managing_agents_label: "None", - provider_type: "LA", - ) do - info = "Seeded MHCLG Organisation" - if Rails.env.development? - pp info - else - Rails.logger.info info + if LaRentRange.count.zero? + Dir.glob("config/rent_range_data/*.csv").each do |path| + start_year = File.basename(path, ".csv") + service = Imports::RentRangesService.new(start_year:, path:) + service.call end end - standalone_owns_stock = Organisation.find_or_create_by!( - name: "Standalone Owns Stock 1 Ltd", - address_line1: "2 Marsham Street", - address_line2: "London", - postcode: "SW1P 4DF", - holds_own_stock: true, - other_stock_owners: "None", - managing_agents_label: "None", - provider_type: "LA", - ) + if LaSaleRange.count.zero? + Dir.glob("config/sale_range_data/*.csv").each do |path| + start_year = File.basename(path, ".csv") + service = Imports::SaleRangesService.new(start_year:, path:) + service.call + end + end + + first_run = Organisation.count.zero? + + all_rent_periods = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] - standalone_no_stock = Organisation.find_or_create_by!( - name: "Standalone No Stock 1 Ltd", + mhclg = FactoryBot.create( + :organisation, + :if_unique, + name: "MHCLG", address_line1: "2 Marsham Street", address_line2: "London", postcode: "SW1P 4DF", - holds_own_stock: false, + holds_own_stock: true, other_stock_owners: "None", managing_agents_label: "None", provider_type: "LA", - ) - - User.find_or_create_by!( - name: "Provider Owns Stock", - email: "provider.owner1@example.com", - organisation: standalone_owns_stock, - role: "data_provider", - is_dpo: true, - ) do |user| - user.password = ENV["REVIEW_APP_USER_PASSWORD"] - user.confirmed_at = Time.zone.now - create_data_protection_confirmation(user) - end - - User.find_or_create_by!( - name: "Coordinator Owns Stock", - email: "coordinator.owner1@example.com", - organisation: standalone_owns_stock, - role: "data_coordinator", - is_dpo: true, - ) do |user| - user.password = ENV["REVIEW_APP_USER_PASSWORD"] - user.confirmed_at = Time.zone.now - create_data_protection_confirmation(user) - end - - User.find_or_create_by!( - name: "Provider No Stock", - email: "provider.nostock@example.com", - organisation: standalone_no_stock, - role: "data_provider", - is_dpo: true, - ) do |user| - user.password = ENV["REVIEW_APP_USER_PASSWORD"] - user.confirmed_at = Time.zone.now - create_data_protection_confirmation(user) - end - - User.find_or_create_by!( - name: "Coordinator No Stock", - email: "coordinator.nostock@example.com", - organisation: standalone_no_stock, - role: "data_coordinator", - ) do |user| - user.password = ENV["REVIEW_APP_USER_PASSWORD"] - user.confirmed_at = Time.zone.now - end - - User.find_or_create_by!( - name: "Stock owner 1", - email: "stock_owner1_dpo@example.com", - organisation: stock_owner1, - role: "data_coordinator", - is_dpo: true, - ) do |user| - user.password = ENV["REVIEW_APP_USER_PASSWORD"] - user.confirmed_at = Time.zone.now - create_data_protection_confirmation(user) - end - - User.find_or_create_by!( - name: "Stock owner 2", - email: "stock_owner2_dpo@example.com", - organisation: stock_owner2, - role: "data_coordinator", - is_dpo: true, - ) do |user| - user.password = ENV["REVIEW_APP_USER_PASSWORD"] - user.confirmed_at = Time.zone.now - create_data_protection_confirmation(user) - end - - User.find_or_create_by!( - name: "Managing agent 1", - email: "managing_agent1_dpo@example.com", - organisation: managing_agent1, - role: "data_coordinator", - is_dpo: true, - ) do |user| - user.password = ENV["REVIEW_APP_USER_PASSWORD"] - user.confirmed_at = Time.zone.now - create_data_protection_confirmation(user) - end - - User.find_or_create_by!( - name: "Managing agent 2", - email: "managing_agent2_dpo@example.com", - organisation: managing_agent2, - role: "data_coordinator", - is_dpo: true, - ) do |user| - user.password = ENV["REVIEW_APP_USER_PASSWORD"] - user.confirmed_at = Time.zone.now - create_data_protection_confirmation(user) - end - - OrganisationRelationship.find_or_create_by!( - parent_organisation: stock_owner1, - child_organisation: org, - ) - OrganisationRelationship.find_or_create_by!( - parent_organisation: stock_owner2, - child_organisation: org, - ) - OrganisationRelationship.find_or_create_by!( - parent_organisation: org, - child_organisation: managing_agent1, - ) - OrganisationRelationship.find_or_create_by!( - parent_organisation: org, - child_organisation: managing_agent2, + housing_registration_no: nil, + rent_periods: all_rent_periods, ) if Rails.env.development? || Rails.env.review? - User.find_or_create_by!( - name: "Provider", - email: "provider@example.com", - organisation: org, - role: "data_provider", - ) do |user| - user.password = ENV["REVIEW_APP_USER_PASSWORD"] - user.confirmed_at = Time.zone.now - end + stock_owner1 = FactoryBot.create(:organisation, :if_unique, :la, :holds_own_stock, name: "Stock Owner 1", rent_periods: all_rent_periods) + stock_owner2 = FactoryBot.create(:organisation, :if_unique, :la, :holds_own_stock, name: "Stock Owner 2", rent_periods: all_rent_periods.sample(5)) + managing_agent1 = FactoryBot.create(:organisation, :if_unique, :prp, :holds_own_stock, name: "Managing Agent 1 (PRP)", rent_periods: all_rent_periods) + managing_agent2 = FactoryBot.create(:organisation, :if_unique, :la, :holds_own_stock, name: "Managing Agent 2", rent_periods: all_rent_periods.sample(5)) + standalone_owns_stock = FactoryBot.create(:organisation, :if_unique, :la, :holds_own_stock, name: "Standalone Owns Stock 1 Ltd", rent_periods: all_rent_periods) + standalone_no_stock = FactoryBot.create(:organisation, :if_unique, :la, :does_not_own_stock, name: "Standalone No Stock 1 Ltd", rent_periods: all_rent_periods) + + other_orgs = FactoryBot.create_list(:organisation, 5, :prp, rent_periods: all_rent_periods.sample(3)) if first_run + + OrganisationRelationship.find_or_create_by!( + parent_organisation: stock_owner1, + child_organisation: mhclg, + ) + OrganisationRelationship.find_or_create_by!( + parent_organisation: stock_owner2, + child_organisation: mhclg, + ) + OrganisationRelationship.find_or_create_by!( + parent_organisation: mhclg, + child_organisation: managing_agent1, + ) + OrganisationRelationship.find_or_create_by!( + parent_organisation: mhclg, + child_organisation: managing_agent2, + ) - User.find_or_create_by!( - name: "Coordinator", - email: "coordinator@example.com", - organisation: org, - role: "data_coordinator", - is_dpo: true, - ) do |user| - user.password = ENV["REVIEW_APP_USER_PASSWORD"] - user.confirmed_at = Time.zone.now - user.is_dpo = true - create_data_protection_confirmation(user) - end + provider = find_or_create_user(mhclg, "provider@example.com", "Provider", :data_provider) + coordinator = find_or_create_user(mhclg, "coordinator@example.com", "Coordinator", :data_coordinator) + support = find_or_create_user(mhclg, "support@example.com", "Support", :support) - support_user = User.find_or_create_by!( - name: "Support", - email: "support@example.com", - organisation: org, - role: "support", - is_dpo: true, - ) do |user| - user.password = ENV["REVIEW_APP_USER_PASSWORD"] - user.confirmed_at = Time.zone.now - create_data_protection_confirmation(user) - end + stock_owner1_user = find_or_create_user(stock_owner1, "stock_owner1_dpo@example.com", "Stock owner 1", :data_coordinator) + stock_owner2_user = find_or_create_user(stock_owner2, "stock_owner2_dpo@example.com", "Stock owner 2", :data_coordinator) - pp "Seeded dummy users" - end + managing_agent1_user = find_or_create_user(managing_agent1, "managing_agent1_dpo@example.com", "Managing agent 1", :data_coordinator) + managing_agent2_user = find_or_create_user(managing_agent2, "managing_agent2_dpo@example.com", "Managing agent 2", :data_coordinator) - if (Rails.env.development? || Rails.env.review?) && SalesLog.count.zero? - SalesLog.find_or_create_by!( - assigned_to: support_user, - owning_organisation: org, - managing_organisation: org, - saledate: Time.zone.today, - purchid: "1", - ownershipsch: 1, - type: 2, - jointpur: 1, - jointmore: 1, - ) + provider_owner1 = find_or_create_user(standalone_owns_stock, "provider.owner1@example.com", "Provider Owns Stock", :data_provider) + coordinator_owner1 = find_or_create_user(standalone_owns_stock, "coordinator.owner1@example.com", "Coordinator Owns Stock", :data_coordinator) - SalesLog.find_or_create_by!( - assigned_to: support_user, - owning_organisation: org, - managing_organisation: org, - saledate: Time.zone.today, - purchid: "1", - ownershipsch: 2, - type: 9, - jointpur: 1, - jointmore: 1, - ) + find_or_create_user(standalone_no_stock, "provider.nostock@example.com", "Provider No Stock", :data_provider) + find_or_create_user(standalone_no_stock, "coordinator.nostock@example.com", "Coordinator No Stock", :data_coordinator) - SalesLog.find_or_create_by!( - assigned_to: support_user, - owning_organisation: org, - managing_organisation: org, - saledate: Time.zone.today, - purchid: "1", - ownershipsch: 3, - type: 10, - companybuy: 1, - ) + if Scheme.count.zero? + beulahside = FactoryBot.create(:scheme, service_name: "Beulahside Care", owning_organisation: mhclg) + abdullah = FactoryBot.create(:scheme, service_name: "Abdullahview Point", owning_organisation: mhclg) - pp "Seeded a sales log of each type" - end + FactoryBot.create(:location, scheme: beulahside, name: "Rectory Road", postcode: "CU193AA", location_code: "E09000033", location_admin_district: "Westminster", type_of_unit: 4, mobility_type: "N") + FactoryBot.create(:location, scheme: beulahside, name: "Smithy Lane", postcode: "DM25ODC", location_code: "E09000033", location_admin_district: "Westminster", type_of_unit: 1, mobility_type: "W") + FactoryBot.create(:location, scheme: abdullah, name: "Smithy Lane", postcode: "YX130WP", location_code: "E09000033", location_admin_district: "Westminster", type_of_unit: 2, mobility_type: "W") - if Rails.env.development? || Rails.env.review? - dummy_org = Organisation.find_or_create_by!( - name: "FooBar LTD", - address_line1: "Higher Kingston", - address_line2: "Yeovil", - postcode: "BA21 4AT", - holds_own_stock: true, - other_stock_owners: "None", - managing_agents_label: "None", - provider_type: "LA", - ) + mhclg_scheme = FactoryBot.create(:scheme, :created_now, owning_organisation: mhclg) + stock_owner_scheme = FactoryBot.create(:scheme, owning_organisation: stock_owner1) - pp "Seeded dummy FooBar LTD organisation" + other_schemes = first_run ? other_orgs.sample(3).map { |org| FactoryBot.create(:scheme, owning_organisation: org) } : [] - User.find_or_create_by!( - name: "Dummy user", - email: "dummy_org@example.com", - organisation: dummy_org, - role: "data_provider", - is_dpo: true, - ) do |user| - user.password = ENV["REVIEW_APP_USER_PASSWORD"] - user.confirmed_at = Time.zone.now - create_data_protection_confirmation(user) + [mhclg_scheme, stock_owner_scheme, *other_schemes].each do |scheme| + FactoryBot.create(:location, scheme:) + end + [beulahside, mhclg_scheme, *other_schemes].each do |scheme| + FactoryBot.create_list(:location, 3, scheme:) + end end - end - if (Rails.env.development? || Rails.env.review?) && Scheme.count.zero? - scheme1 = Scheme.create!( - service_name: "Beulahside Care", - sensitive: 0, - registered_under_care_act: 1, - support_type: 2, - scheme_type: 4, - intended_stay: "M", - primary_client_group: "O", - has_other_client_group: 1, - secondary_client_group: "H", - owning_organisation: org, - arrangement_type: "D", - confirmed: true, - created_at: Time.zone.now, - ) + other_org_users = first_run ? other_orgs.map { |org| org.users.first } : [] + users_with_logs = [provider, coordinator, support, stock_owner1_user, stock_owner2_user, managing_agent1_user, managing_agent2_user, provider_owner1, coordinator_owner1, *other_org_users] - scheme2 = Scheme.create!( - service_name: "Abdullahview Point", - sensitive: 0, - registered_under_care_act: 1, - support_type: 2, - scheme_type: 5, - intended_stay: "S", - primary_client_group: "D", - secondary_client_group: "E", - has_other_client_group: 1, - owning_organisation: org, - arrangement_type: "D", - confirmed: true, - created_at: Time.zone.now, - ) + if SalesLog.count.zero? + users_with_logs.each do |user| + FactoryBot.create(:sales_log, :shared_ownership_setup_complete, assigned_to: user) + FactoryBot.create(:sales_log, :discounted_ownership_setup_complete, assigned_to: user) + FactoryBot.create(:sales_log, :outright_sale_setup_complete, assigned_to: user) if Time.zone.today < Time.zone.local(2025, 4, 1) + FactoryBot.create(:sales_log, :completed, assigned_to: user) + FactoryBot.create_list(:sales_log, 2, :completed, :ignore_validation_errors, saledate: Time.zone.today - 1.year, assigned_to: user) - Scheme.create!( - service_name: "Caspermouth Center", - sensitive: 1, - registered_under_care_act: 1, - support_type: 4, - scheme_type: 7, - intended_stay: "X", - primary_client_group: "G", - has_other_client_group: 1, - secondary_client_group: "R", - owning_organisation: dummy_org, - arrangement_type: "D", - confirmed: true, - created_at: Time.zone.now, - ) + next unless FeatureToggle.allow_future_form_use? - Location.create!( - scheme: scheme1, - location_code: "E09000033", - location_admin_district: "Westminster", - postcode: "CU193AA", - name: "Rectory Road", - type_of_unit: 4, - units: 1, - mobility_type: "N", - ) + FactoryBot.create(:sales_log, :shared_ownership_setup_complete, saledate: Time.zone.today + 1.year, assigned_to: user) + FactoryBot.create(:sales_log, :discounted_ownership_setup_complete, saledate: Time.zone.today + 1.year, assigned_to: user) + FactoryBot.create(:sales_log, :completed, saledate: Time.zone.today + 1.year, assigned_to: user) + end - Location.create!( - scheme: scheme1, - location_code: "E09000033", - location_admin_district: "Westminster", - postcode: "DM250DC", - name: "Smithy Lane", - type_of_unit: 1, - units: 1, - mobility_type: "W", - ) - - Location.create!( - scheme: scheme2, - location_code: "E09000033", - location_admin_district: "Westminster", - postcode: "YX130WP", - name: "Smithy Lane", - type_of_unit: 2, - units: 1, - mobility_type: "W", - ) - pp "Seeded dummy schemes" - end - - if LocalAuthority.count.zero? - la_path = "config/local_authorities_data/initial_local_authorities.csv" - service = Imports::LocalAuthoritiesService.new(path: la_path) - service.call - end - - if (Rails.env.development? || Rails.env.review?) && LocalAuthorityLink.count.zero? - links_data_paths = ["config/local_authorities_data/local_authority_links_2023.csv", "config/local_authorities_data/local_authority_links_2022.csv"] - links_data_paths.each do |path| - service = Imports::LocalAuthorityLinksService.new(path:) - service.call + FactoryBot.create(:sales_log, :completed, assigned_to: managing_agent1_user, owning_organisation: mhclg) + FactoryBot.create(:sales_log, :completed, assigned_to: provider, owning_organisation: stock_owner1) end - pp "Seeded local authority links" - end - - if LaRentRange.count.zero? - Dir.glob("config/rent_range_data/*.csv").each do |path| - start_year = File.basename(path, ".csv") - service = Imports::RentRangesService.new(start_year:, path:) - service.call + if LettingsLog.count.zero? + users_with_logs.each do |user| + FactoryBot.create(:lettings_log, :setup_completed, assigned_to: user) + FactoryBot.create(:lettings_log, :completed, assigned_to: user) + if user.organisation.owned_schemes.any? + scheme = user.organisation.owned_schemes.first + FactoryBot.create(:lettings_log, :setup_completed, :sh, scheme:, location: scheme.locations.first, assigned_to: user) + end + FactoryBot.create_list(:lettings_log, 2, :completed, :ignore_validation_errors, startdate: Time.zone.today - 1.year, assigned_to: user) + + next unless FeatureToggle.allow_future_form_use? + + FactoryBot.create(:lettings_log, :setup_completed, startdate: Time.zone.today + 1.year, assigned_to: user) + FactoryBot.create(:lettings_log, :completed, startdate: Time.zone.today + 1.year, assigned_to: user) + if user.organisation.owned_schemes.any? + scheme = user.organisation.owned_schemes.first + FactoryBot.create(:lettings_log, :setup_completed, :sh, scheme:, location: scheme.locations.first, startdate: Time.zone.today + 1.year, assigned_to: user) + end + end + + FactoryBot.create(:lettings_log, :completed, assigned_to: managing_agent1_user, owning_organisation: mhclg) + FactoryBot.create(:lettings_log, :completed, assigned_to: provider, owning_organisation: stock_owner1) end - end - if LaSaleRange.count.zero? - Dir.glob("config/sale_range_data/*.csv").each do |path| - start_year = File.basename(path, ".csv") - service = Imports::SaleRangesService.new(start_year:, path:) - service.call + if LocalAuthorityLink.count.zero? + links_data_paths = ["config/local_authorities_data/local_authority_links_2023.csv", "config/local_authorities_data/local_authority_links_2022.csv"] + links_data_paths.each do |path| + service = Imports::LocalAuthorityLinksService.new(path:) + service.call + end end end end @@ -440,4 +179,3 @@ if LocalAuthority.count.zero? service = Imports::LocalAuthoritiesService.new(path:) service.call end -# rubocop:enable Rails/Output diff --git a/spec/components/check_answers_summary_list_card_component_spec.rb b/spec/components/check_answers_summary_list_card_component_spec.rb index aa9ad1be5..c08fb3f42 100644 --- a/spec/components/check_answers_summary_list_card_component_spec.rb +++ b/spec/components/check_answers_summary_list_card_component_spec.rb @@ -6,7 +6,7 @@ RSpec.describe CheckAnswersSummaryListCardComponent, type: :component do let(:rendered) { render_inline(component) } let(:user) { create(:user) } - let(:log) { create(:lettings_log, :completed, age2: 99, retirement_value_check: 1) } + let(:log) { create(:lettings_log, :completed, sex1: "F", age2: 99, retirement_value_check: 1) } let(:subsection_id) { "household_characteristics" } let(:subsection) { log.form.get_subsection(subsection_id) } let(:questions) { subsection.applicable_questions(log) } diff --git a/spec/components/lettings_log_summary_component_spec.rb b/spec/components/lettings_log_summary_component_spec.rb index 52b6b1955..78ce5d804 100644 --- a/spec/components/lettings_log_summary_component_spec.rb +++ b/spec/components/lettings_log_summary_component_spec.rb @@ -3,9 +3,11 @@ require "rails_helper" RSpec.describe LettingsLogSummaryComponent, type: :component do let(:support_user) { FactoryBot.create(:user, :support) } let(:coordinator_user) { FactoryBot.create(:user) } + let(:organisation) { create(:organisation, name: "MHCLG") } + let(:log_user) { create(:user, name: "Danny Rojas", organisation:) } let(:propcode) { "P3647" } let(:tenancycode) { "T62863" } - let(:lettings_log) { FactoryBot.create(:lettings_log, needstype: 1, tenancycode:, propcode:, startdate: Time.zone.today) } + let(:lettings_log) { FactoryBot.create(:lettings_log, assigned_to: log_user, needstype: 1, tenancycode:, propcode:, startdate: Time.zone.today) } context "when rendering lettings log for a support user" do it "shows the log summary with organisational relationships" do diff --git a/spec/components/sales_log_summary_component_spec.rb b/spec/components/sales_log_summary_component_spec.rb index ce9b3a45c..ec5c3cd80 100644 --- a/spec/components/sales_log_summary_component_spec.rb +++ b/spec/components/sales_log_summary_component_spec.rb @@ -16,7 +16,7 @@ RSpec.describe SalesLogSummaryComponent, type: :component do it "shows the log summary with organisational relationships" do result = render_inline(described_class.new(current_user: support_user, log: sales_log)) - expect(result).to have_content("Owned by\n MHCLG") + expect(result).to have_content("Owned by\n #{sales_log.owning_organisation.name}") expect(result).not_to have_content("Managed by") end end diff --git a/spec/factories/lettings_log.rb b/spec/factories/lettings_log.rb index 8ebe44875..c8c51ecf3 100644 --- a/spec/factories/lettings_log.rb +++ b/spec/factories/lettings_log.rb @@ -56,8 +56,8 @@ FactoryBot.define do status { 2 } tenancycode { Faker::Name.initials(number: 10) } age1_known { 0 } - age1 { 35 } - sex1 { "F" } + age1 { Faker::Number.within(range: 25..45) } + sex1 { %w[F M X R].sample } ethnic_group { 0 } ethnic { 2 } national { 13 } @@ -67,8 +67,8 @@ FactoryBot.define do relat2 { "P" } age2_known { 0 } details_known_2 { 0 } - age2 { 32 } - sex2 { "M" } + age2 { Faker::Number.within(range: 25..45) } + sex2 { %w[F M X R].sample } ecstat2 { 6 } homeless { 1 } underoccupation_benefitcap { 0 } @@ -163,7 +163,7 @@ FactoryBot.define do uprn_known { 0 } joint { 3 } address_line1 { "Address line 1" } - town_or_city { "London" } + town_or_city { Faker::Address.city } ppcodenk { 1 } tshortfall_known { 1 } after(:build) do |log, _evaluator| diff --git a/spec/factories/organisation.rb b/spec/factories/organisation.rb index 6623ff355..ecab5bf81 100644 --- a/spec/factories/organisation.rb +++ b/spec/factories/organisation.rb @@ -1,11 +1,11 @@ FactoryBot.define do factory :organisation do - name { "MHCLG" } - address_line1 { "2 Marsham Street" } - address_line2 { "London" } + name { Faker::Company.name } + address_line1 { Faker::Address.street_address } + address_line2 { Faker::Address.city } provider_type { "LA" } - housing_registration_no { "1234" } - postcode { "SW1P 4DF" } + housing_registration_no { rand(99_999).to_s } + postcode { Faker::Address.postcode } created_at { Time.zone.now } updated_at { Time.zone.now } holds_own_stock { true } @@ -41,7 +41,13 @@ FactoryBot.define do trait :prp do provider_type { "PRP" } end + trait :la do + provider_type { "LA" } + end + trait :holds_own_stock do + holds_own_stock { true } + end trait :does_not_own_stock do holds_own_stock { false } end @@ -53,6 +59,10 @@ FactoryBot.define do data_protection_confirmation { nil } end + + trait :if_unique do + initialize_with { Organisation.find_or_create_by(name:) } + end end factory :organisation_rent_period do diff --git a/spec/factories/sales_log.rb b/spec/factories/sales_log.rb index b809b9daf..687b042a4 100644 --- a/spec/factories/sales_log.rb +++ b/spec/factories/sales_log.rb @@ -5,7 +5,7 @@ FactoryBot.define do created_by { assigned_to } owning_organisation { assigned_to.organisation } - managing_organisation { owning_organisation } + managing_organisation { assigned_to.organisation } created_at { Time.zone.now } updated_at { Time.zone.now } trait :in_progress do @@ -32,6 +32,17 @@ FactoryBot.define do jointpur { 2 } noint { 2 } privacynotice { 1 } + purchid { rand(999_999_999).to_s } + end + trait :discounted_ownership_setup_complete do + saledate_today + ownershipsch { 2 } + type { 9 } + jointpur { 1 } + jointmore { 1 } + noint { 2 } + privacynotice { 1 } + purchid { rand(999_999_999).to_s } end trait :outright_sale_setup_complete do saledate_today @@ -67,14 +78,14 @@ FactoryBot.define do noint { 2 } privacynotice { 1 } age1_known { 0 } - age1 { 30 } - sex1 { "X" } + age1 { Faker::Number.within(range: 27..45) } + sex1 { %w[F M X R].sample } national { 18 } buy1livein { 1 } relat2 { "P" } proptype { 1 } age2_known { 0 } - age2 { 35 } + age2 { Faker::Number.within(range: 25..45) } builtype { 1 } ethnic { 3 } ethnic_group { 17 } @@ -104,16 +115,16 @@ FactoryBot.define do inc2mort { 1 } uprn_known { 0 } address_line1 { "Address line 1" } - town_or_city { "Town or city" } + town_or_city { Faker::Address.city } la_known { 1 } la { "E09000003" } savingsnk { 1 } prevown { 1 } prevshared { 2 } - sex3 { "X" } - sex4 { "X" } - sex5 { "X" } - sex6 { "X" } + sex3 { %w[F M X R].sample } + sex4 { %w[F M X R].sample } + sex5 { %w[F M X R].sample } + sex6 { %w[F M X R].sample } mortgage { 20_000 } ecstat3 { 9 } ecstat4 { 3 } @@ -122,6 +133,7 @@ FactoryBot.define do disabled { 1 } deposit { 80_000 } value { 110_000 } + value_value_check { 0 } grant { 10_000 } proplen { 10 } pregyrha { 1 } @@ -161,6 +173,13 @@ FactoryBot.define do log.uprn = "10033558653" log.uprn_selection = 1 end + if log.saledate >= Time.zone.local(2025, 4, 1) + log.relat2 = "X" if log.relat2 == "C" + log.relat3 = "X" if log.relat3 == "C" + log.relat4 = "X" if log.relat4 == "C" + log.relat5 = "X" if log.relat5 == "C" + log.relat6 = "X" if log.relat6 == "C" + end end end trait :with_uprn do @@ -174,5 +193,12 @@ FactoryBot.define do trait :imported do old_id { Random.hex } end + trait :ignore_validation_errors do + to_create do |instance| + instance.valid? + instance.errors.clear + instance.save!(validate: false) + end + end end end diff --git a/spec/factories/scheme.rb b/spec/factories/scheme.rb index 77d74b70a..34f98a8a6 100644 --- a/spec/factories/scheme.rb +++ b/spec/factories/scheme.rb @@ -41,5 +41,8 @@ FactoryBot.define do support_type { 2 } intended_stay { "M" } end + trait :created_now do + created_at { Time.zone.now } + end end end diff --git a/spec/factories/user.rb b/spec/factories/user.rb index 956d88332..8708e319b 100644 --- a/spec/factories/user.rb +++ b/spec/factories/user.rb @@ -1,7 +1,7 @@ FactoryBot.define do factory :user do sequence(:email) { "test#{SecureRandom.hex}@example.com" } - name { "Danny Rojas" } + name { Faker::Name.name } password { "pAssword1" } organisation { association :organisation, with_dsa: is_dpo ? false : true } role { "data_provider" } @@ -40,5 +40,9 @@ FactoryBot.define do ) end end + + trait :if_unique do + initialize_with { User.find_or_create_by(email:) } + end end end diff --git a/spec/features/lettings_log_spec.rb b/spec/features/lettings_log_spec.rb index 092ab2b40..efb7e7665 100644 --- a/spec/features/lettings_log_spec.rb +++ b/spec/features/lettings_log_spec.rb @@ -276,7 +276,7 @@ RSpec.describe "Lettings Log Features" do expect(breadcrumbs.length).to eq 3 expect(breadcrumbs[0].text).to eq "Home" expect(breadcrumbs[0][:href]).to eq root_path - expect(breadcrumbs[1].text).to eq "Lettings logs (MHCLG)" + expect(breadcrumbs[1].text).to eq "Lettings logs (#{lettings_log.owning_organisation.name})" expect(breadcrumbs[1][:href]).to eq lettings_logs_organisation_path(lettings_log.owning_organisation) expect(breadcrumbs[2].text).to eq "Log #{lettings_log.id}" expect(breadcrumbs[2][:href]).to eq lettings_log_path(lettings_log) @@ -292,7 +292,7 @@ RSpec.describe "Lettings Log Features" do expect(breadcrumbs.length).to eq 3 expect(breadcrumbs[0].text).to eq "Home" expect(breadcrumbs[0][:href]).to eq root_path - expect(breadcrumbs[1].text).to eq "Lettings logs (MHCLG)" + expect(breadcrumbs[1].text).to eq "Lettings logs (#{lettings_log.owning_organisation.name})" expect(breadcrumbs[1][:href]).to eq lettings_logs_organisation_path(lettings_log.owning_organisation) expect(breadcrumbs[2].text).to eq "Log #{lettings_log.id}" expect(breadcrumbs[2][:href]).to eq lettings_log_path(lettings_log) diff --git a/spec/features/sales_log_spec.rb b/spec/features/sales_log_spec.rb index ceff00f94..879f2b5c8 100644 --- a/spec/features/sales_log_spec.rb +++ b/spec/features/sales_log_spec.rb @@ -259,7 +259,7 @@ RSpec.describe "Sales Log Features" do expect(breadcrumbs.length).to eq 3 expect(breadcrumbs[0].text).to eq "Home" expect(breadcrumbs[0][:href]).to eq root_path - expect(breadcrumbs[1].text).to eq "Sales logs (MHCLG)" + expect(breadcrumbs[1].text).to eq "Sales logs (#{sales_log.owning_organisation.name})" expect(breadcrumbs[1][:href]).to eq sales_logs_organisation_path(sales_log.owning_organisation) expect(breadcrumbs[2].text).to eq "Log #{sales_log.id}" expect(breadcrumbs[2][:href]).to eq sales_log_path(sales_log.id) @@ -273,7 +273,7 @@ RSpec.describe "Sales Log Features" do expect(breadcrumbs.length).to eq 3 expect(breadcrumbs[0].text).to eq "Home" expect(breadcrumbs[0][:href]).to eq root_path - expect(breadcrumbs[1].text).to eq "Sales logs (MHCLG)" + expect(breadcrumbs[1].text).to eq "Sales logs (#{sales_log.owning_organisation.name})" expect(breadcrumbs[1][:href]).to eq sales_logs_organisation_path(sales_log.owning_organisation) expect(breadcrumbs[2].text).to eq "Log #{sales_log.id}" expect(breadcrumbs[2][:href]).to eq sales_log_path(sales_log.id) diff --git a/spec/features/schemes_spec.rb b/spec/features/schemes_spec.rb index 7c7d9f3fb..dfb56665b 100644 --- a/spec/features/schemes_spec.rb +++ b/spec/features/schemes_spec.rb @@ -5,9 +5,10 @@ RSpec.describe "Schemes scheme Features" do include SchemesHelpers context "when viewing list of schemes" do context "when I am signed as a coordinator user and there are schemes in the database" do - let!(:user) { FactoryBot.create(:user, :data_coordinator, last_sign_in_at: Time.zone.now) } - let!(:schemes) { FactoryBot.create_list(:scheme, 5, owning_organisation: user.organisation) } - let!(:scheme_to_search) { FactoryBot.create(:scheme, owning_organisation: user.organisation) } + let(:organisation) { FactoryBot.create(:organisation, name: "MHCLG") } + let!(:user) { FactoryBot.create(:user, :data_coordinator, organisation:, last_sign_in_at: Time.zone.now) } + let!(:schemes) { FactoryBot.create_list(:scheme, 5, owning_organisation: organisation) } + let!(:scheme_to_search) { FactoryBot.create(:scheme, owning_organisation: organisation) } before do Timecop.freeze(Time.zone.local(2024, 3, 1)) @@ -225,7 +226,7 @@ RSpec.describe "Schemes scheme Features" do end it "shows list of links to the organisation's schemes" do - click_on("Schemes (MHCLG)") + click_on("Schemes (#{user.organisation.name})") same_organisation_schemes.each do |scheme| expect(page).to have_link(scheme.service_name) expect(page).to have_content(scheme.id_to_display) diff --git a/spec/helpers/organisations_helper_spec.rb b/spec/helpers/organisations_helper_spec.rb index 9ebe12d1f..ea0d9b158 100644 --- a/spec/helpers/organisations_helper_spec.rb +++ b/spec/helpers/organisations_helper_spec.rb @@ -3,7 +3,7 @@ require "rails_helper" RSpec.describe OrganisationsHelper do include TagHelper describe "display_organisation_attributes" do - let(:organisation) { create(:organisation) } + let(:organisation) { create(:organisation, :la, :holds_own_stock, address_line1: "2 Marsham Street", address_line2: "London", postcode: "SW1P 4DF", housing_registration_no: 1234, organisation_rent_periods: []) } it "has the correct values" do expect(display_organisation_attributes(organisation)).to eq( diff --git a/spec/helpers/tab_nav_helper_spec.rb b/spec/helpers/tab_nav_helper_spec.rb index 4a2d13377..89f867775 100644 --- a/spec/helpers/tab_nav_helper_spec.rb +++ b/spec/helpers/tab_nav_helper_spec.rb @@ -15,7 +15,7 @@ RSpec.describe TabNavHelper do describe "#org_cell" do it "returns the users org name and role separated by a newline character" do - expected_html = "MHCLG\nData provider" + expected_html = "#{organisation.name}\nData provider" expect(org_cell(current_user)).to match(expected_html) end end diff --git a/spec/models/form_spec.rb b/spec/models/form_spec.rb index d3dc7e21f..74594c9b9 100644 --- a/spec/models/form_spec.rb +++ b/spec/models/form_spec.rb @@ -329,7 +329,7 @@ RSpec.describe Form, type: :model do end context "when a value is changed such that a radio and free input questions are no longer routed to" do - let(:log) { FactoryBot.create(:lettings_log, :completed, startdate: now) } + let(:log) { FactoryBot.create(:lettings_log, :completed, startdate: now, hhmemb: 2, details_known_2: 0, sex2: "M", relat2: "P", age2_known: 0, age2: 32, ecstat2: 6) } it "all attributes relating to that checkbox question are cleared" do expect(log.hhmemb).to be 2 diff --git a/spec/models/organisation_spec.rb b/spec/models/organisation_spec.rb index fe919a336..9b01845ae 100644 --- a/spec/models/organisation_spec.rb +++ b/spec/models/organisation_spec.rb @@ -207,7 +207,7 @@ RSpec.describe Organisation, type: :model do end describe "paper trail" do - let(:organisation) { create(:organisation) } + let(:organisation) { create(:organisation, name: "MHCLG") } it "creates a record of changes to a log" do expect { organisation.update!(name: "new test name") }.to change(organisation.versions, :count).by(1) diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 89f4f9dee..51cfc00bd 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -380,7 +380,7 @@ RSpec.describe User, type: :model do end describe "paper trail" do - let(:user) { create(:user) } + let(:user) { create(:user, name: "Danny Rojas") } it "creates a record of changes to a log" do expect { user.update!(name: "new test name") }.to change(user.versions, :count).by(1) diff --git a/spec/requests/merge_requests_controller_spec.rb b/spec/requests/merge_requests_controller_spec.rb index a73db8067..2e2488b93 100644 --- a/spec/requests/merge_requests_controller_spec.rb +++ b/spec/requests/merge_requests_controller_spec.rb @@ -49,7 +49,7 @@ RSpec.describe MergeRequestsController, type: :request do end it "shows the correct content" do - expect(page).to have_content("Which organisations are merging into MHCLG?") + expect(page).to have_content("Which organisations are merging into #{organisation.name}?") end end end @@ -64,7 +64,7 @@ RSpec.describe MergeRequestsController, type: :request do it "adds merging organisation to the page" do merge_request.reload - expect(page).to have_content("MHCLG") + expect(page).to have_content(organisation.name) expect(page).to have_content("Other Test Org") expect(page).to have_link("Remove") end @@ -657,7 +657,7 @@ RSpec.describe MergeRequestsController, type: :request do it "shows user outcomes after merge" do expect(page).to have_link("View all 4 Organisation with some users users (opens in a new tab)", href: users_organisation_path(organisation_with_some_users)) expect(page).to have_link("View all 12 Organisation with many users users (opens in a new tab)", href: users_organisation_path(organisation_with_some_more_users)) - expect(page).to have_link("View all 3 MHCLG users (opens in a new tab)", href: users_organisation_path(organisation)) + expect(page).to have_link("View all 3 #{organisation.name} users (opens in a new tab)", href: users_organisation_path(organisation)) expect(page).to have_content("Organisation with no users and Organisation with no users too have no users.") expect(page).to have_content("19 users after merge") end @@ -684,7 +684,7 @@ RSpec.describe MergeRequestsController, type: :request do it "shows scheme outcomes after merge" do expect(page).to have_link("View all 4 Organisation with some schemes schemes (opens in a new tab)", href: schemes_organisation_path(organisation_with_some_schemes)) expect(page).to have_link("View all 6 Organisation with many schemes schemes (opens in a new tab)", href: schemes_organisation_path(organisation_with_some_more_schemes)) - expect(page).to have_link("View all 3 MHCLG schemes (opens in a new tab)", href: schemes_organisation_path(organisation)) + expect(page).to have_link("View all 3 #{organisation.name} schemes (opens in a new tab)", href: schemes_organisation_path(organisation)) expect(page).to have_content("Organisation with no schemes and Organisation with no schemes too have no schemes.") expect(page).to have_content("13 schemes after merge") end @@ -710,8 +710,8 @@ RSpec.describe MergeRequestsController, type: :request do it "shows logs outcomes after merge" do expect(page).to have_link("View all 4 Organisation with some logs lettings logs (opens in a new tab)", href: lettings_logs_organisation_path(organisation_with_some_logs)) expect(page).to have_link("View all 2 Organisation with some logs sales logs (opens in a new tab)", href: sales_logs_organisation_path(organisation_with_some_logs)) - expect(page).to have_link("View all 2 MHCLG lettings logs (opens in a new tab)", href: lettings_logs_organisation_path(organisation)) - expect(page).to have_link("View all 3 MHCLG sales logs (opens in a new tab)", href: sales_logs_organisation_path(organisation)) + expect(page).to have_link("View all 2 #{organisation.name} lettings logs (opens in a new tab)", href: lettings_logs_organisation_path(organisation)) + expect(page).to have_link("View all 3 #{organisation.name} sales logs (opens in a new tab)", href: sales_logs_organisation_path(organisation)) expect(page).to have_content("Organisation with no logs and Organisation with no logs too have no lettings logs.") expect(page).to have_content("Organisation with no logs and Organisation with no logs too have no sales logs.") expect(page).to have_content("6 lettings logs after merge") diff --git a/spec/requests/organisations_controller_spec.rb b/spec/requests/organisations_controller_spec.rb index 5a300c56c..c99ad3cfb 100644 --- a/spec/requests/organisations_controller_spec.rb +++ b/spec/requests/organisations_controller_spec.rb @@ -1431,7 +1431,7 @@ RSpec.describe OrganisationsController, type: :request do end context "when a search parameter is passed" do - let!(:matching_user) { create(:user, organisation:, name: "joe", email: "matching@example.com") } + let!(:matching_user) { create(:user, organisation:, name: "abcdefghijklmnopqrstuvwxyz", email: "matching@example.com") } let(:org_user_count) { User.where(organisation:).count } before do @@ -1439,7 +1439,7 @@ RSpec.describe OrganisationsController, type: :request do end context "when our search string matches case" do - let(:search_param) { "joe" } + let(:search_param) { "abcdefghijklmnopqrstuvwxyz" } it "returns only matching results" do expect(page).to have_content(matching_user.name) @@ -1459,7 +1459,7 @@ RSpec.describe OrganisationsController, type: :request do end context "when we need case insensitive search" do - let(:search_param) { "Joe" } + let(:search_param) { "Abcdefghijklmnopqrstuvwxyz" } it "returns only matching results" do expect(page).to have_content(matching_user.name) @@ -1643,6 +1643,11 @@ RSpec.describe OrganisationsController, type: :request do context "when search results require pagination" do let(:search_param) { "MHCLG" } + before do + create_list(:organisation, 27, name: "MHCLG") + get "/organisations?search=#{search_param}" + end + it "has search and pagination in the title" do expect(page).to have_title("Organisations (27 organisations matching ‘#{search_param}’) (page 1 of 2) - Submit social housing lettings and sales data (CORE) - GOV.UK") end diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb index 2b7210402..12418d532 100644 --- a/spec/requests/users_controller_spec.rb +++ b/spec/requests/users_controller_spec.rb @@ -1264,8 +1264,8 @@ RSpec.describe UsersController, type: :request do end context "when user is signed in as a support user" do - let(:user) { create(:user, :support, organisation: create(:organisation, :without_dpc)) } - let(:other_user) { create(:user, organisation: user.organisation, last_sign_in_at: Time.zone.now) } + let(:user) { create(:user, :support, name: "Danny Rojas", organisation: create(:organisation, :without_dpc)) } + let(:other_user) { create(:user, name: "Danny Rojas", organisation: user.organisation, last_sign_in_at: Time.zone.now) } before do allow(user).to receive(:need_two_factor_authentication?).and_return(false) @@ -2002,7 +2002,7 @@ RSpec.describe UsersController, type: :request do end context "when the user is not part of the same organisation as the current user" do - let(:other_user) { create(:user) } + let(:other_user) { create(:user, organisation: create(:organisation, name: "Another org")) } let(:params) { { id: other_user.id, user: { name: new_name } } } it "updates the user" do @@ -2191,9 +2191,9 @@ RSpec.describe UsersController, type: :request do context "when different organisations manage the logs" do before do - create(:lettings_log, managing_organisation: other_user.organisation, assigned_to: other_user) - create(:lettings_log, managing_organisation: new_organisation_2, assigned_to: other_user) - create(:sales_log, managing_organisation: new_organisation_3, assigned_to: other_user) + create(:lettings_log, owning_organisation: other_user.organisation, managing_organisation: other_user.organisation, assigned_to: other_user) + create(:lettings_log, owning_organisation: other_user.organisation, managing_organisation: new_organisation_2, assigned_to: other_user) + create(:sales_log, owning_organisation: other_user.organisation, managing_organisation: new_organisation_3, assigned_to: other_user) patch "/users/#{other_user.id}/log-reassignment", headers:, params: end @@ -2209,8 +2209,8 @@ RSpec.describe UsersController, type: :request do context "when users organisation manages the logs" do before do - create(:lettings_log, owning_organisation: other_user.organisation, assigned_to: other_user) - create(:sales_log, owning_organisation: other_user.organisation, assigned_to: other_user) + create(:lettings_log, owning_organisation: other_user.organisation, managing_organisation: other_user.organisation, assigned_to: other_user) + create(:sales_log, owning_organisation: other_user.organisation, managing_organisation: other_user.organisation, assigned_to: other_user) patch "/users/#{other_user.id}/log-reassignment", headers:, params: end @@ -2222,15 +2222,15 @@ RSpec.describe UsersController, type: :request do context "when different organisations manage the logs" do before do - create(:lettings_log, owning_organisation: other_user.organisation, assigned_to: other_user) - create(:lettings_log, owning_organisation: new_organisation_2, assigned_to: other_user) + create(:lettings_log, owning_organisation: other_user.organisation, managing_organisation: other_user.organisation, assigned_to: other_user) + create(:lettings_log, owning_organisation: new_organisation_2, managing_organisation: other_user.organisation, assigned_to: other_user) create(:sales_log, owning_organisation: new_organisation_3, managing_organisation: other_user.organisation, assigned_to: other_user) patch "/users/#{other_user.id}/log-reassignment", headers:, params: end it "required the new org to have managing agent relationship with owning organisations" do expect(response).to have_http_status(:unprocessable_entity) - expect(page).to have_content("New org must be a managing agent of #{other_user.organisation_name}, #{new_organisation_2.name}, and #{new_organisation_3.name} to make this change.") + expect(page).to have_content("New org must be a managing agent of #{other_user.organisation.name}, #{new_organisation_2.name}, and #{new_organisation_3.name} to make this change.") end end end diff --git a/spec/services/csv/lettings_log_csv_service_spec.rb b/spec/services/csv/lettings_log_csv_service_spec.rb index 3134fa451..7a0e15a12 100644 --- a/spec/services/csv/lettings_log_csv_service_spec.rb +++ b/spec/services/csv/lettings_log_csv_service_spec.rb @@ -196,7 +196,7 @@ RSpec.describe Csv::LettingsLogCsvService do describe "the full CSV output" do context "when the requested log year is 2024" do let(:year) { 2024 } - let(:organisation) { create(:organisation, provider_type: "LA") } + let(:organisation) { create(:organisation, provider_type: "LA", name: "MHCLG") } let(:log) do create( :lettings_log, @@ -389,7 +389,7 @@ RSpec.describe Csv::LettingsLogCsvService do context "when the requested log year is 2023" do let(:year) { 2023 } - let(:organisation) { create(:organisation, provider_type: "LA") } + let(:organisation) { create(:organisation, provider_type: "LA", name: "MHCLG") } let(:log) do create( :lettings_log, diff --git a/spec/services/csv/sales_log_csv_service_spec.rb b/spec/services/csv/sales_log_csv_service_spec.rb index 4bbbaf645..3cf56af2e 100644 --- a/spec/services/csv/sales_log_csv_service_spec.rb +++ b/spec/services/csv/sales_log_csv_service_spec.rb @@ -4,7 +4,7 @@ RSpec.describe Csv::SalesLogCsvService do subject(:task) { Rake::Task["data_import:add_variable_definitions"] } let(:form_handler_mock) { instance_double(FormHandler) } - let(:organisation) { create(:organisation) } + let(:organisation) { create(:organisation, name: "MHCLG") } let(:fixed_time) { now } let(:now) { Time.zone.now } let(:user) { create(:user, :support, email: "billyboy@eyeKLAUD.com") } @@ -17,15 +17,23 @@ RSpec.describe Csv::SalesLogCsvService do created_at: fixed_time, updated_at: now, owning_organisation: organisation, + managing_organisation: organisation, purchid: nil, hholdcount: 3, + age1: 30, + sex1: "X", + age2: 35, + sex2: "X", + sex3: "X", age4_known: 1, + sex4: "X", details_known_5: 2, age6_known: nil, age6: nil, ecstat6: nil, relat6: nil, sex6: nil, + town_or_city: "Town or city", address_line1_as_entered: "address line 1 as entered", address_line2_as_entered: "address line 2 as entered", town_or_city_as_entered: "town or city as entered", diff --git a/spec/services/csv/scheme_csv_service_spec.rb b/spec/services/csv/scheme_csv_service_spec.rb index 57f009c65..77c0bae45 100644 --- a/spec/services/csv/scheme_csv_service_spec.rb +++ b/spec/services/csv/scheme_csv_service_spec.rb @@ -1,7 +1,7 @@ require "rails_helper" RSpec.describe Csv::SchemeCsvService do - let(:organisation) { create(:organisation) } + let(:organisation) { create(:organisation, name: "MHCLG") } let(:fixed_time) { Time.zone.local(2023, 6, 26) } let(:scheme) { create(:scheme, :export, owning_organisation: organisation, service_name: "Test name") } let(:location) { create(:location, :export, scheme:) } diff --git a/spec/services/exports/lettings_log_export_service_spec.rb b/spec/services/exports/lettings_log_export_service_spec.rb index 6f7d88c91..6a07af8dd 100644 --- a/spec/services/exports/lettings_log_export_service_spec.rb +++ b/spec/services/exports/lettings_log_export_service_spec.rb @@ -15,7 +15,8 @@ RSpec.describe Exports::LettingsLogExportService do let(:expected_data_filename) { "core_2021_2022_apr_mar_f0001_inc0001_pt001.xml" } let(:expected_manifest_filename) { "manifest.xml" } let(:start_time) { Time.zone.local(2022, 5, 1) } - let(:user) { FactoryBot.create(:user, email: "test1@example.com") } + let(:organisation) { create(:organisation, name: "MHCLG", housing_registration_no: 1234) } + let(:user) { FactoryBot.create(:user, email: "test1@example.com", organisation:) } def replace_entity_ids(lettings_log, export_template) export_template.sub!(/\{id\}/, (lettings_log["id"] + Exports::LettingsLogExportService::LOG_ID_OFFSET).to_s) @@ -79,7 +80,7 @@ RSpec.describe Exports::LettingsLogExportService do end context "and one lettings log is available for export" do - let!(:lettings_log) { FactoryBot.create(:lettings_log, :completed, assigned_to: user, propcode: "123", ppostcode_full: "SE2 6RT", postcode_full: "NW1 5TY", tenancycode: "BZ737", startdate: Time.zone.local(2022, 2, 2, 10, 36, 49), voiddate: Time.zone.local(2019, 11, 3), mrcdate: Time.zone.local(2020, 5, 5, 10, 36, 49), tenancylength: 5, underoccupation_benefitcap: 4) } + let!(:lettings_log) { FactoryBot.create(:lettings_log, :completed, assigned_to: user, age1: 35, sex1: "F", age2: 32, sex2: "M", propcode: "123", ppostcode_full: "SE2 6RT", postcode_full: "NW1 5TY", town_or_city: "London", tenancycode: "BZ737", startdate: Time.zone.local(2022, 2, 2, 10, 36, 49), voiddate: Time.zone.local(2019, 11, 3), mrcdate: Time.zone.local(2020, 5, 5, 10, 36, 49), tenancylength: 5, underoccupation_benefitcap: 4) } it "generates a ZIP export file with the expected filename" do expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) @@ -123,7 +124,7 @@ RSpec.describe Exports::LettingsLogExportService do end context "and one lettings log with unknown user details is available for export" do - let!(:lettings_log) { FactoryBot.create(:lettings_log, :completed, details_known_2: 1, assigned_to: user, propcode: "123", ppostcode_full: "SE2 6RT", postcode_full: "NW1 5TY", tenancycode: "BZ737", startdate: Time.zone.local(2022, 2, 2, 10, 36, 49), voiddate: Time.zone.local(2019, 11, 3), mrcdate: Time.zone.local(2020, 5, 5, 10, 36, 49), tenancylength: 5, underoccupation_benefitcap: 4) } + let!(:lettings_log) { FactoryBot.create(:lettings_log, :completed, details_known_2: 1, assigned_to: user, age1: 35, sex1: "F", propcode: "123", ppostcode_full: "SE2 6RT", postcode_full: "NW1 5TY", town_or_city: "London", tenancycode: "BZ737", startdate: Time.zone.local(2022, 2, 2, 10, 36, 49), voiddate: Time.zone.local(2019, 11, 3), mrcdate: Time.zone.local(2020, 5, 5, 10, 36, 49), tenancylength: 5, underoccupation_benefitcap: 4) } def replace_person_details(export_file) export_file.sub!("32", "-9") @@ -176,7 +177,7 @@ RSpec.describe Exports::LettingsLogExportService do end context "and one lettings log is available for export" do - let!(:lettings_log) { FactoryBot.create(:lettings_log, :completed, assigned_to: user, uprn_known: 1, uprn: "100023336956", propcode: "123", postcode_full: "SE2 6RT", ppostcode_full: "SE2 6RT", tenancycode: "BZ737", startdate: Time.zone.local(2023, 4, 2, 10, 36, 49), voiddate: Time.zone.local(2021, 11, 3), mrcdate: Time.zone.local(2022, 5, 5, 10, 36, 49), tenancylength: 5, underoccupation_benefitcap: 4) } + let!(:lettings_log) { FactoryBot.create(:lettings_log, :completed, assigned_to: user, age1: 35, sex1: "F", age2: 32, sex2: "M", uprn_known: 1, uprn: "100023336956", propcode: "123", postcode_full: "SE2 6RT", ppostcode_full: "SE2 6RT", tenancycode: "BZ737", startdate: Time.zone.local(2023, 4, 2, 10, 36, 49), voiddate: Time.zone.local(2021, 11, 3), mrcdate: Time.zone.local(2022, 5, 5, 10, 36, 49), tenancylength: 5, underoccupation_benefitcap: 4) } let(:expected_zip_filename) { "core_2023_2024_apr_mar_f0001_inc0001.zip" } let(:expected_data_filename) { "core_2023_2024_apr_mar_f0001_inc0001_pt001.xml" } let(:xml_export_file) { File.open("spec/fixtures/exports/general_needs_log_23_24.xml", "r:UTF-8") } @@ -396,7 +397,7 @@ RSpec.describe Exports::LettingsLogExportService do end context "and one lettings log with duplicate reference is available for export" do - let!(:lettings_log) { FactoryBot.create(:lettings_log, :completed, assigned_to: user, propcode: "123", ppostcode_full: "SE2 6RT", postcode_full: "NW1 5TY", tenancycode: "BZ737", startdate: Time.zone.local(2022, 2, 2, 10, 36, 49), voiddate: Time.zone.local(2019, 11, 3), mrcdate: Time.zone.local(2020, 5, 5, 10, 36, 49), tenancylength: 5, underoccupation_benefitcap: 4, duplicate_set_id: 123) } + let!(:lettings_log) { FactoryBot.create(:lettings_log, :completed, assigned_to: user, age1: 35, sex1: "F", age2: 32, sex2: "M", propcode: "123", ppostcode_full: "SE2 6RT", postcode_full: "NW1 5TY", town_or_city: "London", tenancycode: "BZ737", startdate: Time.zone.local(2022, 2, 2, 10, 36, 49), voiddate: Time.zone.local(2019, 11, 3), mrcdate: Time.zone.local(2020, 5, 5, 10, 36, 49), tenancylength: 5, underoccupation_benefitcap: 4, duplicate_set_id: 123) } def replace_duplicate_set_id(export_file) export_file.sub!("", "123") @@ -429,7 +430,7 @@ RSpec.describe Exports::LettingsLogExportService do end context "and one lettings log is available for export" do - let!(:lettings_log) { FactoryBot.create(:lettings_log, :completed, assigned_to: user, ppostcode_full: "A1 1AA", nationality_all_group: 13, propcode: "123", postcode_full: "SE2 6RT", tenancycode: "BZ737", startdate: Time.zone.local(2024, 4, 2, 10, 36, 49), voiddate: Time.zone.local(2021, 11, 3), mrcdate: Time.zone.local(2022, 5, 5, 10, 36, 49), tenancylength: 5, underoccupation_benefitcap: 4, creation_method: 2, bulk_upload_id: 1, address_line1_as_entered: "address line 1 as entered", address_line2_as_entered: "address line 2 as entered", town_or_city_as_entered: "town or city as entered", county_as_entered: "county as entered", postcode_full_as_entered: "AB1 2CD", la_as_entered: "la as entered") } + let!(:lettings_log) { FactoryBot.create(:lettings_log, :completed, assigned_to: user, age1: 35, sex1: "F", age2: 32, sex2: "M", ppostcode_full: "A1 1AA", nationality_all_group: 13, propcode: "123", postcode_full: "SE2 6RT", tenancycode: "BZ737", startdate: Time.zone.local(2024, 4, 2, 10, 36, 49), voiddate: Time.zone.local(2021, 11, 3), mrcdate: Time.zone.local(2022, 5, 5, 10, 36, 49), tenancylength: 5, underoccupation_benefitcap: 4, creation_method: 2, bulk_upload_id: 1, address_line1_as_entered: "address line 1 as entered", address_line2_as_entered: "address line 2 as entered", town_or_city_as_entered: "town or city as entered", county_as_entered: "county as entered", postcode_full_as_entered: "AB1 2CD", la_as_entered: "la as entered") } let(:expected_zip_filename) { "core_2024_2025_apr_mar_f0001_inc0001.zip" } let(:expected_data_filename) { "core_2024_2025_apr_mar_f0001_inc0001_pt001.xml" } let(:xml_export_file) { File.open("spec/fixtures/exports/general_needs_log_24_25.xml", "r:UTF-8") } @@ -450,13 +451,13 @@ RSpec.describe Exports::LettingsLogExportService do context "when exporting a supported housing lettings logs in XML" do let(:export_file) { File.open("spec/fixtures/exports/supported_housing_logs.xml", "r:UTF-8") } - let(:organisation) { FactoryBot.create(:organisation, provider_type: "LA") } + let(:organisation) { FactoryBot.create(:organisation, name: "MHCLG", provider_type: "LA", housing_registration_no: 1234) } let(:user) { FactoryBot.create(:user, organisation:, email: "fake@email.com") } let(:other_user) { FactoryBot.create(:user, organisation:, email: "other@email.com") } let(:scheme) { FactoryBot.create(:scheme, :export, owning_organisation: organisation) } let(:location) { FactoryBot.create(:location, :export, scheme:, startdate: Time.zone.local(2021, 4, 1), old_id: "1a") } - let(:lettings_log) { FactoryBot.create(:lettings_log, :completed, :export, :sh, scheme:, location:, assigned_to: user, updated_by: other_user, owning_organisation: organisation, startdate: Time.zone.local(2022, 2, 2, 10, 36, 49), voiddate: Time.zone.local(2019, 11, 3), mrcdate: Time.zone.local(2020, 5, 5, 10, 36, 49), underoccupation_benefitcap: 4, sheltered: 1) } + let(:lettings_log) { FactoryBot.create(:lettings_log, :completed, :export, :sh, scheme:, location:, assigned_to: user, updated_by: other_user, owning_organisation: organisation, age1: 35, sex1: "F", age2: 32, sex2: "M", startdate: Time.zone.local(2022, 2, 2, 10, 36, 49), voiddate: Time.zone.local(2019, 11, 3), mrcdate: Time.zone.local(2020, 5, 5, 10, 36, 49), underoccupation_benefitcap: 4, sheltered: 1) } before do lettings_log.postcode_full = nil diff --git a/spec/services/exports/organisation_export_service_spec.rb b/spec/services/exports/organisation_export_service_spec.rb index 4de0e84a8..43ca19095 100644 --- a/spec/services/exports/organisation_export_service_spec.rb +++ b/spec/services/exports/organisation_export_service_spec.rb @@ -42,7 +42,7 @@ RSpec.describe Exports::OrganisationExportService do end context "and one organisation is available for export" do - let!(:organisation) { create(:organisation) } + let!(:organisation) { create(:organisation, name: "MHCLG", address_line1: "2 Marsham Street", address_line2: "London", postcode: "SW1P 4DF", housing_registration_no: "1234") } it "generates a ZIP export file with the expected filename" do expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) diff --git a/spec/services/exports/user_export_service_spec.rb b/spec/services/exports/user_export_service_spec.rb index 713d6f907..8a0e22267 100644 --- a/spec/services/exports/user_export_service_spec.rb +++ b/spec/services/exports/user_export_service_spec.rb @@ -12,7 +12,7 @@ RSpec.describe Exports::UserExportService do let(:expected_data_filename) { "users_2024_2025_apr_mar_f0001_inc0001_pt001.xml" } let(:expected_manifest_filename) { "manifest.xml" } let(:start_time) { Time.zone.local(2022, 5, 1) } - let(:organisation) { create(:organisation, with_dsa: false) } + let(:organisation) { create(:organisation, name: "MHCLG", with_dsa: false) } def replace_entity_ids(user, export_template) export_template.sub!(/\{id\}/, user["id"].to_s) @@ -42,7 +42,7 @@ RSpec.describe Exports::UserExportService do end context "and one user is available for export" do - let!(:user) { create(:user, organisation:, phone_extension: "123") } + let!(:user) { create(:user, organisation:, name: "Danny Rojas", phone_extension: "123") } it "generates a ZIP export file with the expected filename" do expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) diff --git a/spec/services/merge/merge_organisations_service_spec.rb b/spec/services/merge/merge_organisations_service_spec.rb index 7163a142f..fbb52c2a5 100644 --- a/spec/services/merge/merge_organisations_service_spec.rb +++ b/spec/services/merge/merge_organisations_service_spec.rb @@ -28,7 +28,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "moves the users from merging organisation to absorbing organisation" do expect(Rails.logger).to receive(:info).with("Merged users from fake org:") - expect(Rails.logger).to receive(:info).with("\tDanny Rojas (#{merging_organisation.data_protection_officers.first.email})") + expect(Rails.logger).to receive(:info).with("\t#{merging_organisation.data_protection_officers.first.name} (#{merging_organisation.data_protection_officers.first.email})") expect(Rails.logger).to receive(:info).with("\tfake name (fake@email.com)") expect(Rails.logger).to receive(:info).with("New schemes from fake org:") merge_organisations_service.call @@ -477,7 +477,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "logs the merged schemes and locations" do expect(Rails.logger).to receive(:info).with("Merged users from fake org:") - expect(Rails.logger).to receive(:info).with("\tDanny Rojas (#{merging_organisation.data_protection_officers.first.email})") + expect(Rails.logger).to receive(:info).with("\t#{merging_organisation.data_protection_officers.first.name} (#{merging_organisation.data_protection_officers.first.email})") expect(Rails.logger).to receive(:info).with("\tfake name (fake@email.com)") expect(Rails.logger).to receive(:info).with("New schemes from fake org:") expect(Rails.logger).to receive(:info).with(/\t#{scheme.service_name} \(S/) @@ -623,7 +623,7 @@ RSpec.describe Merge::MergeOrganisationsService do context "and merging sales logs" do let(:owning_organisation) { create(:organisation, holds_own_stock: true) } - let!(:sales_log) { create(:sales_log, saledate: Time.zone.today, owning_organisation: merging_organisation, purchid: "owned") } + let!(:sales_log) { create(:sales_log, saledate: Time.zone.today, owning_organisation: merging_organisation, managing_organisation: merging_organisation, purchid: "owned") } let!(:managed_sales_log) { create(:sales_log, saledate: Time.zone.today, purchid: "managed") } before do @@ -744,7 +744,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "logs the merged schemes" do expect(Rails.logger).to receive(:info).with("Merged users from fake org:") - expect(Rails.logger).to receive(:info).with("\tDanny Rojas (#{merging_organisation.data_protection_officers.first.email})") + expect(Rails.logger).to receive(:info).with("\t#{merging_organisation.data_protection_officers.first.name} (#{merging_organisation.data_protection_officers.first.email})") expect(Rails.logger).to receive(:info).with("\tfake name (fake@email.com)") expect(Rails.logger).to receive(:info).with("New schemes from fake org:") expect(Rails.logger).to receive(:info).with(/\t#{scheme.service_name} \(S/) @@ -922,7 +922,7 @@ RSpec.describe Merge::MergeOrganisationsService do let!(:merging_organisation_user) { create(:user, organisation: merging_organisation, name: "fake name", email: "fake@email.com") } before do - create_list(:user, 5, organisation: merging_organisation_too) + create_list(:user, 5, organisation: merging_organisation_too, name: "Danny Rojas") end it "sets merge date and absorbing organisation on merged organisations" do @@ -961,10 +961,11 @@ RSpec.describe Merge::MergeOrganisationsService do context "and merging users" do it "moves the users from merging organisations to absorbing organisation" do expect(Rails.logger).to receive(:info).with("Merged users from fake org:") - expect(Rails.logger).to receive(:info).with("\tDanny Rojas (#{merging_organisation.data_protection_officers.first.email})") + expect(Rails.logger).to receive(:info).with("\t#{merging_organisation.data_protection_officers.first.name} (#{merging_organisation.data_protection_officers.first.email})") expect(Rails.logger).to receive(:info).with("\tfake name (fake@email.com)") expect(Rails.logger).to receive(:info).with("Merged users from second org:") - expect(Rails.logger).to receive(:info).with(/\tDanny Rojas/).exactly(6).times + expect(Rails.logger).to receive(:info).with(/\tDanny Rojas/).exactly(5).times + expect(Rails.logger).to receive(:info).with(/\t#{merging_organisation_too.data_protection_officers.first.name}/) expect(Rails.logger).to receive(:info).with("New schemes from fake org:") expect(Rails.logger).to receive(:info).with("New schemes from second org:") merge_organisations_service.call @@ -1113,7 +1114,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "moves the users from merging organisation to absorbing organisation" do expect(Rails.logger).to receive(:info).with("Merged users from fake org:") - expect(Rails.logger).to receive(:info).with("\tDanny Rojas (#{merging_organisation.data_protection_officers.first.email})") + expect(Rails.logger).to receive(:info).with("\t#{merging_organisation.data_protection_officers.first.name} (#{merging_organisation.data_protection_officers.first.email})") expect(Rails.logger).to receive(:info).with("\tfake name (fake@email.com)") expect(Rails.logger).to receive(:info).with("New schemes from fake org:") merge_organisations_service.call @@ -1251,7 +1252,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "logs the merged schemes" do expect(Rails.logger).to receive(:info).with("Merged users from fake org:") - expect(Rails.logger).to receive(:info).with("\tDanny Rojas (#{merging_organisation.data_protection_officers.first.email})") + expect(Rails.logger).to receive(:info).with("\t#{merging_organisation.data_protection_officers.first.name} (#{merging_organisation.data_protection_officers.first.email})") expect(Rails.logger).to receive(:info).with("\tfake name (fake@email.com)") expect(Rails.logger).to receive(:info).with("New schemes from fake org:") expect(Rails.logger).to receive(:info).with(/\t#{scheme.service_name} \(S/) @@ -1462,7 +1463,7 @@ RSpec.describe Merge::MergeOrganisationsService do it "logs the merged schemes" do expect(Rails.logger).to receive(:info).with("Merged users from fake org:") - expect(Rails.logger).to receive(:info).with("\tDanny Rojas (#{merging_organisation.data_protection_officers.first.email})") + expect(Rails.logger).to receive(:info).with("\t#{merging_organisation.data_protection_officers.first.name} (#{merging_organisation.data_protection_officers.first.email})") expect(Rails.logger).to receive(:info).with("\tfake name (fake@email.com)") expect(Rails.logger).to receive(:info).with("New schemes from fake org:") expect(Rails.logger).to receive(:info).with(/\t#{scheme.service_name} \(S/) @@ -1590,15 +1591,16 @@ RSpec.describe Merge::MergeOrganisationsService do let!(:merging_organisation_user) { create(:user, organisation: merging_organisation, name: "fake name", email: "fake@email.com") } before do - create_list(:user, 5, organisation: merging_organisation_too) + create_list(:user, 5, organisation: merging_organisation_too, name: "Danny Rojas") end it "moves the users from merging organisations to absorbing organisation" do expect(Rails.logger).to receive(:info).with("Merged users from fake org:") - expect(Rails.logger).to receive(:info).with("\tDanny Rojas (#{merging_organisation.data_protection_officers.first.email})") + expect(Rails.logger).to receive(:info).with("\t#{merging_organisation.data_protection_officers.first.name} (#{merging_organisation.data_protection_officers.first.email})") expect(Rails.logger).to receive(:info).with("\tfake name (fake@email.com)") expect(Rails.logger).to receive(:info).with("Merged users from second org:") - expect(Rails.logger).to receive(:info).with(/\tDanny Rojas/).exactly(6).times + expect(Rails.logger).to receive(:info).with(/\tDanny Rojas/).exactly(5).times + expect(Rails.logger).to receive(:info).with(/\t#{merging_organisation_too.data_protection_officers.first.name}/) expect(Rails.logger).to receive(:info).with("New schemes from fake org:") expect(Rails.logger).to receive(:info).with("New schemes from second org:") merge_organisations_service.call From b3dca512162f267dc4d584ba091bc4a73c99172a Mon Sep 17 00:00:00 2001 From: Rachael Booth Date: Thu, 28 Nov 2024 09:45:38 +0000 Subject: [PATCH 02/36] CLDC-3753: Update soft income validations (#2779) * CLDC-3753: Update soft income validations * Update tests * Fix linting * Include reference to informative text translations copy * Combine min and max checks for income based on ecstat * Fixes * Update tests --- ...yer1_income_discounted_max_value_check.rb} | 6 +- ...rb => buyer1_income_ecstat_value_check.rb} | 18 +- ...yer2_income_discounted_max_value_check.rb} | 6 +- ...rb => buyer2_income_ecstat_value_check.rb} | 18 +- .../pages/combined_income_max_value_check.rb | 2 +- .../subsections/household_characteristics.rb | 4 +- .../income_benefits_and_savings.rb | 8 +- .../sales/subsections/property_information.rb | 8 +- .../validations/sales/soft_validations.rb | 87 ++++-- .../forms/2023/sales/soft_validations.en.yml | 28 +- .../forms/2024/sales/soft_validations.en.yml | 16 +- .../forms/2025/sales/soft_validations.en.yml | 22 +- ...income_discounted_max_value_check_spec.rb} | 4 +- ... buyer1_income_ecstat_value_check_spec.rb} | 8 +- ...income_discounted_max_value_check_spec.rb} | 4 +- ... buyer2_income_ecstat_value_check_spec.rb} | 8 +- .../combined_income_max_value_check_spec.rb | 2 +- .../household_characteristics_spec.rb | 130 +-------- .../income_benefits_and_savings_spec.rb | 145 +++++---- .../sales/soft_validations_spec.rb | 275 +++++++++++++----- spec/services/documentation_generator_spec.rb | 4 +- 21 files changed, 437 insertions(+), 366 deletions(-) rename app/models/form/sales/pages/{buyer1_income_max_value_check.rb => buyer1_income_discounted_max_value_check.rb} (78%) rename app/models/form/sales/pages/{buyer1_income_min_value_check.rb => buyer1_income_ecstat_value_check.rb} (69%) rename app/models/form/sales/pages/{buyer2_income_max_value_check.rb => buyer2_income_discounted_max_value_check.rb} (78%) rename app/models/form/sales/pages/{buyer2_income_min_value_check.rb => buyer2_income_ecstat_value_check.rb} (69%) rename spec/models/form/sales/pages/{buyer1_income_max_value_check_spec.rb => buyer1_income_discounted_max_value_check_spec.rb} (84%) rename spec/models/form/sales/pages/{buyer1_income_min_value_check_spec.rb => buyer1_income_ecstat_value_check_spec.rb} (75%) rename spec/models/form/sales/pages/{buyer2_income_max_value_check_spec.rb => buyer2_income_discounted_max_value_check_spec.rb} (84%) rename spec/models/form/sales/pages/{buyer2_income_min_value_check_spec.rb => buyer2_income_ecstat_value_check_spec.rb} (72%) diff --git a/app/models/form/sales/pages/buyer1_income_max_value_check.rb b/app/models/form/sales/pages/buyer1_income_discounted_max_value_check.rb similarity index 78% rename from app/models/form/sales/pages/buyer1_income_max_value_check.rb rename to app/models/form/sales/pages/buyer1_income_discounted_max_value_check.rb index 55599ff26..6a774d36f 100644 --- a/app/models/form/sales/pages/buyer1_income_max_value_check.rb +++ b/app/models/form/sales/pages/buyer1_income_discounted_max_value_check.rb @@ -1,12 +1,12 @@ -class Form::Sales::Pages::Buyer1IncomeMaxValueCheck < ::Form::Page +class Form::Sales::Pages::Buyer1IncomeDiscountedMaxValueCheck < ::Form::Page def initialize(id, hsh, subsection, check_answers_card_number:) super(id, hsh, subsection) @depends_on = [ { - "income1_over_soft_max?" => true, + "income1_over_soft_max_for_discounted_ownership?" => true, }, ] - @copy_key = "sales.soft_validations.income1_value_check.max" + @copy_key = "sales.soft_validations.income1_value_check.discounted" @title_text = { "translation" => "forms.#{form.start_date.year}.#{@copy_key}.title_text", "arguments" => [ diff --git a/app/models/form/sales/pages/buyer1_income_min_value_check.rb b/app/models/form/sales/pages/buyer1_income_ecstat_value_check.rb similarity index 69% rename from app/models/form/sales/pages/buyer1_income_min_value_check.rb rename to app/models/form/sales/pages/buyer1_income_ecstat_value_check.rb index 9fc85bb76..34b408432 100644 --- a/app/models/form/sales/pages/buyer1_income_min_value_check.rb +++ b/app/models/form/sales/pages/buyer1_income_ecstat_value_check.rb @@ -1,12 +1,12 @@ -class Form::Sales::Pages::Buyer1IncomeMinValueCheck < ::Form::Page +class Form::Sales::Pages::Buyer1IncomeEcstatValueCheck < ::Form::Page def initialize(id, hsh, subsection) super @depends_on = [ { - "income1_under_soft_min?" => true, + "income1_outside_soft_range_for_ecstat?" => true, }, ] - @copy_key = "sales.soft_validations.income1_value_check.min" + @copy_key = "sales.soft_validations.income1_value_check.ecstat" @title_text = { "translation" => "forms.#{form.start_date.year}.#{@copy_key}.title_text", "arguments" => [ @@ -15,16 +15,16 @@ class Form::Sales::Pages::Buyer1IncomeMinValueCheck < ::Form::Page "arguments_for_key" => "income1", "i18n_template" => "income", }, - { - "key" => "income_soft_min_for_ecstat", - "arguments_for_key" => "ecstat1", - "i18n_template" => "minimum", - }, ], } @informative_text = { "translation" => "forms.#{form.start_date.year}.#{@copy_key}.informative_text", - "arguments" => [], + "arguments" => [ + { + "key" => "income1_more_or_less_text", + "i18n_template" => "more_or_less", + }, + ], } end diff --git a/app/models/form/sales/pages/buyer2_income_max_value_check.rb b/app/models/form/sales/pages/buyer2_income_discounted_max_value_check.rb similarity index 78% rename from app/models/form/sales/pages/buyer2_income_max_value_check.rb rename to app/models/form/sales/pages/buyer2_income_discounted_max_value_check.rb index deece885e..356a5ed20 100644 --- a/app/models/form/sales/pages/buyer2_income_max_value_check.rb +++ b/app/models/form/sales/pages/buyer2_income_discounted_max_value_check.rb @@ -1,12 +1,12 @@ -class Form::Sales::Pages::Buyer2IncomeMaxValueCheck < ::Form::Page +class Form::Sales::Pages::Buyer2IncomeDiscountedMaxValueCheck < ::Form::Page def initialize(id, hsh, subsection, check_answers_card_number:) super(id, hsh, subsection) @depends_on = [ { - "income2_over_soft_max?" => true, + "income2_over_soft_max_for_discounted_ownership?" => true, }, ] - @copy_key = "sales.soft_validations.income2_value_check.max" + @copy_key = "sales.soft_validations.income2_value_check.discounted" @title_text = { "translation" => "forms.#{form.start_date.year}.#{@copy_key}.title_text", "arguments" => [ diff --git a/app/models/form/sales/pages/buyer2_income_min_value_check.rb b/app/models/form/sales/pages/buyer2_income_ecstat_value_check.rb similarity index 69% rename from app/models/form/sales/pages/buyer2_income_min_value_check.rb rename to app/models/form/sales/pages/buyer2_income_ecstat_value_check.rb index a7b68cd10..3b9503669 100644 --- a/app/models/form/sales/pages/buyer2_income_min_value_check.rb +++ b/app/models/form/sales/pages/buyer2_income_ecstat_value_check.rb @@ -1,12 +1,12 @@ -class Form::Sales::Pages::Buyer2IncomeMinValueCheck < ::Form::Page +class Form::Sales::Pages::Buyer2IncomeEcstatValueCheck < ::Form::Page def initialize(id, hsh, subsection) super @depends_on = [ { - "income2_under_soft_min?" => true, + "income2_outside_soft_range_for_ecstat?" => true, }, ] - @copy_key = "sales.soft_validations.income2_value_check.min" + @copy_key = "sales.soft_validations.income2_value_check.ecstat" @title_text = { "translation" => "forms.#{form.start_date.year}.#{@copy_key}.title_text", "arguments" => [ @@ -15,16 +15,16 @@ class Form::Sales::Pages::Buyer2IncomeMinValueCheck < ::Form::Page "arguments_for_key" => "income2", "i18n_template" => "income", }, - { - "key" => "income_soft_min_for_ecstat", - "arguments_for_key" => "ecstat2", - "i18n_template" => "minimum", - }, ], } @informative_text = { "translation" => "forms.#{form.start_date.year}.#{@copy_key}.informative_text", - "arguments" => [], + "arguments" => [ + { + "key" => "income2_more_or_less_text", + "i18n_template" => "more_or_less", + }, + ], } end diff --git a/app/models/form/sales/pages/combined_income_max_value_check.rb b/app/models/form/sales/pages/combined_income_max_value_check.rb index 1cd1fd851..89eea3027 100644 --- a/app/models/form/sales/pages/combined_income_max_value_check.rb +++ b/app/models/form/sales/pages/combined_income_max_value_check.rb @@ -3,7 +3,7 @@ class Form::Sales::Pages::CombinedIncomeMaxValueCheck < ::Form::Page super(id, hsh, subsection) @depends_on = [ { - "combined_income_over_soft_max?" => true, + "combined_income_over_soft_max_for_discounted_ownership?" => true, }, ] @copy_key = "sales.soft_validations.combined_income_value_check" diff --git a/app/models/form/sales/subsections/household_characteristics.rb b/app/models/form/sales/subsections/household_characteristics.rb index e839b1979..5c3c0b9e3 100644 --- a/app/models/form/sales/subsections/household_characteristics.rb +++ b/app/models/form/sales/subsections/household_characteristics.rb @@ -35,7 +35,7 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection Form::Sales::Pages::Buyer1WorkingSituation.new(nil, nil, self), Form::Sales::Pages::RetirementValueCheck.new("working_situation_1_retirement_value_check", nil, self, person_index: 1), (Form::Sales::Pages::NotRetiredValueCheck.new("working_situation_1_not_retired_value_check", nil, self, person_index: 1) if form.start_year_2024_or_later?), - Form::Sales::Pages::Buyer1IncomeMinValueCheck.new("working_situation_buyer_1_income_min_value_check", nil, self), + Form::Sales::Pages::Buyer1IncomeEcstatValueCheck.new("working_situation_buyer_1_income_value_check", nil, self), Form::Sales::Pages::Buyer1LiveInProperty.new(nil, nil, self), Form::Sales::Pages::BuyerLiveInValueCheck.new("buyer_1_live_in_property_value_check", nil, self, person_index: 1), (form.start_year_2025_or_later? ? Form::Sales::Pages::Buyer2RelationshipToBuyer1YesNo.new(nil, nil, self) : Form::Sales::Pages::Buyer2RelationshipToBuyer1.new(nil, nil, self)), @@ -51,7 +51,7 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection Form::Sales::Pages::Buyer2WorkingSituation.new(nil, nil, self), Form::Sales::Pages::RetirementValueCheck.new("working_situation_2_retirement_value_check_joint_purchase", nil, self, person_index: 2), (Form::Sales::Pages::NotRetiredValueCheck.new("working_situation_2_not_retired_value_check_joint_purchase", nil, self, person_index: 2) if form.start_year_2024_or_later?), - Form::Sales::Pages::Buyer2IncomeMinValueCheck.new("working_situation_buyer_2_income_min_value_check", nil, self), + Form::Sales::Pages::Buyer2IncomeEcstatValueCheck.new("working_situation_buyer_2_income_value_check", nil, self), Form::Sales::Pages::PersonStudentNotChildValueCheck.new("buyer_2_working_situation_student_not_child_value_check", nil, self, person_index: 2), Form::Sales::Pages::Buyer2LiveInProperty.new(nil, nil, self), Form::Sales::Pages::BuyerLiveInValueCheck.new("buyer_2_live_in_property_value_check", nil, self, person_index: 2), diff --git a/app/models/form/sales/subsections/income_benefits_and_savings.rb b/app/models/form/sales/subsections/income_benefits_and_savings.rb index 9244267b9..84d3c49a9 100644 --- a/app/models/form/sales/subsections/income_benefits_and_savings.rb +++ b/app/models/form/sales/subsections/income_benefits_and_savings.rb @@ -16,16 +16,16 @@ class Form::Sales::Subsections::IncomeBenefitsAndSavings < ::Form::Subsection def pages @pages ||= [ Form::Sales::Pages::Buyer1Income.new(nil, nil, self), - Form::Sales::Pages::Buyer1IncomeMinValueCheck.new("buyer_1_income_min_value_check", nil, self), - Form::Sales::Pages::Buyer1IncomeMaxValueCheck.new("buyer_1_income_max_value_check", nil, self, check_answers_card_number: 1), + Form::Sales::Pages::Buyer1IncomeEcstatValueCheck.new("buyer_1_income_ecstat_value_check", nil, self), + Form::Sales::Pages::Buyer1IncomeDiscountedMaxValueCheck.new("buyer_1_income_discounted_max_value_check", nil, self, check_answers_card_number: 1), Form::Sales::Pages::CombinedIncomeMaxValueCheck.new("buyer_1_combined_income_max_value_check", nil, self, check_answers_card_number: 1), Form::Sales::Pages::MortgageValueCheck.new("buyer_1_income_mortgage_value_check", nil, self, 1), Form::Sales::Pages::Buyer1Mortgage.new(nil, nil, self), Form::Sales::Pages::MortgageValueCheck.new("buyer_1_mortgage_value_check", nil, self, 1), Form::Sales::Pages::Buyer2Income.new(nil, nil, self), Form::Sales::Pages::MortgageValueCheck.new("buyer_2_income_mortgage_value_check", nil, self, 2), - Form::Sales::Pages::Buyer2IncomeMinValueCheck.new("buyer_2_income_min_value_check", nil, self), - Form::Sales::Pages::Buyer2IncomeMaxValueCheck.new("buyer_2_income_max_value_check", nil, self, check_answers_card_number: 2), + Form::Sales::Pages::Buyer2IncomeEcstatValueCheck.new("buyer_2_income_ecstat_value_check", nil, self), + Form::Sales::Pages::Buyer2IncomeDiscountedMaxValueCheck.new("buyer_2_income_discounted_max_value_check", nil, self, check_answers_card_number: 2), Form::Sales::Pages::CombinedIncomeMaxValueCheck.new("buyer_2_combined_income_max_value_check", nil, self, check_answers_card_number: 2), Form::Sales::Pages::Buyer2Mortgage.new(nil, nil, self), Form::Sales::Pages::MortgageValueCheck.new("buyer_2_mortgage_value_check", nil, self, 2), diff --git a/app/models/form/sales/subsections/property_information.rb b/app/models/form/sales/subsections/property_information.rb index 5d4021681..28c0ad004 100644 --- a/app/models/form/sales/subsections/property_information.rb +++ b/app/models/form/sales/subsections/property_information.rb @@ -31,8 +31,8 @@ class Form::Sales::Subsections::PropertyInformation < ::Form::Subsection Form::Sales::Pages::UprnSelection.new(nil, nil, self), Form::Sales::Pages::AddressFallback.new(nil, nil, self), Form::Sales::Pages::PropertyLocalAuthority.new(nil, nil, self), - Form::Sales::Pages::Buyer1IncomeMaxValueCheck.new("local_authority_buyer_1_income_max_value_check", nil, self, check_answers_card_number: nil), - Form::Sales::Pages::Buyer2IncomeMaxValueCheck.new("local_authority_buyer_2_income_max_value_check", nil, self, check_answers_card_number: nil), + Form::Sales::Pages::Buyer1IncomeDiscountedMaxValueCheck.new("local_authority_buyer_1_income_max_value_check", nil, self, check_answers_card_number: nil), + Form::Sales::Pages::Buyer2IncomeDiscountedMaxValueCheck.new("local_authority_buyer_2_income_max_value_check", nil, self, check_answers_card_number: nil), Form::Sales::Pages::CombinedIncomeMaxValueCheck.new("local_authority_combined_income_max_value_check", nil, self, check_answers_card_number: nil), Form::Sales::Pages::AboutPriceValueCheck.new("about_price_la_value_check", nil, self), ] @@ -42,8 +42,8 @@ class Form::Sales::Subsections::PropertyInformation < ::Form::Subsection Form::Sales::Pages::UprnConfirmation.new(nil, nil, self), Form::Sales::Pages::Address.new(nil, nil, self), Form::Sales::Pages::PropertyLocalAuthority.new(nil, nil, self), - Form::Sales::Pages::Buyer1IncomeMaxValueCheck.new("local_authority_buyer_1_income_max_value_check", nil, self, check_answers_card_number: nil), - Form::Sales::Pages::Buyer2IncomeMaxValueCheck.new("local_authority_buyer_2_income_max_value_check", nil, self, check_answers_card_number: nil), + Form::Sales::Pages::Buyer1IncomeDiscountedMaxValueCheck.new("local_authority_buyer_1_income_max_value_check", nil, self, check_answers_card_number: nil), + Form::Sales::Pages::Buyer2IncomeDiscountedMaxValueCheck.new("local_authority_buyer_2_income_max_value_check", nil, self, check_answers_card_number: nil), Form::Sales::Pages::CombinedIncomeMaxValueCheck.new("local_authority_combined_income_max_value_check", nil, self, check_answers_card_number: nil), Form::Sales::Pages::AboutPriceValueCheck.new("about_price_la_value_check", nil, self), ] diff --git a/app/models/validations/sales/soft_validations.rb b/app/models/validations/sales/soft_validations.rb index d53391dd1..2bc574774 100644 --- a/app/models/validations/sales/soft_validations.rb +++ b/app/models/validations/sales/soft_validations.rb @@ -2,41 +2,59 @@ module Validations::Sales::SoftValidations include Validations::Sales::SaleInformationValidations ALLOWED_INCOME_RANGES_SALES = { - 1 => OpenStruct.new(soft_min: 5000), - 2 => OpenStruct.new(soft_min: 1500), - 3 => OpenStruct.new(soft_min: 1000), - 5 => OpenStruct.new(soft_min: 2000), - 0 => OpenStruct.new(soft_min: 2000), + 2024 => { + 1 => OpenStruct.new(soft_min: 5000), + 2 => OpenStruct.new(soft_min: 1500), + 3 => OpenStruct.new(soft_min: 1000), + 5 => OpenStruct.new(soft_min: 2000), + 0 => OpenStruct.new(soft_min: 2000), + }, + 2025 => { + 1 => OpenStruct.new(soft_min: 13_400, soft_max: 150_000), + 2 => OpenStruct.new(soft_min: 2_600, soft_max: 80_000), + 3 => OpenStruct.new(soft_min: 2_080, soft_max: 30_000), + 4 => OpenStruct.new(soft_min: 520, soft_max: 23_400), + 5 => OpenStruct.new(soft_min: 520, soft_max: 80_000), + 6 => OpenStruct.new(soft_min: 520, soft_max: 50_000), + 7 => OpenStruct.new(soft_min: 520, soft_max: 30_000), + 8 => OpenStruct.new(soft_min: 520, soft_max: 150_000), + 9 => OpenStruct.new(soft_min: 520, soft_max: 150_000), + 0 => OpenStruct.new(soft_min: 520, soft_max: 150_000), + }, }.freeze - def income1_under_soft_min? - return false unless ecstat1 && income1 && ALLOWED_INCOME_RANGES_SALES[ecstat1] + def income1_outside_soft_range_for_ecstat? + income1_under_soft_min? || income1_over_soft_max_for_ecstat? + end - income1 < ALLOWED_INCOME_RANGES_SALES[ecstat1][:soft_min] + def income1_more_or_less_text + income1_under_soft_min? ? "less" : "more" end - def income2_under_soft_min? - return false unless ecstat2 && income2 && ALLOWED_INCOME_RANGES_SALES[ecstat2] + def income2_outside_soft_range_for_ecstat? + income2_under_soft_min? || income2_over_soft_max_for_ecstat? + end - income2 < ALLOWED_INCOME_RANGES_SALES[ecstat2][:soft_min] + def income2_more_or_less_text + income2_under_soft_min? ? "less" : "more" end - def income1_over_soft_max? + def income1_over_soft_max_for_discounted_ownership? return unless income1 && la && discounted_ownership_sale? - income_over_soft_max?(income1) + income_over_discounted_sale_soft_max?(income1) end - def income2_over_soft_max? + def income2_over_soft_max_for_discounted_ownership? return unless income2 && la && discounted_ownership_sale? - income_over_soft_max?(income2) + income_over_discounted_sale_soft_max?(income2) end - def combined_income_over_soft_max? + def combined_income_over_soft_max_for_discounted_ownership? return unless income1 && income2 && la && discounted_ownership_sale? - income_over_soft_max?(income1 + income2) + income_over_discounted_sale_soft_max?(income1 + income2) end def staircase_bought_above_fifty? @@ -192,7 +210,40 @@ private ) end - def income_over_soft_max?(income) + def income1_under_soft_min? + income_under_soft_min?(income1, ecstat1) + end + + def income2_under_soft_min? + income_under_soft_min?(income2, ecstat2) + end + + def income_under_soft_min?(income, ecstat) + return unless income && ecstat + + income_ranges = form.start_year_2025_or_later? ? ALLOWED_INCOME_RANGES_SALES[2025] : ALLOWED_INCOME_RANGES_SALES[2024] + return false unless income_ranges[ecstat] + + income < income_ranges[ecstat][:soft_min] + end + + def income1_over_soft_max_for_ecstat? + income_over_soft_max?(income1, ecstat1) + end + + def income2_over_soft_max_for_ecstat? + income_over_soft_max?(income2, ecstat2) + end + + def income_over_soft_max?(income, ecstat) + return unless income && ecstat && form.start_year_2025_or_later? + + return false unless ALLOWED_INCOME_RANGES_SALES[2025][ecstat] + + income > ALLOWED_INCOME_RANGES_SALES[2025][ecstat][:soft_max] + end + + def income_over_discounted_sale_soft_max?(income) (london_property? && income > 90_000) || (property_not_in_london? && income > 80_000) end end diff --git a/config/locales/forms/2023/sales/soft_validations.en.yml b/config/locales/forms/2023/sales/soft_validations.en.yml index 20f131e90..abe77ccef 100644 --- a/config/locales/forms/2023/sales/soft_validations.en.yml +++ b/config/locales/forms/2023/sales/soft_validations.en.yml @@ -18,7 +18,7 @@ en: question_text: "Are you sure this person is retired?" title_text: "You told us this person is aged %{age} years and retired." informative_text: "The minimum expected retirement age in England is 66." - + old_persons_shared_ownership_value_check: page_header: "" check_answer_label: "Shared ownership confirmation" @@ -28,28 +28,28 @@ en: joint_purchase: "You told us the buyers are using the Older Persons Shared Ownership scheme." not_joint_purchase: "You told us the buyer is using the Older Persons Shared Ownership scheme." informative_text: "At least one buyer must be aged 65 years and over to use this scheme." - + income1_value_check: check_answer_label: "Buyer 1 income confirmation" hint_text: "" question_text: "Are you sure this is correct?" - min: + ecstat: page_header: "" title_text: "You told us income was %{income}." informative_text: "This is less than we would expect for someone in this working situation." - max: + discounted: page_header: "" title_text: "You told us the income of buyer 1 is %{income}. This seems high. Are you sure this is correct?" - + income2_value_check: check_answer_label: "Buyer 2 income confirmation" hint_text: "" question_text: "Are you sure this is correct?" - min: + ecstat: page_header: "" title_text: "You told us income was %{income}." informative_text: "This is less than we would expect for someone in this working situation." - max: + discounted: page_header: "" title_text: "You told us the income of buyer 2 is %{income}. This seems high. Are you sure this is correct?" @@ -110,7 +110,7 @@ en: hint_text: "" question_text: "Are you sure?" title_text: "You told us practical completion or handover date is more than 3 years before sale completion date." - + value_value_check: page_header: "" check_answer_label: "Purchase price confirmation" @@ -141,14 +141,14 @@ en: question_text: "Are you sure that the deposit is this much higher than the buyer's savings?" title_text: "You told us the buyer’s deposit was %{deposit} and their savings were %{savings}." informative_text: "The deposit amount is higher than we would expect for the amount of savings they have." - + wheel_value_check: page_header: "" check_answer_label: "Does anyone in the household use a wheelchair?" hint_text: "" question_text: "You told us that someone in the household uses a wheelchair." title_text: "You told us that someone in the household uses a wheelchair." - + buyer_livein_value_check: buyer1: page_header: "" @@ -164,7 +164,7 @@ en: question_text: "Are you sure this is correct?" title_text: "You told us that buyer 2 will not live in the property." informative_text: "For %{ownership_scheme} types, the buyer usually lives in the property." - + student_not_child_value_check: page_header: "" check_answer_label: "Student not a child confirmation" @@ -172,7 +172,7 @@ en: question_text: "Are you sure this person is not a child?" title_text: "You told us this person is a student aged between 16 and 19." informative_text: "Are you sure this person is not a child?" - + partner_under_16_value_check: page_header: "" check_answer_label: "Partner under 16 confirmation" @@ -180,7 +180,7 @@ en: question_text: "Are you sure this is correct?" title_text: "You told us this person is aged %{age} years and has 'Partner' relationship to buyer 1." informative_text: "Are you sure this is correct?" - + multiple_partners_value_check: page_header: "" check_answer_label: "Multiple partners confirmation" @@ -196,7 +196,7 @@ en: question_text: "Are you sure this is correct?" title_text: "You told us that the monthly charges were %{mscharge}." informative_text: "This is higher than we would expect." - + extra_borrowing_value_check: page_header: "" check_answer_label: "Extra borrowing confirmation" diff --git a/config/locales/forms/2024/sales/soft_validations.en.yml b/config/locales/forms/2024/sales/soft_validations.en.yml index b9b1ad479..fa5434311 100644 --- a/config/locales/forms/2024/sales/soft_validations.en.yml +++ b/config/locales/forms/2024/sales/soft_validations.en.yml @@ -31,11 +31,11 @@ en: check_answer_label: "Buyer 1 income confirmation" hint_text: "" question_text: "Are you sure this is correct?" - min: + ecstat: page_header: "" title_text: "You told us income was %{income}." - informative_text: "This is less than we would expect for someone in this working situation." - max: + informative_text: "This is %{more_or_less} than we would expect for someone in this working situation." + discounted: page_header: "" title_text: "You told us the income of buyer 1 is %{income}. This seems high. Are you sure this is correct?" @@ -43,11 +43,11 @@ en: check_answer_label: "Buyer 2 income confirmation" hint_text: "" question_text: "Are you sure this is correct?" - min: + ecstat: page_header: "" title_text: "You told us income was %{income}." - informative_text: "This is less than we would expect for someone in this working situation." - max: + informative_text: "This is %{more_or_less} than we would expect for someone in this working situation." + discounted: page_header: "" title_text: "You told us the income of buyer 2 is %{income}. This seems high. Are you sure this is correct?" @@ -108,7 +108,7 @@ en: hint_text: "" question_text: "Are you sure?" title_text: "You told us practical completion or handover date is more than 3 years before sale completion date." - + value_value_check: page_header: "" check_answer_label: "Purchase price confirmation" @@ -202,7 +202,7 @@ en: question_text: "Are you sure this is correct?" title_text: "You told us that the monthly charges were %{mscharge}." informative_text: "This is higher than we would expect." - + extra_borrowing_value_check: page_header: "" check_answer_label: "Extra borrowing confirmation" diff --git a/config/locales/forms/2025/sales/soft_validations.en.yml b/config/locales/forms/2025/sales/soft_validations.en.yml index c8f0990ba..11bbefd72 100644 --- a/config/locales/forms/2025/sales/soft_validations.en.yml +++ b/config/locales/forms/2025/sales/soft_validations.en.yml @@ -31,32 +31,32 @@ en: check_answer_label: "Buyer 1 income confirmation" hint_text: "" question_text: "Are you sure this is correct?" - min: + ecstat: page_header: "" - title_text: "You told us income was %{income}." - informative_text: "This is less than we would expect for someone in this working situation." - max: + title_text: "You told us the income of buyer 1 is %{income}." + informative_text: "This is %{more_or_less} than we would expect for someone in this working situation." + discounted: page_header: "" - title_text: "You told us the income of buyer 1 is %{income}. This seems high. Are you sure this is correct?" + title_text: "You told us the income of buyer 1 is %{income}. This seems high for this sale type. Are you sure this is correct?" income2_value_check: check_answer_label: "Buyer 2 income confirmation" hint_text: "" question_text: "Are you sure this is correct?" - min: + ecstat: page_header: "" - title_text: "You told us income was %{income}." - informative_text: "This is less than we would expect for someone in this working situation." - max: + title_text: "You told us the income of buyer 2 is %{income}." + informative_text: "This is %{more_or_less} than we would expect for someone in this working situation." + discounted: page_header: "" - title_text: "You told us the income of buyer 2 is %{income}. This seems high. Are you sure this is correct?" + title_text: "You told us the income of buyer 2 is %{income}. This seems high for this sale type. Are you sure this is correct?" combined_income_value_check: page_header: "" check_answer_label: "Combined income confirmation" hint_text: "" question_text: "Are you sure this is correct?" - title_text: "You told us the combined income of this household is %{combined_income}. This seems high. Are you sure this is correct?" + title_text: "You told us the combined income of this household is %{combined_income}. This seems high for this sale type. Are you sure this is correct?" mortgage_value_check: page_header: "" diff --git a/spec/models/form/sales/pages/buyer1_income_max_value_check_spec.rb b/spec/models/form/sales/pages/buyer1_income_discounted_max_value_check_spec.rb similarity index 84% rename from spec/models/form/sales/pages/buyer1_income_max_value_check_spec.rb rename to spec/models/form/sales/pages/buyer1_income_discounted_max_value_check_spec.rb index fa5cf1e7c..2282a9535 100644 --- a/spec/models/form/sales/pages/buyer1_income_max_value_check_spec.rb +++ b/spec/models/form/sales/pages/buyer1_income_discounted_max_value_check_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -RSpec.describe Form::Sales::Pages::Buyer1IncomeMaxValueCheck, type: :model do +RSpec.describe Form::Sales::Pages::Buyer1IncomeDiscountedMaxValueCheck, type: :model do subject(:page) { described_class.new(page_id, page_definition, subsection, check_answers_card_number: 1) } let(:page_id) { "prefix_buyer_1_income_max_value_check" } @@ -23,7 +23,7 @@ RSpec.describe Form::Sales::Pages::Buyer1IncomeMaxValueCheck, type: :model do it "has correct depends_on" do expect(page.depends_on).to eq([ { - "income1_over_soft_max?" => true, + "income1_over_soft_max_for_discounted_ownership?" => true, }, ]) end diff --git a/spec/models/form/sales/pages/buyer1_income_min_value_check_spec.rb b/spec/models/form/sales/pages/buyer1_income_ecstat_value_check_spec.rb similarity index 75% rename from spec/models/form/sales/pages/buyer1_income_min_value_check_spec.rb rename to spec/models/form/sales/pages/buyer1_income_ecstat_value_check_spec.rb index 79d61ce06..9d710aebb 100644 --- a/spec/models/form/sales/pages/buyer1_income_min_value_check_spec.rb +++ b/spec/models/form/sales/pages/buyer1_income_ecstat_value_check_spec.rb @@ -1,9 +1,9 @@ require "rails_helper" -RSpec.describe Form::Sales::Pages::Buyer1IncomeMinValueCheck, type: :model do +RSpec.describe Form::Sales::Pages::Buyer1IncomeEcstatValueCheck, type: :model do subject(:page) { described_class.new(page_id, page_definition, subsection) } - let(:page_id) { "prefix_buyer_1_income_min_value_check" } + let(:page_id) { "prefix_buyer_1_income_ecstat_value_check" } let(:page_definition) { nil } let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) } let(:subsection) { instance_double(Form::Subsection, form:) } @@ -17,13 +17,13 @@ RSpec.describe Form::Sales::Pages::Buyer1IncomeMinValueCheck, type: :model do end it "has the correct id" do - expect(page.id).to eq("prefix_buyer_1_income_min_value_check") + expect(page.id).to eq("prefix_buyer_1_income_ecstat_value_check") end it "has correct depends_on" do expect(page.depends_on).to eq([ { - "income1_under_soft_min?" => true, + "income1_outside_soft_range_for_ecstat?" => true, }, ]) end diff --git a/spec/models/form/sales/pages/buyer2_income_max_value_check_spec.rb b/spec/models/form/sales/pages/buyer2_income_discounted_max_value_check_spec.rb similarity index 84% rename from spec/models/form/sales/pages/buyer2_income_max_value_check_spec.rb rename to spec/models/form/sales/pages/buyer2_income_discounted_max_value_check_spec.rb index f467db18d..36a04cedb 100644 --- a/spec/models/form/sales/pages/buyer2_income_max_value_check_spec.rb +++ b/spec/models/form/sales/pages/buyer2_income_discounted_max_value_check_spec.rb @@ -1,6 +1,6 @@ require "rails_helper" -RSpec.describe Form::Sales::Pages::Buyer2IncomeMaxValueCheck, type: :model do +RSpec.describe Form::Sales::Pages::Buyer2IncomeDiscountedMaxValueCheck, type: :model do subject(:page) { described_class.new(page_id, page_definition, subsection, check_answers_card_number: 2) } let(:page_id) { "prefix_buyer_2_income_max_value_check" } @@ -23,7 +23,7 @@ RSpec.describe Form::Sales::Pages::Buyer2IncomeMaxValueCheck, type: :model do it "has correct depends_on" do expect(page.depends_on).to eq([ { - "income2_over_soft_max?" => true, + "income2_over_soft_max_for_discounted_ownership?" => true, }, ]) end diff --git a/spec/models/form/sales/pages/buyer2_income_min_value_check_spec.rb b/spec/models/form/sales/pages/buyer2_income_ecstat_value_check_spec.rb similarity index 72% rename from spec/models/form/sales/pages/buyer2_income_min_value_check_spec.rb rename to spec/models/form/sales/pages/buyer2_income_ecstat_value_check_spec.rb index 44b85ef9e..2ced58f17 100644 --- a/spec/models/form/sales/pages/buyer2_income_min_value_check_spec.rb +++ b/spec/models/form/sales/pages/buyer2_income_ecstat_value_check_spec.rb @@ -1,9 +1,9 @@ require "rails_helper" -RSpec.describe Form::Sales::Pages::Buyer2IncomeMinValueCheck, type: :model do +RSpec.describe Form::Sales::Pages::Buyer2IncomeEcstatValueCheck, type: :model do subject(:page) { described_class.new(page_id, page_definition, subsection) } - let(:page_id) { "prefix_buyer_2_income_min_value_check" } + let(:page_id) { "prefix_buyer_2_income_ecstat_value_check" } let(:page_definition) { nil } let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) } let(:subsection) { instance_double(Form::Subsection, form:) } @@ -17,13 +17,13 @@ RSpec.describe Form::Sales::Pages::Buyer2IncomeMinValueCheck, type: :model do end it "has the correct id" do - expect(page.id).to eq("prefix_buyer_2_income_min_value_check") + expect(page.id).to eq("prefix_buyer_2_income_ecstat_value_check") end it "has correct depends_on" do expect(page.depends_on).to eq([ { - "income2_under_soft_min?" => true, + "income2_outside_soft_range_for_ecstat?" => true, }, ]) end diff --git a/spec/models/form/sales/pages/combined_income_max_value_check_spec.rb b/spec/models/form/sales/pages/combined_income_max_value_check_spec.rb index f9b9954d9..85dd29c5c 100644 --- a/spec/models/form/sales/pages/combined_income_max_value_check_spec.rb +++ b/spec/models/form/sales/pages/combined_income_max_value_check_spec.rb @@ -23,7 +23,7 @@ RSpec.describe Form::Sales::Pages::CombinedIncomeMaxValueCheck, type: :model do it "has correct depends_on" do expect(page.depends_on).to eq([ { - "combined_income_over_soft_max?" => true, + "combined_income_over_soft_max_for_discounted_ownership?" => true, }, ]) end diff --git a/spec/models/form/sales/subsections/household_characteristics_spec.rb b/spec/models/form/sales/subsections/household_characteristics_spec.rb index 3cd856eb3..d892febc4 100644 --- a/spec/models/form/sales/subsections/household_characteristics_spec.rb +++ b/spec/models/form/sales/subsections/household_characteristics_spec.rb @@ -16,116 +16,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model expect(household_characteristics.section).to eq(section) end - it "has the correct id" do - expect(household_characteristics.id).to eq("household_characteristics") - end - - it "has the correct label" do - expect(household_characteristics.label).to eq("Household characteristics") - end - - context "with 2022/23 form" do - before do - allow(form).to receive(:start_date).and_return(Time.zone.local(2022, 4, 1)) - allow(form).to receive(:start_year_2024_or_later?).and_return(false) - allow(form).to receive(:start_year_2025_or_later?).and_return(false) - end - - it "has correct pages" do - expect(household_characteristics.pages.map(&:id)).to eq( - %w[ - buyer_interview_joint_purchase - buyer_interview - privacy_notice_joint_purchase - privacy_notice - buyer_1_age - age_1_retirement_value_check - age_1_old_persons_shared_ownership_joint_purchase_value_check - age_1_old_persons_shared_ownership_value_check - buyer_1_gender_identity - buyer_1_ethnic_group - buyer_1_ethnic_background_black - buyer_1_ethnic_background_asian - buyer_1_ethnic_background_arab - buyer_1_ethnic_background_mixed - buyer_1_ethnic_background_white - buyer_1_nationality - buyer_1_working_situation - working_situation_1_retirement_value_check - working_situation_buyer_1_income_min_value_check - buyer_1_live_in_property - buyer_1_live_in_property_value_check - buyer_2_relationship_to_buyer_1 - buyer_2_relationship_student_not_child_value_check - buyer_2_age - age_2_old_persons_shared_ownership_joint_purchase_value_check - age_2_old_persons_shared_ownership_value_check - age_2_buyer_retirement_value_check - buyer_2_age_student_not_child_value_check - buyer_2_gender_identity - buyer_2_working_situation - working_situation_2_retirement_value_check_joint_purchase - working_situation_buyer_2_income_min_value_check - buyer_2_working_situation_student_not_child_value_check - buyer_2_live_in_property - buyer_2_live_in_property_value_check - number_of_others_in_property - number_of_others_in_property_joint_purchase - person_2_known - person_2_relationship_to_buyer_1 - relationship_2_student_not_child_value_check - person_2_age - age_2_retirement_value_check - age_2_student_not_child_value_check - person_2_gender_identity - person_2_working_situation - working_situation_2_retirement_value_check - working_situation_2_student_not_child_value_check - person_3_known - person_3_relationship_to_buyer_1 - relationship_3_student_not_child_value_check - person_3_age - age_3_retirement_value_check - age_3_student_not_child_value_check - person_3_gender_identity - person_3_working_situation - working_situation_3_retirement_value_check - working_situation_3_student_not_child_value_check - person_4_known - person_4_relationship_to_buyer_1 - relationship_4_student_not_child_value_check - person_4_age - age_4_retirement_value_check - age_4_student_not_child_value_check - person_4_gender_identity - person_4_working_situation - working_situation_4_retirement_value_check - working_situation_4_student_not_child_value_check - person_5_known - person_5_relationship_to_buyer_1 - relationship_5_student_not_child_value_check - person_5_age - age_5_retirement_value_check - age_5_student_not_child_value_check - person_5_gender_identity - person_5_working_situation - working_situation_5_retirement_value_check - working_situation_5_student_not_child_value_check - person_6_known - person_6_relationship_to_buyer_1 - relationship_6_student_not_child_value_check - person_6_age - age_6_retirement_value_check - age_6_student_not_child_value_check - person_6_gender_identity - person_6_working_situation - working_situation_6_retirement_value_check - working_situation_6_student_not_child_value_check - ], - ) - end - end - context "with 2023/24 form" do before do allow(form).to receive(:start_date).and_return(Time.zone.local(2023, 4, 1)) @@ -154,7 +44,7 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model buyer_1_nationality buyer_1_working_situation working_situation_1_retirement_value_check - working_situation_buyer_1_income_min_value_check + working_situation_buyer_1_income_value_check buyer_1_live_in_property buyer_1_live_in_property_value_check buyer_2_relationship_to_buyer_1 @@ -174,7 +64,7 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model buyer_2_nationality buyer_2_working_situation working_situation_2_retirement_value_check_joint_purchase - working_situation_buyer_2_income_min_value_check + working_situation_buyer_2_income_value_check buyer_2_working_situation_student_not_child_value_check buyer_2_live_in_property buyer_2_live_in_property_value_check @@ -281,7 +171,7 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model buyer_1_working_situation working_situation_1_retirement_value_check working_situation_1_not_retired_value_check - working_situation_buyer_1_income_min_value_check + working_situation_buyer_1_income_value_check buyer_1_live_in_property buyer_1_live_in_property_value_check buyer_2_relationship_to_buyer_1 @@ -303,7 +193,7 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model buyer_2_working_situation working_situation_2_retirement_value_check_joint_purchase working_situation_2_not_retired_value_check_joint_purchase - working_situation_buyer_2_income_min_value_check + working_situation_buyer_2_income_value_check buyer_2_working_situation_student_not_child_value_check buyer_2_live_in_property buyer_2_live_in_property_value_check @@ -396,10 +286,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model allow(form).to receive(:start_year_2025_or_later?).and_return(true) end - it "has correct depends on" do - expect(household_characteristics.depends_on).to eq([{ "setup_completed?" => true }]) - end - it "has correct pages" do expect(household_characteristics.pages.map(&:id)).to eq( %w[ @@ -419,7 +305,7 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model buyer_1_working_situation working_situation_1_retirement_value_check working_situation_1_not_retired_value_check - working_situation_buyer_1_income_min_value_check + working_situation_buyer_1_income_value_check buyer_1_live_in_property buyer_1_live_in_property_value_check buyer_2_relationship_to_buyer_1 @@ -441,7 +327,7 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model buyer_2_working_situation working_situation_2_retirement_value_check_joint_purchase working_situation_2_not_retired_value_check_joint_purchase - working_situation_buyer_2_income_min_value_check + working_situation_buyer_2_income_value_check buyer_2_working_situation_student_not_child_value_check buyer_2_live_in_property buyer_2_live_in_property_value_check @@ -525,5 +411,9 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model ], ) end + + it "has correct depends on" do + expect(household_characteristics.depends_on).to eq([{ "setup_completed?" => true }]) + end end end diff --git a/spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb b/spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb index 557155758..e6c46a119 100644 --- a/spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb +++ b/spec/models/form/sales/subsections/income_benefits_and_savings_spec.rb @@ -5,7 +5,8 @@ RSpec.describe Form::Sales::Subsections::IncomeBenefitsAndSavings, type: :model let(:subsection_id) { nil } let(:subsection_definition) { nil } - let(:section) { instance_double(Form::Sales::Sections::Household) } + let(:section) { instance_double(Form::Sales::Sections::Household, form:) } + let(:form) { instance_double(Form, start_date: Time.utc(2024, 4, 1), start_year_2025_or_later?: false) } it "has correct section" do expect(subsection.section).to eq(section) @@ -19,88 +20,80 @@ RSpec.describe Form::Sales::Subsections::IncomeBenefitsAndSavings, type: :model expect(subsection.label).to eq("Income, benefits and savings") end - describe "pages" do - let(:section) { instance_double(Form::Sales::Sections::Household, form: instance_double(Form, start_date:, start_year_2025_or_later?: false)) } + context "when before 2025" do + let(:form) { instance_double(Form, start_date: Time.utc(2024, 4, 1), start_year_2025_or_later?: false) } - context "when 2022" do - let(:start_date) { Time.utc(2022, 2, 8) } - - it "has correct pages" do - expect(subsection.pages.compact.map(&:id)).to eq( - %w[ - buyer_1_income - buyer_1_income_min_value_check - buyer_1_income_max_value_check - buyer_1_combined_income_max_value_check - buyer_1_income_mortgage_value_check - buyer_1_mortgage - buyer_1_mortgage_value_check - buyer_2_income - buyer_2_income_mortgage_value_check - buyer_2_income_min_value_check - buyer_2_income_max_value_check - buyer_2_combined_income_max_value_check - buyer_2_mortgage - buyer_2_mortgage_value_check - housing_benefits_joint_purchase - housing_benefits_not_joint_purchase - savings_joint_purchase - savings - savings_joint_purchase_value_check - savings_value_check - savings_deposit_joint_purchase_value_check - savings_deposit_value_check - previous_ownership_joint_purchase - previous_ownership_not_joint_purchase - ], - ) - end + it "has correct pages" do + expect(subsection.pages.map(&:id)).to eq( + %w[ + buyer_1_income + buyer_1_income_ecstat_value_check + buyer_1_income_discounted_max_value_check + buyer_1_combined_income_max_value_check + buyer_1_income_mortgage_value_check + buyer_1_mortgage + buyer_1_mortgage_value_check + buyer_2_income + buyer_2_income_mortgage_value_check + buyer_2_income_ecstat_value_check + buyer_2_income_discounted_max_value_check + buyer_2_combined_income_max_value_check + buyer_2_mortgage + buyer_2_mortgage_value_check + housing_benefits_joint_purchase + housing_benefits_not_joint_purchase + savings_joint_purchase + savings + savings_joint_purchase_value_check + savings_value_check + savings_deposit_joint_purchase_value_check + savings_deposit_value_check + previous_ownership_joint_purchase + previous_ownership_not_joint_purchase + previous_shared + ], + ) end - context "when 2023" do - let(:start_date) { Time.utc(2023, 2, 8) } - - it "has correct pages" do - expect(subsection.pages.map(&:id)).to eq( - %w[ - buyer_1_income - buyer_1_income_min_value_check - buyer_1_income_max_value_check - buyer_1_combined_income_max_value_check - buyer_1_income_mortgage_value_check - buyer_1_mortgage - buyer_1_mortgage_value_check - buyer_2_income - buyer_2_income_mortgage_value_check - buyer_2_income_min_value_check - buyer_2_income_max_value_check - buyer_2_combined_income_max_value_check - buyer_2_mortgage - buyer_2_mortgage_value_check - housing_benefits_joint_purchase - housing_benefits_not_joint_purchase - savings_joint_purchase - savings - savings_joint_purchase_value_check - savings_value_check - savings_deposit_joint_purchase_value_check - savings_deposit_value_check - previous_ownership_joint_purchase - previous_ownership_not_joint_purchase - previous_shared - ], - ) - end - - it "has correct depends on" do - expect(subsection.depends_on).to eq([{ "setup_completed?" => true }]) - end + it "has correct depends on" do + expect(subsection.depends_on).to eq([{ "setup_completed?" => true }]) end end context "when 2025" do - let(:start_date) { Time.utc(2025, 2, 8) } - let(:section) { instance_double(Form::Sales::Sections::Household, form: instance_double(Form, start_date:, start_year_2025_or_later?: true)) } + let(:form) { instance_double(Form, start_date: Time.utc(2025, 4, 1), start_year_2025_or_later?: true) } + + it "has correct pages" do + expect(subsection.pages.map(&:id)).to eq( + %w[ + buyer_1_income + buyer_1_income_ecstat_value_check + buyer_1_income_discounted_max_value_check + buyer_1_combined_income_max_value_check + buyer_1_income_mortgage_value_check + buyer_1_mortgage + buyer_1_mortgage_value_check + buyer_2_income + buyer_2_income_mortgage_value_check + buyer_2_income_ecstat_value_check + buyer_2_income_discounted_max_value_check + buyer_2_combined_income_max_value_check + buyer_2_mortgage + buyer_2_mortgage_value_check + housing_benefits_joint_purchase + housing_benefits_not_joint_purchase + savings_joint_purchase + savings + savings_joint_purchase_value_check + savings_value_check + savings_deposit_joint_purchase_value_check + savings_deposit_value_check + previous_ownership_joint_purchase + previous_ownership_not_joint_purchase + previous_shared + ], + ) + end it "has correct depends on" do expect(subsection.depends_on).to eq([{ "setup_completed?" => true, "is_staircase?" => false }]) diff --git a/spec/models/validations/sales/soft_validations_spec.rb b/spec/models/validations/sales/soft_validations_spec.rb index 99c615250..51f0d695a 100644 --- a/spec/models/validations/sales/soft_validations_spec.rb +++ b/spec/models/validations/sales/soft_validations_spec.rb @@ -3,85 +3,222 @@ require "rails_helper" RSpec.describe Validations::Sales::SoftValidations do let(:record) { build(:sales_log) } - describe "income1 min validations" do - context "when validating soft min" do - it "returns false if no income1 is given" do + describe "income validations" do + context "when validating soft range based on ecstat" do + it "does not trigger for income1 if no income1 is given" do record.income1 = nil - - expect(record).not_to be_income1_under_soft_min + expect(record).not_to be_income1_outside_soft_range_for_ecstat end - it "returns false if no ecstat1 is given" do + it "does not trigger for low income1 if no ecstat1 is given" do + record.income1 = 50 record.ecstat1 = nil + expect(record).not_to be_income1_outside_soft_range_for_ecstat + end - expect(record).not_to be_income1_under_soft_min + it "does not trigger for income2 if no income2 is given" do + record.income2 = nil + expect(record).not_to be_income2_outside_soft_range_for_ecstat end - [ - { - income1: 4500, - ecstat1: 1, - }, - { - income1: 1400, - ecstat1: 2, - }, - { - income1: 999, - ecstat1: 3, - }, - { - income1: 1899, - ecstat1: 5, - }, - { - income1: 1888, - ecstat1: 0, - }, - ].each do |test_case| - it "returns true if income1 is below soft min for ecstat1 #{test_case[:ecstat1]}" do - record.income1 = test_case[:income1] - record.ecstat1 = test_case[:ecstat1] - expect(record) - .to be_income1_under_soft_min + it "does not trigger for low income2 if no ecstat2 is given" do + record.income2 = 50 + record.ecstat2 = nil + expect(record).not_to be_income2_outside_soft_range_for_ecstat + end + + context "when log year is before 2025" do + let(:record) { build(:sales_log, saledate: Time.zone.local(2024, 12, 25)) } + + it "does not trigger for low income1 if ecstat1 has no soft min" do + record.income1 = 50 + record.ecstat1 = 4 + expect(record).not_to be_income1_outside_soft_range_for_ecstat + end + + it "returns true if income1 is below soft min for ecstat1" do + record.income1 = 4500 + record.ecstat1 = 1 + expect(record).to be_income1_outside_soft_range_for_ecstat + end + + it "returns false if income1 is >= soft min for ecstat1" do + record.income1 = 1500 + record.ecstat1 = 2 + expect(record).not_to be_income1_outside_soft_range_for_ecstat + end + + it "does not trigger for income2 if ecstat2 has no soft min" do + record.income2 = 50 + record.ecstat2 = 8 + expect(record).not_to be_income2_outside_soft_range_for_ecstat + end + + it "returns true if income2 is below soft min for ecstat2" do + record.income2 = 999 + record.ecstat2 = 3 + expect(record).to be_income2_outside_soft_range_for_ecstat + end + + it "returns false if income2 is >= soft min for ecstat2" do + record.income2 = 2500 + record.ecstat2 = 5 + expect(record).not_to be_income2_outside_soft_range_for_ecstat + end + + it "does not trigger for being over maxima" do + record.ecstat1 = 1 + record.income1 = 200_000 + record.ecstat2 = 2 + record.income2 = 100_000 + expect(record).not_to be_income1_outside_soft_range_for_ecstat + expect(record).not_to be_income2_outside_soft_range_for_ecstat end end - [ - { - income1: 5001, - ecstat1: 1, - }, - { - income1: 1600, - ecstat1: 2, - }, - { - income1: 1004, - ecstat1: 3, - }, - { - income1: 2899, - ecstat1: 4, - }, - { - income1: 2899, - ecstat1: 5, - }, - { - income1: 5, - ecstat1: 6, - }, - { - income1: 10_888, - ecstat1: 0, - }, - ].each do |test_case| - it "returns false if income1 is over soft min for ecstat1 #{test_case[:ecstat1]}" do - record.income1 = test_case[:income1] - record.ecstat1 = test_case[:ecstat1] - expect(record) - .not_to be_income1_under_soft_min + context "when log year is 2025" do + let(:record) { build(:sales_log, saledate: Time.zone.local(2025, 12, 25)) } + + it "returns true if income1 is below soft min for ecstat1" do + record.income1 = 13_399 + record.ecstat1 = 1 + expect(record).to be_income1_outside_soft_range_for_ecstat + end + + it "returns false if income1 is >= soft min for ecstat1" do + record.income1 = 2600 + record.ecstat1 = 2 + expect(record).not_to be_income1_outside_soft_range_for_ecstat + end + + it "returns true if income2 is below soft min for ecstat2" do + record.income2 = 2079 + record.ecstat2 = 3 + expect(record).to be_income2_outside_soft_range_for_ecstat + end + + it "returns false if income2 is >= soft min for ecstat2" do + record.income2 = 520 + record.ecstat2 = 5 + expect(record).not_to be_income2_outside_soft_range_for_ecstat + end + + it "returns true if income1 is above soft max for ecstat1" do + record.income1 = 80_001 + record.ecstat1 = 2 + expect(record).to be_income1_outside_soft_range_for_ecstat + end + + it "returns false if income1 is <= soft max for ecstat1" do + record.income1 = 80_000 + record.ecstat1 = 2 + expect(record).not_to be_income1_outside_soft_range_for_ecstat + end + + it "returns true if income2 is above soft max for ecstat2" do + record.income2 = 50_001 + record.ecstat2 = 6 + expect(record).to be_income2_outside_soft_range_for_ecstat + end + + it "returns false if income2 is <= soft max for ecstat2" do + record.income2 = 50_000 + record.ecstat2 = 6 + expect(record).not_to be_income2_outside_soft_range_for_ecstat + end + end + end + + context "when validating soft max for discounted ownership" do + it "does not trigger if la is not set" do + record.la = nil + record.income1 = 100_000 + record.income2 = 95_000 + record.ownershipsch = 2 + expect(record).not_to be_income1_over_soft_max_for_discounted_ownership + expect(record).not_to be_income2_over_soft_max_for_discounted_ownership + expect(record).not_to be_combined_income_over_soft_max_for_discounted_ownership + end + + it "does not trigger if sale is not discounted ownership" do + record.la = "E09000001" + record.income1 = 100_000 + record.income2 = 95_000 + record.ownershipsch = 1 + expect(record).not_to be_income1_over_soft_max_for_discounted_ownership + expect(record).not_to be_income2_over_soft_max_for_discounted_ownership + expect(record).not_to be_combined_income_over_soft_max_for_discounted_ownership + end + + context "when property is in London for a discounted ownership sale" do + let(:record) { build(:sales_log, ownershipsch: 2, la: "E09000001") } + + it "returns true for income1 if income1 > London threshold" do + record.income1 = 90_001 + expect(record).to be_income1_over_soft_max_for_discounted_ownership + end + + it "returns false for income1 if income1 <= London threshold" do + record.income1 = 90_000 + expect(record).not_to be_income1_over_soft_max_for_discounted_ownership + end + + it "returns true for income2 if income2 > London threshold" do + record.income2 = 100_000 + expect(record).to be_income2_over_soft_max_for_discounted_ownership + end + + it "returns false for income2 if income2 <= London threshold" do + record.income2 = 30_000 + expect(record).not_to be_income2_over_soft_max_for_discounted_ownership + end + + it "returns true for combined income if > London threshold" do + record.income1 = 61_000 + record.income2 = 30_000 + expect(record).to be_combined_income_over_soft_max_for_discounted_ownership + end + + it "returns false for combined income if <= London threshold" do + record.income1 = 40_000 + record.income2 = 20_000 + expect(record).not_to be_combined_income_over_soft_max_for_discounted_ownership + end + end + + context "when property is not in London for a discounted ownership sale" do + let(:record) { build(:sales_log, ownershipsch: 2, la: "E08000001") } + + it "returns true for income1 if income1 > non-London threshold" do + record.income1 = 80_001 + expect(record).to be_income1_over_soft_max_for_discounted_ownership + end + + it "returns false for income1 if income1 <= non-London threshold" do + record.income1 = 80_000 + expect(record).not_to be_income1_over_soft_max_for_discounted_ownership + end + + it "returns true for income2 if income2 > non-London threshold" do + record.income2 = 85_000 + expect(record).to be_income2_over_soft_max_for_discounted_ownership + end + + it "returns false for income2 if income2 <= non-London threshold" do + record.income2 = 30_000 + expect(record).not_to be_income2_over_soft_max_for_discounted_ownership + end + + it "returns true for combined income if > non-London threshold" do + record.income1 = 61_000 + record.income2 = 20_000 + expect(record).to be_combined_income_over_soft_max_for_discounted_ownership + end + + it "returns false for combined income if <= non-London threshold" do + record.income1 = 40_000 + record.income2 = 20_000 + expect(record).not_to be_combined_income_over_soft_max_for_discounted_ownership end end end diff --git a/spec/services/documentation_generator_spec.rb b/spec/services/documentation_generator_spec.rb index 6f4714d01..d214aaa9e 100644 --- a/spec/services/documentation_generator_spec.rb +++ b/spec/services/documentation_generator_spec.rb @@ -127,11 +127,11 @@ describe DocumentationGenerator do context "when the service is run for sales" do let(:log_type) { "sales" } - let(:all_validation_methods) { ["income2_under_soft_min?"] } + let(:all_validation_methods) { ["income2_outside_soft_range_for_ecstat?"] } it "creates new validation documentation records" do expect { described_class.new.describe_soft_validations(client, all_validation_methods, all_helper_methods, log_type) }.to change(LogValidation, :count) - expect(LogValidation.where(validation_name: "income2_under_soft_min?").count).to be_positive + expect(LogValidation.where(validation_name: "income2_outside_soft_range_for_ecstat?").count).to be_positive any_validation = LogValidation.first expect(any_validation.description).to eq("Validates the format.") expect(any_validation.field).not_to be_empty From e48e9926f1f838b986dce4e4e244a7782ac7db31 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Thu, 28 Nov 2024 09:58:59 +0000 Subject: [PATCH 03/36] CLDC-3726 Display Helpdesk ticket question conditionally (#2808) * Add has_helpdesk_ticket question * lint * typo --- app/controllers/merge_requests_controller.rb | 10 +++ app/helpers/merge_requests_helper.rb | 12 ++- .../merge_requests/helpdesk_ticket.html.erb | 21 ++++- config/locales/en.yml | 4 + .../20241122154743_add_has_helpdest_ticket.rb | 5 ++ db/schema.rb | 3 +- spec/factories/merge_request.rb | 1 + .../merge_requests_controller_spec.rb | 78 ++++++++++++++++++- 8 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 db/migrate/20241122154743_add_has_helpdest_ticket.rb diff --git a/app/controllers/merge_requests_controller.rb b/app/controllers/merge_requests_controller.rb index a6e2c08e5..e38d1bdf0 100644 --- a/app/controllers/merge_requests_controller.rb +++ b/app/controllers/merge_requests_controller.rb @@ -116,6 +116,7 @@ private def merge_request_params merge_params = params.fetch(:merge_request, {}).permit( :requesting_organisation_id, + :has_helpdesk_ticket, :helpdesk_ticket, :status, :absorbing_organisation_id, @@ -124,6 +125,7 @@ private ) merge_params[:requesting_organisation_id] = current_user.organisation.id + merge_params[:helpdesk_ticket] = nil if merge_params[:has_helpdesk_ticket] == "false" merge_params end @@ -151,6 +153,14 @@ private if merge_request_params[:existing_absorbing_organisation].nil? @merge_request.errors.add(:existing_absorbing_organisation, :blank) end + when "helpdesk_ticket" + @merge_request.has_helpdesk_ticket = merge_request_params[:has_helpdesk_ticket] + @merge_request.helpdesk_ticket = merge_request_params[:helpdesk_ticket] + if merge_request_params[:has_helpdesk_ticket].blank? + @merge_request.errors.add(:has_helpdesk_ticket, :blank) + elsif merge_request_params[:has_helpdesk_ticket] == "true" && merge_request_params[:helpdesk_ticket].blank? + @merge_request.errors.add(:helpdesk_ticket, :blank) + end end end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 28c693935..a8ed72120 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -9,7 +9,7 @@ module MergeRequestsHelper def request_details(merge_request) [ { label: "Requester", value: display_value_or_placeholder(merge_request.requester&.name) }, - { label: "Helpdesk ticket", value: merge_request.helpdesk_ticket.present? ? link_to("#{merge_request.helpdesk_ticket} (opens in a new tab)", "https://mhclgdigital.atlassian.net/browse/#{merge_request.helpdesk_ticket}", target: "_blank", rel: "noopener noreferrer") : display_value_or_placeholder(nil), action: merge_request_action(merge_request, "helpdesk_ticket") }, + { label: "Helpdesk ticket", value: helpdesk_ticket_value(merge_request), action: merge_request_action(merge_request, "helpdesk_ticket") }, { label: "Status", value: status_tag(merge_request.status) }, ] end @@ -280,4 +280,14 @@ module MergeRequestsHelper def begin_merge_disabled?(merge_request) merge_request.status != "ready_to_merge" || merge_request.merge_date.future? end + + def helpdesk_ticket_value(merge_request) + if merge_request.helpdesk_ticket.present? + link_to("#{merge_request.helpdesk_ticket} (opens in a new tab)", "https://mhclgdigital.atlassian.net/browse/#{merge_request.helpdesk_ticket}", target: "_blank", rel: "noopener noreferrer") + elsif merge_request.has_helpdesk_ticket == false + "Not reported by a helpdesk ticket" + else + display_value_or_placeholder(nil) + end + end end diff --git a/app/views/merge_requests/helpdesk_ticket.html.erb b/app/views/merge_requests/helpdesk_ticket.html.erb index 4ebd11395..9ebed7a90 100644 --- a/app/views/merge_requests/helpdesk_ticket.html.erb +++ b/app/views/merge_requests/helpdesk_ticket.html.erb @@ -7,14 +7,27 @@ <%= form_with model: @merge_request, url: submit_merge_request_url(request.query_parameters["referrer"]), method: :patch do |f| %> <%= f.govuk_error_summary %> -

Which helpdesk ticket reported this merge?

-

If this merge was reported via a helpdesk ticket, provide the ticket number.
The ticket will be linked to the merge request for reference.

-
- <%= f.govuk_text_field :helpdesk_ticket, caption: { text: "Ticket number", class: "govuk-label govuk-label--s" }, label: { text: "For example, MSD-12345", class: "app-!-colour-muted" } %> + <%= f.govuk_radio_buttons_fieldset :has_helpdesk_ticket, + legend: { text: "Was this merge reported by a helpdesk ticket?", size: "l" } do %> + + <%= f.govuk_radio_button "has_helpdesk_ticket", + true, + label: { text: "Yes" }, + **basic_conditional_html_attributes({ "helpdesk_ticket" => [true] }, "merge_request") do %> + <%= f.govuk_text_field :helpdesk_ticket, + caption: { text: "Ticket number", class: "govuk-label govuk-label--s" }, + label: { text: "For example, MSD-12345", class: "app-!-colour-muted" } %> + <% end %> + + <%= f.govuk_radio_button "has_helpdesk_ticket", + false, + label: { text: "No" } %> + <% end %> + <%= f.hidden_field :page, value: "helpdesk_ticket" %>
<%= f.govuk_submit submit_merge_request_button_text(request.query_parameters["referrer"]) %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 698618717..80711129e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -188,6 +188,10 @@ en: blank: "You must answer absorbing organisation already active?" merging_organisation_id: part_of_another_merge: "Another merge request records %{organisation} as merging into %{absorbing_organisation} on %{merge_date}. Select another organisation or remove this organisation from the other merge request." + has_helpdesk_ticket: + blank: "You must answer was this merge reported by a helpdesk ticket?" + helpdesk_ticket: + blank: "You must answer the ticket number" notification: attributes: title: diff --git a/db/migrate/20241122154743_add_has_helpdest_ticket.rb b/db/migrate/20241122154743_add_has_helpdest_ticket.rb new file mode 100644 index 000000000..103611ad8 --- /dev/null +++ b/db/migrate/20241122154743_add_has_helpdest_ticket.rb @@ -0,0 +1,5 @@ +class AddHasHelpdestTicket < ActiveRecord::Migration[7.0] + def change + add_column :merge_requests, :has_helpdesk_ticket, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index 9aa744dc2..c53872020 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_11_18_104046) do +ActiveRecord::Schema[7.0].define(version: 2024_11_22_154743) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -478,6 +478,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_11_18_104046) do t.boolean "request_merged" t.boolean "processing" t.boolean "existing_absorbing_organisation" + t.boolean "has_helpdesk_ticket" end create_table "notifications", force: :cascade do |t| diff --git a/spec/factories/merge_request.rb b/spec/factories/merge_request.rb index 19020fce1..4b33e4002 100644 --- a/spec/factories/merge_request.rb +++ b/spec/factories/merge_request.rb @@ -2,6 +2,7 @@ FactoryBot.define do factory :merge_request do status { "incomplete" } merge_date { nil } + has_helpdesk_ticket { true } helpdesk_ticket { "MSD-99999" } association :requesting_organisation, factory: :organisation end diff --git a/spec/requests/merge_requests_controller_spec.rb b/spec/requests/merge_requests_controller_spec.rb index 2e2488b93..074d2186c 100644 --- a/spec/requests/merge_requests_controller_spec.rb +++ b/spec/requests/merge_requests_controller_spec.rb @@ -260,7 +260,7 @@ RSpec.describe MergeRequestsController, type: :request do end it "shows the correct content" do - expect(page).to have_content("Which helpdesk ticket reported this merge?") + expect(page).to have_content("Was this merge reported by a helpdesk ticket?") expect(page).to have_link("Back", href: existing_absorbing_organisation_merge_request_path(merge_request)) expect(page).to have_button("Save and continue") end @@ -476,6 +476,82 @@ RSpec.describe MergeRequestsController, type: :request do end end end + + describe "from helpdesk_ticket page" do + context "when not answering the question" do + let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation, absorbing_organisation: other_organisation) } + let(:params) do + { merge_request: { page: "helpdesk_ticket" } } + end + let(:request) do + patch "/merge-request/#{merge_request.id}", headers:, params: + end + + it "renders the error" do + request + + expect(response).to have_http_status(:unprocessable_entity) + expect(page).to have_content("You must answer was this merge reported by a helpdesk ticket?") + end + + it "does not update the request" do + expect { request }.not_to(change { merge_request.reload.attributes }) + end + end + + context "when has_helpdesk_ticket is true but no ticket is given" do + let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation, absorbing_organisation: other_organisation) } + let(:params) do + { merge_request: { page: "helpdesk_ticket", has_helpdesk_ticket: true } } + end + let(:request) do + patch "/merge-request/#{merge_request.id}", headers:, params: + end + + it "renders the error" do + request + + expect(response).to have_http_status(:unprocessable_entity) + expect(page).to have_content("You must answer the ticket number") + end + + it "does not update the request" do + expect { request }.not_to(change { merge_request.reload.attributes }) + end + end + + context "when has_helpdesk_ticket is false" do + let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation, absorbing_organisation: other_organisation, helpdesk_ticket: "123") } + let(:params) do + { merge_request: { page: "helpdesk_ticket", has_helpdesk_ticket: false } } + end + let(:request) do + patch "/merge-request/#{merge_request.id}", headers:, params: + end + + it "updates has_helpdesk_ticket and clears helpdesk_ticket" do + request + expect(merge_request.reload.has_helpdesk_ticket).to eq(false) + expect(merge_request.helpdesk_ticket).to eq(nil) + end + end + + context "when has_helpdesk_ticket is true and ticket is given" do + let(:merge_request) { MergeRequest.create!(requesting_organisation: organisation, absorbing_organisation: other_organisation) } + let(:params) do + { merge_request: { page: "helpdesk_ticket", has_helpdesk_ticket: true, helpdesk_ticket: "321" } } + end + let(:request) do + patch "/merge-request/#{merge_request.id}", headers:, params: + end + + it "updates has_helpdesk_ticket and clears helpdesk_ticket" do + request + expect(merge_request.reload.has_helpdesk_ticket).to eq(true) + expect(merge_request.helpdesk_ticket).to eq("321") + end + end + end end describe "#merge_start_confirmation" do From 56f70f9cd6aa5808a16ae1715b70435191fd627a Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Thu, 28 Nov 2024 10:32:48 +0000 Subject: [PATCH 04/36] Count active scheme and location duplicates (#2828) --- app/models/location.rb | 23 +++++ app/models/scheme.rb | 16 ++++ lib/tasks/count_duplicates.rake | 62 +++++++++++++ spec/factories/scheme.rb | 5 ++ spec/lib/tasks/count_duplicates_spec.rb | 110 ++++++++++++++++++++++++ 5 files changed, 216 insertions(+) diff --git a/app/models/location.rb b/app/models/location.rb index 12c6f2fad..58afc76e8 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -144,6 +144,29 @@ class Location < ApplicationRecord scope.pluck("ARRAY_AGG(id)") } + scope :duplicate_active_sets, lambda { + scope = active + .group(*DUPLICATE_LOCATION_ATTRIBUTES) + .where.not(scheme_id: nil) + .where.not(postcode: nil) + .where.not(mobility_type: nil) + .having( + "COUNT(*) > 1", + ) + scope.pluck("ARRAY_AGG(id)") + } + + scope :duplicate_active_sets_within_given_schemes, lambda { + scope = active + .group(*DUPLICATE_LOCATION_ATTRIBUTES - %w[scheme_id]) + .where.not(postcode: nil) + .where.not(mobility_type: nil) + .having( + "COUNT(*) > 1", + ) + scope.pluck("ARRAY_AGG(id)") + } + DUPLICATE_LOCATION_ATTRIBUTES = %w[scheme_id postcode mobility_type].freeze LOCAL_AUTHORITIES = LocalAuthority.all.map { |la| [la.name, la.code] }.to_h diff --git a/app/models/scheme.rb b/app/models/scheme.rb index 33f236374..1cd56ac7d 100644 --- a/app/models/scheme.rb +++ b/app/models/scheme.rb @@ -119,6 +119,22 @@ class Scheme < ApplicationRecord scope.pluck("ARRAY_AGG(id)") } + scope :duplicate_active_sets, lambda { + scope = active + .group(*DUPLICATE_SCHEME_ATTRIBUTES) + .where.not(scheme_type: nil) + .where.not(registered_under_care_act: nil) + .where.not(primary_client_group: nil) + .where.not(has_other_client_group: nil) + .where.not(secondary_client_group: nil).or(where(has_other_client_group: 0)) + .where.not(support_type: nil) + .where.not(intended_stay: nil) + .having( + "COUNT(*) > 1", + ) + scope.pluck("ARRAY_AGG(id)") + } + validate :validate_confirmed validate :validate_owning_organisation diff --git a/lib/tasks/count_duplicates.rake b/lib/tasks/count_duplicates.rake index e65688b4d..76cd1d991 100644 --- a/lib/tasks/count_duplicates.rake +++ b/lib/tasks/count_duplicates.rake @@ -60,4 +60,66 @@ namespace :count_duplicates do url = storage_service.get_presigned_url(filename, 72.hours.to_i) Rails.logger.info("Download URL: #{url}") end + + desc "Count the number of duplicate active schemes per organisation" + task active_scheme_duplicates_per_org: :environment do + duplicates_csv = CSV.generate(headers: true) do |csv| + csv << ["Organisation id", "Number of duplicate sets", "Total duplicate schemes"] + + Organisation.visible.each do |organisation| + if organisation.owned_schemes.duplicate_active_sets.count.positive? + csv << [organisation.id, organisation.owned_schemes.duplicate_active_sets.count, organisation.owned_schemes.duplicate_active_sets.sum(&:size)] + end + end + end + + filename = "active-scheme-duplicates-#{Time.zone.now}.csv" + storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"]) + storage_service.write_file(filename, "#{duplicates_csv}") + + url = storage_service.get_presigned_url(filename, 72.hours.to_i) + Rails.logger.info("Download URL: #{url}") + end + + desc "Count the number of duplicate active locations per organisation" + task active_location_duplicates_per_org: :environment do + duplicates_csv = CSV.generate(headers: true) do |csv| + csv << ["Organisation id", "Duplicate sets within individual schemes", "Duplicate locations within individual schemes", "All duplicate sets", "All duplicates"] + + Organisation.visible.each do |organisation| + duplicate_sets_within_individual_schemes = [] + + organisation.owned_schemes.each do |scheme| + duplicate_sets_within_individual_schemes += scheme.locations.duplicate_active_sets + end + duplicate_locations_within_individual_schemes = duplicate_sets_within_individual_schemes.flatten + + duplicate_sets_within_duplicate_schemes = [] + if organisation.owned_schemes.duplicate_active_sets.count.positive? + organisation.owned_schemes.duplicate_active_sets.each do |duplicate_set| + duplicate_sets_within_duplicate_schemes += Location.where(scheme_id: duplicate_set).duplicate_active_sets_within_given_schemes + end + duplicate_locations_within_duplicate_schemes_ids = duplicate_sets_within_duplicate_schemes.flatten + + duplicate_sets_within_individual_schemes_without_intersecting_sets = duplicate_sets_within_individual_schemes.reject { |set| set.any? { |id| duplicate_sets_within_duplicate_schemes.any? { |duplicate_set| duplicate_set.include?(id) } } } + all_duplicate_sets_count = (duplicate_sets_within_individual_schemes_without_intersecting_sets + duplicate_sets_within_duplicate_schemes).count + all_duplicate_locations_count = (duplicate_locations_within_duplicate_schemes_ids + duplicate_locations_within_individual_schemes).uniq.count + else + all_duplicate_sets_count = duplicate_sets_within_individual_schemes.count + all_duplicate_locations_count = duplicate_locations_within_individual_schemes.count + end + + if all_duplicate_locations_count.positive? + csv << [organisation.id, duplicate_sets_within_individual_schemes.count, duplicate_locations_within_individual_schemes.count, all_duplicate_sets_count, all_duplicate_locations_count] + end + end + end + + filename = "active-location-duplicates-#{Time.zone.now}.csv" + storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["BULK_UPLOAD_BUCKET"]) + storage_service.write_file(filename, "#{duplicates_csv}") + + url = storage_service.get_presigned_url(filename, 72.hours.to_i) + Rails.logger.info("Download URL: #{url}") + end end diff --git a/spec/factories/scheme.rb b/spec/factories/scheme.rb index 34f98a8a6..e7ecc8b60 100644 --- a/spec/factories/scheme.rb +++ b/spec/factories/scheme.rb @@ -44,5 +44,10 @@ FactoryBot.define do trait :created_now do created_at { Time.zone.now } end + trait :with_location do + after(:create) do |scheme| + create(:location, scheme:) + end + end end end diff --git a/spec/lib/tasks/count_duplicates_spec.rb b/spec/lib/tasks/count_duplicates_spec.rb index 99da5b2fb..b4f6a8db8 100644 --- a/spec/lib/tasks/count_duplicates_spec.rb +++ b/spec/lib/tasks/count_duplicates_spec.rb @@ -108,4 +108,114 @@ RSpec.describe "count_duplicates" do end end end + + describe "count_duplicates:active_scheme_duplicates_per_org", type: :task do + subject(:task) { Rake::Task["count_duplicates:active_scheme_duplicates_per_org"] } + + let(:storage_service) { instance_double(Storage::S3Service) } + let(:test_url) { "test_url" } + + before do + Rake.application.rake_require("tasks/count_duplicates") + Rake::Task.define_task(:environment) + task.reenable + end + + context "when the rake task is run" do + context "and there are no duplicate schemes" do + before do + create(:organisation) + end + + it "creates a csv with headers only" do + expect(storage_service).to receive(:write_file).with(/scheme-duplicates-.*\.csv/, "\uFEFFOrganisation id,Number of duplicate sets,Total duplicate schemes\n") + expect(Rails.logger).to receive(:info).with("Download URL: #{test_url}") + task.invoke + end + end + + context "and there are duplicate schemes" do + let(:organisation) { create(:organisation) } + let(:organisation2) { create(:organisation) } + + before do + create_list(:scheme, 2, :duplicate, :with_location, owning_organisation: organisation) + create_list(:scheme, 3, :duplicate, :with_location, primary_client_group: "I", owning_organisation: organisation) + create_list(:scheme, 5, :duplicate, :with_location, owning_organisation: organisation2) + deactivated_schemes = create_list(:scheme, 2, :duplicate, owning_organisation: organisation) + deactivated_schemes.each do |scheme| + create(:scheme_deactivation_period, deactivation_date: Time.zone.yesterday, reactivation_date: nil, scheme:) + end + end + + it "creates a csv with correct duplicate numbers" do + expect(storage_service).to receive(:write_file).with(/scheme-duplicates-.*\.csv/, "\uFEFFOrganisation id,Number of duplicate sets,Total duplicate schemes\n#{organisation.id},2,5\n#{organisation2.id},1,5\n") + expect(Rails.logger).to receive(:info).with("Download URL: #{test_url}") + task.invoke + end + end + end + end + + describe "count_duplicates:active_location_duplicates_per_org", type: :task do + subject(:task) { Rake::Task["count_duplicates:active_location_duplicates_per_org"] } + + let(:storage_service) { instance_double(Storage::S3Service) } + let(:test_url) { "test_url" } + + before do + Rake.application.rake_require("tasks/count_duplicates") + Rake::Task.define_task(:environment) + task.reenable + end + + context "when the rake task is run" do + context "and there are no duplicate locations" do + before do + create(:organisation) + end + + it "creates a csv with headers only" do + expect(storage_service).to receive(:write_file).with(/location-duplicates-.*\.csv/, "\uFEFFOrganisation id,Duplicate sets within individual schemes,Duplicate locations within individual schemes,All duplicate sets,All duplicates\n") + expect(Rails.logger).to receive(:info).with("Download URL: #{test_url}") + task.invoke + end + end + + context "and there are duplicate locations" do + let(:organisation) { create(:organisation) } + let(:scheme_a) { create(:scheme, :duplicate, owning_organisation: organisation) } + let(:scheme_b) { create(:scheme, :duplicate, owning_organisation: organisation) } + let(:scheme_c) { create(:scheme, owning_organisation: organisation) } + let(:organisation2) { create(:organisation) } + let(:scheme2) { create(:scheme, owning_organisation: organisation2) } + let(:scheme3) { create(:scheme, owning_organisation: organisation2) } + + before do + create_list(:location, 2, postcode: "A1 1AB", mobility_type: "M", scheme: scheme_a) # Location A + create_list(:location, 1, postcode: "A1 1AB", mobility_type: "A", scheme: scheme_a) # Location B + + create_list(:location, 1, postcode: "A1 1AB", mobility_type: "M", scheme: scheme_b) # Location A + create_list(:location, 1, postcode: "A1 1AB", mobility_type: "A", scheme: scheme_b) # Location B + create_list(:location, 2, postcode: "A1 1AB", mobility_type: "N", scheme: scheme_b) # Location C + + create_list(:location, 2, postcode: "A1 1AB", mobility_type: "A", scheme: scheme_c) # Location B + + create_list(:location, 5, postcode: "A1 1AB", mobility_type: "M", scheme: scheme2) + create_list(:location, 2, postcode: "A1 1AB", mobility_type: "M", scheme: scheme3) + + deactivated_locations = create_list(:location, 1, postcode: "A1 1AB", mobility_type: "M", scheme: scheme_b) + deactivated_locations.each do |location| + create(:location_deactivation_period, deactivation_date: Time.zone.yesterday, reactivation_date: nil, location:) + end + end + + it "creates a csv with correct duplicate numbers" do + expect(storage_service).to receive(:write_file).with(/location-duplicates-.*\.csv/, "\uFEFFOrganisation id,Duplicate sets within individual schemes,Duplicate locations within individual schemes,All duplicate sets,All duplicates\n#{organisation.id},3,6,4,9\n#{organisation2.id},2,7,2,7\n") + expect(Rails.logger).to receive(:info).with("Download URL: #{test_url}") + task.invoke + end + end + end + end end From baf12a1c4fffe3e6c27404b2081a88e3a9adaf00 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:33:26 +0000 Subject: [PATCH 05/36] CLDC-3730 Update tasklist styling (#2822) * Update tasklist styling * Update tasklist * Update test --- app/helpers/tag_helper.rb | 9 +++++++-- app/helpers/tasklist_helper.rb | 13 ++++++++++++- app/views/logs/_tasklist.html.erb | 28 +++++++++++++++------------- spec/helpers/tag_helper_spec.rb | 4 ++-- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/app/helpers/tag_helper.rb b/app/helpers/tag_helper.rb index bc0d8e06b..398f897cf 100644 --- a/app/helpers/tag_helper.rb +++ b/app/helpers/tag_helper.rb @@ -30,8 +30,7 @@ module TagHelper }.freeze COLOUR = { - not_started: "grey", - cannot_start_yet: "grey", + not_started: "light-blue", in_progress: "blue", completed: "green", active: "green", @@ -58,6 +57,8 @@ module TagHelper }.freeze def status_tag(status, classes = []) + return nil if COLOUR[status.to_sym].nil? + govuk_tag( classes:, colour: COLOUR[status.to_sym], @@ -65,6 +66,10 @@ module TagHelper ) end + def status_text(status) + TEXT[status.to_sym] + end + def status_tag_from_resource(resource, classes = []) status = resource.status status = :active if resource.deactivates_in_a_long_time? diff --git a/app/helpers/tasklist_helper.rb b/app/helpers/tasklist_helper.rb index 2caef019a..3a07a00f3 100644 --- a/app/helpers/tasklist_helper.rb +++ b/app/helpers/tasklist_helper.rb @@ -41,12 +41,19 @@ module TasklistHelper 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 }) + govuk_link_to(subsection.label, next_page_path.dasherize, class: "govuk-task-list__link", aria: { describedby: subsection.id.dasherize }) else subsection.label end end + def subsection_href(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 + next_page_path.dasherize + end + end + def review_log_text(log) if log.collection_period_open? path = log.sales? ? review_sales_log_path(id: log, sales_log: true) : review_lettings_log_path(log) @@ -59,6 +66,10 @@ module TasklistHelper end end + def tasklist_link_class(status) + status == :cannot_start_yet ? "" : "govuk-task-list__item--with-link" + end + private def breadcrumb_organisation(log) diff --git a/app/views/logs/_tasklist.html.erb b/app/views/logs/_tasklist.html.erb index e2f977a70..7a5e26a0e 100644 --- a/app/views/logs/_tasklist.html.erb +++ b/app/views/logs/_tasklist.html.erb @@ -8,19 +8,21 @@ <% if section.description %>

<%= section.description.html_safe %>

<% end %> -
    - <% section.subsections.each do |subsection| %> - <% if subsection.displayed_in_tasklist?(@log) && (subsection.applicable_questions(@log).count > 0 || !subsection.enabled?(@log)) %> - <% subsection_status = subsection.status(@log) %> -
  • - - <%= subsection_link(subsection, @log, current_user) %> - - <%= status_tag(subsection_status, "app-task-list__tag") %> -
  • - <% end %> - <% end %> -
+ <%= govuk_task_list(id_prefix: "logs", classes: "app-task-list__items") do |task_list| + section.subsections.each do |subsection| + next unless subsection.displayed_in_tasklist?(@log) && (subsection.applicable_questions(@log).count.positive? || !subsection.enabled?(@log)) + + subsection_status = subsection.status(@log) + task_list.with_item(classes: "#{tasklist_link_class(subsection_status)} app-task-list__item") do |item| + item.with_title(text: subsection.label, href: subsection_href(subsection, @log, current_user), classes: "app-task-list__name-and-hint--my-modifier") + if status_tag(subsection_status, "app-task-list__tag").present? + item.with_status(text: status_tag(subsection_status), classes: "app-task-list__tag") + else + item.with_status(text: status_text(subsection_status), classes: "app-task-list__tag") + end + end + end + end %> <% end %> diff --git a/spec/helpers/tag_helper_spec.rb b/spec/helpers/tag_helper_spec.rb index 231323278..1a6c75724 100644 --- a/spec/helpers/tag_helper_spec.rb +++ b/spec/helpers/tag_helper_spec.rb @@ -10,8 +10,8 @@ RSpec.describe TagHelper do end it "returns tag with correct status text and colour and custom class" do - expect(status_tag("not_started", "app-tag--small")).to eq("Not started") - expect(status_tag("cannot_start_yet", "app-tag--small")).to eq("Cannot start yet") + expect(status_tag("not_started", "app-tag--small")).to eq("Not started") + expect(status_tag("cannot_start_yet", "app-tag--small")).to eq(nil) expect(status_tag("in_progress", "app-tag--small")).to eq("In progress") expect(status_tag("completed", "app-tag--small")).to eq("Completed") expect(status_tag("active", "app-tag--small")).to eq("Active") From ad4a7dcfc9a127f01c63244a685f12690918bf55 Mon Sep 17 00:00:00 2001 From: Manny Dinssa <44172848+Dinssa@users.noreply.github.com> Date: Mon, 2 Dec 2024 11:05:40 +0000 Subject: [PATCH 06/36] CLDC-3723: Add validation on location units question (#2798) * Update location units question validation * Update value * Change nil value in incomplete locations to another attribute * Update validation * Update test * Update test with new additional error message * Updates * Update test --- app/models/location.rb | 3 +- config/locales/en.yml | 4 ++- spec/models/location_spec.rb | 36 ++++++++++++++++--- .../validations/setup_validations_spec.rb | 2 +- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/app/models/location.rb b/app/models/location.rb index 58afc76e8..c333f653f 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -2,7 +2,8 @@ class Location < ApplicationRecord validates :postcode, on: :postcode, presence: { message: I18n.t("validations.location.postcode_blank") } validate :validate_postcode, on: :postcode, if: proc { |model| model.postcode.presence } validates :location_admin_district, on: :location_admin_district, presence: { message: I18n.t("validations.location_admin_district") } - validates :units, on: :units, presence: { message: I18n.t("validations.location.units") } + validates :units, on: :units, presence: { message: I18n.t("validations.location.units.must_be_number") } + validates :units, on: :units, numericality: { greater_than_or_equal_to: 1, message: I18n.t("validations.location.units.must_be_one_or_more") } validates :type_of_unit, on: :type_of_unit, presence: { message: I18n.t("validations.location.type_of_unit") } validates :mobility_type, on: :mobility_type, presence: { message: I18n.t("validations.location.mobility_standards") } validates :startdate, on: :startdate, presence: { message: I18n.t("validations.location.startdate_invalid") } diff --git a/config/locales/en.yml b/config/locales/en.yml index 80711129e..851a9ea2c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -361,7 +361,9 @@ en: location: postcode_blank: "Enter a postcode." - units: "The units at this location must be a number." + units: + must_be_number: "The units at this location must be a number." + must_be_one_or_more: "Number of units must be at least 1." type_of_unit: "Select the most common type of unit at this location." mobility_standards: "Select the mobility standard for the majority of the units at this location." startdate_invalid: "Enter a valid day, month and year when the first property became available at this location." diff --git a/spec/models/location_spec.rb b/spec/models/location_spec.rb index 18581fb6e..c220914b7 100644 --- a/spec/models/location_spec.rb +++ b/spec/models/location_spec.rb @@ -740,10 +740,38 @@ RSpec.describe Location, type: :model do describe "#units" do let(:location) { FactoryBot.build(:location) } - it "does add an error when the number of units is invalid" do - location.units = nil - location.valid?(:units) - expect(location.errors.count).to eq(1) + context "when the number of units is invalid" do + it "adds an error when units is nil" do + location.units = nil + location.valid?(:units) + expect(location.errors.count).to eq(2) + end + + it "adds an error when units is 0" do + location.units = 0 + location.valid?(:units) + expect(location.errors.count).to eq(1) + end + end + + context "when the number of units is valid" do + it "does not add an error when units is 1" do + location.units = 1 + location.valid?(:units) + expect(location.errors.count).to eq(0) + end + + it "does not add an error when units is 10" do + location.units = 10 + location.valid?(:units) + expect(location.errors.count).to eq(0) + end + + it "does not add an error when units is 200" do + location.units = 200 + location.valid?(:units) + expect(location.errors.count).to eq(0) + end end end diff --git a/spec/models/validations/setup_validations_spec.rb b/spec/models/validations/setup_validations_spec.rb index 5b4f03365..1104cc23d 100644 --- a/spec/models/validations/setup_validations_spec.rb +++ b/spec/models/validations/setup_validations_spec.rb @@ -703,7 +703,7 @@ RSpec.describe Validations::SetupValidations do .to include("This location is incomplete. Select another location or update this one.") end - it "produces no error when location is completes" do + it "produces no error when location is complete" do location.update!(units: 1) location.reload record.location = location From dcbfb70c463b1af2c571cbb1531556bbe71d5a02 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Mon, 2 Dec 2024 12:46:22 +0000 Subject: [PATCH 07/36] CLCD-3759 Update reset password copy and move the link (#2832) * Update reset password copy and move the link * lint --- app/views/devise/sessions/new.html.erb | 4 ++-- app/views/devise/shared/_links.html.erb | 2 +- spec/features/user_spec.rb | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index 440eeb624..04b436aa5 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -12,6 +12,8 @@ <%= content_for(:title) %> + <%= render "devise/shared/links" %> + <%= f.govuk_email_field :email, label: { text: "Email address" }, autocomplete: "email", @@ -25,5 +27,3 @@
<% end %> - -<%= render "devise/shared/links" %> diff --git a/app/views/devise/shared/_links.html.erb b/app/views/devise/shared/_links.html.erb index e463f5deb..f48e1b3af 100644 --- a/app/views/devise/shared/_links.html.erb +++ b/app/views/devise/shared/_links.html.erb @@ -7,7 +7,7 @@ <% end %> <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> -

You can <%= govuk_link_to "reset your password", new_password_path(resource_name) %> if you’ve forgotten it.

+

<%= govuk_link_to "Forgot password", new_password_path(resource_name) %>

<% end %> <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> diff --git a/spec/features/user_spec.rb b/spec/features/user_spec.rb index bc562824c..2e837abe2 100644 --- a/spec/features/user_spec.rb +++ b/spec/features/user_spec.rb @@ -61,7 +61,7 @@ RSpec.describe "User Features" do context "when the user has forgotten their password" do it "is redirected to the reset password page when they click the reset password link" do visit("/lettings-logs") - click_link("reset your password") + click_link("Forgot password") expect(page).to have_current_path("/account/password/new") end @@ -744,7 +744,7 @@ RSpec.describe "User Features" do it "is redirected to the reset password page when they click the reset password link" do visit("/account/sign-in") - click_link("reset your password") + click_link("Forgot password") expect(page).to have_current_path("/account/password/new") end From 128ac5d57ad719d36705cefaa6936d38dd15dfcc Mon Sep 17 00:00:00 2001 From: Manny Dinssa <44172848+Dinssa@users.noreply.github.com> Date: Mon, 2 Dec 2024 14:06:59 +0000 Subject: [PATCH 08/36] CLDC-3758: Sales - Add shared ownership staircasing transaction section (#2788) * db migration, new sales log fields * Add new staircasing transaction section * Update tests * Lint * Add tests * Use log methods * Lint * Update test * Remove compact * Change section display condition * CLDC-3760: Sales - firststair and numstair staircasing questions validations (#2793) * Add validation to new questions firststair and numstair * Remove page headers on single question page * Remove page * More useful page header * Show don't know option from 2025 onwards * Add page header to monthly rent only for the version with two questions on the page * Remove page header staircase initial date only page * Update displayed answer options * Fix lint * Updates after merge * Update test * Update tests --- .../derived_variables/sales_log_variables.rb | 3 + .../form/sales/pages/about_staircase.rb | 2 +- app/models/form/sales/pages/equity.rb | 2 +- .../sales/pages/monthly_rent_staircasing.rb | 17 ++++ .../pages/monthly_rent_staircasing_owned.rb | 17 ++++ .../form/sales/pages/staircase_first_time.rb | 15 +++ .../sales/pages/staircase_initial_date.rb | 16 +++ .../form/sales/pages/staircase_previous.rb | 18 ++++ app/models/form/sales/pages/staircase_sale.rb | 17 ++++ .../sales/pages/value_shared_ownership.rb | 2 +- app/models/form/sales/questions/equity.rb | 1 + .../monthly_rent_after_staircasing.rb | 15 +++ .../monthly_rent_before_staircasing.rb | 15 +++ .../form/sales/questions/mortgageused.rb | 2 +- .../form/sales/questions/staircase_count.rb | 15 +++ .../sales/questions/staircase_first_time.rb | 16 +++ .../sales/questions/staircase_initial_date.rb | 11 +++ .../sales/questions/staircase_last_date.rb | 11 +++ .../form/sales/questions/staircase_sale.rb | 2 +- app/models/form/sales/questions/value.rb | 1 + .../form/sales/sections/sale_information.rb | 16 +-- .../shared_ownership_initial_purchase.rb | 5 +- .../subsections/shared_ownership_scheme.rb | 4 +- ...hared_ownership_staircasing_transaction.rb | 35 +++++++ app/models/sales_log.rb | 4 + .../sales/sale_information_validations.rb | 17 ++++ .../forms/2025/sales/sale_information.en.yml | 92 ++++++++++++----- .../validations/sales/sale_information.en.yml | 6 ++ .../20241114173226_add_fields_to_sales_log.rb | 11 +++ db/schema.rb | 5 + .../form/sales/pages/about_staircase_spec.rb | 4 + spec/models/form/sales/pages/equity_spec.rb | 4 - .../monthly_rent_staircasing_owned_spec.rb | 31 ++++++ .../pages/monthly_rent_staircasing_spec.rb | 31 ++++++ .../sales/pages/staircase_first_time_spec.rb | 31 ++++++ .../pages/staircase_initial_date_spec.rb | 31 ++++++ .../sales/pages/staircase_previous_spec.rb | 31 ++++++ .../form/sales/pages/staircase_sale_spec.rb | 38 +++++++ .../pages/value_shared_ownership_spec.rb | 2 +- .../form/sales/questions/equity_spec.rb | 2 +- .../monthly_rent_after_staircasing_spec.rb | 37 +++++++ .../monthly_rent_before_staircasing_spec.rb | 37 +++++++ .../form/sales/questions/mortgageused_spec.rb | 98 +++++++++++++++---- .../sales/questions/staircase_count_spec.rb | 33 +++++++ .../questions/staircase_first_time_spec.rb | 40 ++++++++ .../questions/staircase_initial_date_spec.rb | 33 +++++++ .../questions/staircase_last_date_spec.rb | 33 +++++++ .../sales/questions/staircase_sale_spec.rb | 4 + .../models/form/sales/questions/value_spec.rb | 6 +- .../sales/sections/sale_information_spec.rb | 11 ++- .../shared_ownership_initial_purchase_spec.rb | 2 +- .../sale_information_validations_spec.rb | 40 ++++++++ 52 files changed, 902 insertions(+), 70 deletions(-) create mode 100644 app/models/form/sales/pages/monthly_rent_staircasing.rb create mode 100644 app/models/form/sales/pages/monthly_rent_staircasing_owned.rb create mode 100644 app/models/form/sales/pages/staircase_first_time.rb create mode 100644 app/models/form/sales/pages/staircase_initial_date.rb create mode 100644 app/models/form/sales/pages/staircase_previous.rb create mode 100644 app/models/form/sales/pages/staircase_sale.rb create mode 100644 app/models/form/sales/questions/monthly_rent_after_staircasing.rb create mode 100644 app/models/form/sales/questions/monthly_rent_before_staircasing.rb create mode 100644 app/models/form/sales/questions/staircase_count.rb create mode 100644 app/models/form/sales/questions/staircase_first_time.rb create mode 100644 app/models/form/sales/questions/staircase_initial_date.rb create mode 100644 app/models/form/sales/questions/staircase_last_date.rb create mode 100644 app/models/form/sales/subsections/shared_ownership_staircasing_transaction.rb create mode 100644 db/migrate/20241114173226_add_fields_to_sales_log.rb create mode 100644 spec/models/form/sales/pages/monthly_rent_staircasing_owned_spec.rb create mode 100644 spec/models/form/sales/pages/monthly_rent_staircasing_spec.rb create mode 100644 spec/models/form/sales/pages/staircase_first_time_spec.rb create mode 100644 spec/models/form/sales/pages/staircase_initial_date_spec.rb create mode 100644 spec/models/form/sales/pages/staircase_previous_spec.rb create mode 100644 spec/models/form/sales/pages/staircase_sale_spec.rb create mode 100644 spec/models/form/sales/questions/monthly_rent_after_staircasing_spec.rb create mode 100644 spec/models/form/sales/questions/monthly_rent_before_staircasing_spec.rb create mode 100644 spec/models/form/sales/questions/staircase_count_spec.rb create mode 100644 spec/models/form/sales/questions/staircase_first_time_spec.rb create mode 100644 spec/models/form/sales/questions/staircase_initial_date_spec.rb create mode 100644 spec/models/form/sales/questions/staircase_last_date_spec.rb diff --git a/app/models/derived_variables/sales_log_variables.rb b/app/models/derived_variables/sales_log_variables.rb index 0c13d4fdf..4f4c3105d 100644 --- a/app/models/derived_variables/sales_log_variables.rb +++ b/app/models/derived_variables/sales_log_variables.rb @@ -80,6 +80,9 @@ module DerivedVariables::SalesLogVariables self.is_la_inferred = false end + self.numstair = is_firststair? ? 1 : nil if numstair == 1 && firststair_changed? + self.mrent = 0 if stairowned_100? + set_encoded_derived_values!(DEPENDENCIES) end diff --git a/app/models/form/sales/pages/about_staircase.rb b/app/models/form/sales/pages/about_staircase.rb index d736bae15..2d42c7456 100644 --- a/app/models/form/sales/pages/about_staircase.rb +++ b/app/models/form/sales/pages/about_staircase.rb @@ -18,7 +18,7 @@ class Form::Sales::Pages::AboutStaircase < ::Form::Page end def staircase_sale_question - if form.start_date.year >= 2023 + if [2023, 2024].include?(form.start_date.year) Form::Sales::Questions::StaircaseSale.new(nil, nil, self) end end diff --git a/app/models/form/sales/pages/equity.rb b/app/models/form/sales/pages/equity.rb index 46eec40a3..12d3c0a1b 100644 --- a/app/models/form/sales/pages/equity.rb +++ b/app/models/form/sales/pages/equity.rb @@ -1,7 +1,7 @@ class Form::Sales::Pages::Equity < ::Form::Page def initialize(id, hsh, subsection) super - @id = "equity" + @copy_key = "sales.sale_information.equity" end def questions diff --git a/app/models/form/sales/pages/monthly_rent_staircasing.rb b/app/models/form/sales/pages/monthly_rent_staircasing.rb new file mode 100644 index 000000000..062439c52 --- /dev/null +++ b/app/models/form/sales/pages/monthly_rent_staircasing.rb @@ -0,0 +1,17 @@ +class Form::Sales::Pages::MonthlyRentStaircasing < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "monthly_rent_staircasing" + @copy_key = "sales.sale_information.mrent_staircasing" + @depends_on = [{ + "stairowned_100?" => false, + }] + end + + def questions + @questions ||= [ + Form::Sales::Questions::MonthlyRentBeforeStaircasing.new(nil, nil, self), + Form::Sales::Questions::MonthlyRentAfterStaircasing.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/monthly_rent_staircasing_owned.rb b/app/models/form/sales/pages/monthly_rent_staircasing_owned.rb new file mode 100644 index 000000000..b772d129f --- /dev/null +++ b/app/models/form/sales/pages/monthly_rent_staircasing_owned.rb @@ -0,0 +1,17 @@ +class Form::Sales::Pages::MonthlyRentStaircasingOwned < ::Form::Page + def initialize(id, hsh, subsection) + super + @id = "monthly_rent_staircasing_owned" + @copy_key = "sales.sale_information.mrent_staircasing" + @header = "" + @depends_on = [{ + "stairowned_100?" => true, + }] + end + + def questions + @questions ||= [ + Form::Sales::Questions::MonthlyRentBeforeStaircasing.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/staircase_first_time.rb b/app/models/form/sales/pages/staircase_first_time.rb new file mode 100644 index 000000000..239a2a930 --- /dev/null +++ b/app/models/form/sales/pages/staircase_first_time.rb @@ -0,0 +1,15 @@ +class Form::Sales::Pages::StaircaseFirstTime < ::Form::Page + def initialize(id, hsh, subsection) + super(id, hsh, subsection) + @id = "staircase_first_time" + @depends_on = [{ + "staircase" => 1, + }] + end + + def questions + @questions ||= [ + Form::Sales::Questions::StaircaseFirstTime.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/staircase_initial_date.rb b/app/models/form/sales/pages/staircase_initial_date.rb new file mode 100644 index 000000000..9404440f4 --- /dev/null +++ b/app/models/form/sales/pages/staircase_initial_date.rb @@ -0,0 +1,16 @@ +class Form::Sales::Pages::StaircaseInitialDate < ::Form::Page + def initialize(id, hsh, subsection) + super(id, hsh, subsection) + @id = "staircase_initial_date" + @header = "" + @depends_on = [{ + "is_firststair?" => true, + }] + end + + def questions + @questions ||= [ + Form::Sales::Questions::StaircaseInitialDate.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/staircase_previous.rb b/app/models/form/sales/pages/staircase_previous.rb new file mode 100644 index 000000000..30d0139ab --- /dev/null +++ b/app/models/form/sales/pages/staircase_previous.rb @@ -0,0 +1,18 @@ +class Form::Sales::Pages::StaircasePrevious < ::Form::Page + def initialize(id, hsh, subsection) + super(id, hsh, subsection) + @id = "staircase_previous" + @copy_key = "sales.sale_information.stairprevious" + @depends_on = [{ + "is_firststair?" => false, + }] + end + + def questions + @questions ||= [ + Form::Sales::Questions::StaircaseCount.new(nil, nil, self), + Form::Sales::Questions::StaircaseLastDate.new(nil, nil, self), + Form::Sales::Questions::StaircaseInitialDate.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/staircase_sale.rb b/app/models/form/sales/pages/staircase_sale.rb new file mode 100644 index 000000000..116db72b5 --- /dev/null +++ b/app/models/form/sales/pages/staircase_sale.rb @@ -0,0 +1,17 @@ +class Form::Sales::Pages::StaircaseSale < ::Form::Page + def initialize(id, hsh, subsection) + super(id, hsh, subsection) + @id = "staircase_sale" + @copy_key = form.start_year_2025_or_later? ? "sales.sale_information.staircasesale" : "sales.sale_information.about_staircasing.staircasesale" + @depends_on = [{ + "staircase" => 1, + "stairowned" => 100, + }] + end + + def questions + @questions ||= [ + Form::Sales::Questions::StaircaseSale.new(nil, nil, self), + ] + end +end diff --git a/app/models/form/sales/pages/value_shared_ownership.rb b/app/models/form/sales/pages/value_shared_ownership.rb index 200563053..bf628b3f8 100644 --- a/app/models/form/sales/pages/value_shared_ownership.rb +++ b/app/models/form/sales/pages/value_shared_ownership.rb @@ -1,7 +1,7 @@ class Form::Sales::Pages::ValueSharedOwnership < ::Form::Page def initialize(id, hsh, subsection) super - @id = "value_shared_ownership" + @copy_key = "sales.sale_information.value" end def questions diff --git a/app/models/form/sales/questions/equity.rb b/app/models/form/sales/questions/equity.rb index 7a2a4ce5b..e39e77ebb 100644 --- a/app/models/form/sales/questions/equity.rb +++ b/app/models/form/sales/questions/equity.rb @@ -2,6 +2,7 @@ class Form::Sales::Questions::Equity < ::Form::Question def initialize(id, hsh, page) super @id = "equity" + @copy_key = "sales.sale_information.equity.#{page.id}" @type = "numeric" @min = 0 @max = 100 diff --git a/app/models/form/sales/questions/monthly_rent_after_staircasing.rb b/app/models/form/sales/questions/monthly_rent_after_staircasing.rb new file mode 100644 index 000000000..1116abb7b --- /dev/null +++ b/app/models/form/sales/questions/monthly_rent_after_staircasing.rb @@ -0,0 +1,15 @@ +class Form::Sales::Questions::MonthlyRentAfterStaircasing < ::Form::Question + def initialize(id, hsh, page) + super + @id = "mrent" + @copy_key = "sales.sale_information.mrent_staircasing.poststaircasing" + @type = "numeric" + @min = 0 + @step = 0.01 + @width = 5 + @prefix = "£" + @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] + end + + QUESTION_NUMBER_FROM_YEAR = { 2025 => 99 }.freeze +end diff --git a/app/models/form/sales/questions/monthly_rent_before_staircasing.rb b/app/models/form/sales/questions/monthly_rent_before_staircasing.rb new file mode 100644 index 000000000..a7966447a --- /dev/null +++ b/app/models/form/sales/questions/monthly_rent_before_staircasing.rb @@ -0,0 +1,15 @@ +class Form::Sales::Questions::MonthlyRentBeforeStaircasing < ::Form::Question + def initialize(id, hsh, page) + super + @id = "mrentprestaircasing" + @copy_key = "sales.sale_information.mrent_staircasing.prestaircasing" + @type = "numeric" + @min = 0 + @step = 0.01 + @width = 5 + @prefix = "£" + @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] + end + + QUESTION_NUMBER_FROM_YEAR = { 2025 => 98 }.freeze +end diff --git a/app/models/form/sales/questions/mortgageused.rb b/app/models/form/sales/questions/mortgageused.rb index 3c3c42840..1c683384b 100644 --- a/app/models/form/sales/questions/mortgageused.rb +++ b/app/models/form/sales/questions/mortgageused.rb @@ -12,7 +12,7 @@ class Form::Sales::Questions::Mortgageused < ::Form::Question def displayed_answer_options(log, _user = nil) if log.outright_sale? && log.saledate && !form.start_year_2024_or_later? answer_options_without_dont_know - elsif log.stairowned == 100 || log.outright_sale? + elsif log.stairowned_100? || log.outright_sale? || (log.is_staircase? && form.start_year_2025_or_later?) ANSWER_OPTIONS else answer_options_without_dont_know diff --git a/app/models/form/sales/questions/staircase_count.rb b/app/models/form/sales/questions/staircase_count.rb new file mode 100644 index 000000000..07095cd6a --- /dev/null +++ b/app/models/form/sales/questions/staircase_count.rb @@ -0,0 +1,15 @@ +class Form::Sales::Questions::StaircaseCount < ::Form::Question + def initialize(id, hsh, page) + super + @id = "numstair" + @copy_key = "sales.sale_information.stairprevious.numstair" + @type = "numeric" + @width = 2 + @min = 2 + @max = 10 + @step = 1 + @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] + end + + QUESTION_NUMBER_FROM_YEAR = { 2025 => 82 }.freeze +end diff --git a/app/models/form/sales/questions/staircase_first_time.rb b/app/models/form/sales/questions/staircase_first_time.rb new file mode 100644 index 000000000..fed2c34fb --- /dev/null +++ b/app/models/form/sales/questions/staircase_first_time.rb @@ -0,0 +1,16 @@ +class Form::Sales::Questions::StaircaseFirstTime < ::Form::Question + def initialize(id, hsh, page) + super + @id = "firststair" + @type = "radio" + @answer_options = ANSWER_OPTIONS + @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] + end + + ANSWER_OPTIONS = { + "1" => { "value" => "Yes" }, + "2" => { "value" => "No" }, + }.freeze + + QUESTION_NUMBER_FROM_YEAR = { 2025 => 81 }.freeze +end diff --git a/app/models/form/sales/questions/staircase_initial_date.rb b/app/models/form/sales/questions/staircase_initial_date.rb new file mode 100644 index 000000000..b810d6689 --- /dev/null +++ b/app/models/form/sales/questions/staircase_initial_date.rb @@ -0,0 +1,11 @@ +class Form::Sales::Questions::StaircaseInitialDate < ::Form::Question + def initialize(id, hsh, page) + super + @id = "initialpurchase" + @copy_key = "sales.sale_information.stairprevious.initialpurchase" + @type = "date" + @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] + end + + QUESTION_NUMBER_FROM_YEAR = { 2025 => 83 }.freeze +end diff --git a/app/models/form/sales/questions/staircase_last_date.rb b/app/models/form/sales/questions/staircase_last_date.rb new file mode 100644 index 000000000..edb75e823 --- /dev/null +++ b/app/models/form/sales/questions/staircase_last_date.rb @@ -0,0 +1,11 @@ +class Form::Sales::Questions::StaircaseLastDate < ::Form::Question + def initialize(id, hsh, page) + super + @id = "lasttransaction" + @copy_key = "sales.sale_information.stairprevious.lasttransaction" + @type = "date" + @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] + end + + QUESTION_NUMBER_FROM_YEAR = { 2025 => 83 }.freeze +end diff --git a/app/models/form/sales/questions/staircase_sale.rb b/app/models/form/sales/questions/staircase_sale.rb index ac54084f5..de0977ecb 100644 --- a/app/models/form/sales/questions/staircase_sale.rb +++ b/app/models/form/sales/questions/staircase_sale.rb @@ -2,7 +2,7 @@ class Form::Sales::Questions::StaircaseSale < ::Form::Question def initialize(id, hsh, page) super @id = "staircasesale" - @copy_key = "sales.sale_information.about_staircasing.staircasesale" + @copy_key = form.start_year_2025_or_later? ? "sales.sale_information.staircasesale" : "sales.sale_information.about_staircasing.staircasesale" @type = "radio" @answer_options = ANSWER_OPTIONS @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] diff --git a/app/models/form/sales/questions/value.rb b/app/models/form/sales/questions/value.rb index 1d258899d..ad021e920 100644 --- a/app/models/form/sales/questions/value.rb +++ b/app/models/form/sales/questions/value.rb @@ -2,6 +2,7 @@ class Form::Sales::Questions::Value < ::Form::Question def initialize(id, hsh, page) super @id = "value" + @copy_key = "sales.sale_information.value.#{page.id}" @type = "numeric" @min = 0 @step = 1 diff --git a/app/models/form/sales/sections/sale_information.rb b/app/models/form/sales/sections/sale_information.rb index 22dbbef5a..fc2180529 100644 --- a/app/models/form/sales/sections/sale_information.rb +++ b/app/models/form/sales/sections/sale_information.rb @@ -4,18 +4,20 @@ class Form::Sales::Sections::SaleInformation < ::Form::Section @id = "sale_information" @label = "Sale information" @description = "" - @subsections = [ - shared_ownership_scheme_subsection, - Form::Sales::Subsections::DiscountedOwnershipScheme.new(nil, nil, self), - Form::Sales::Subsections::OutrightSale.new(nil, nil, self), - ] || [] + @subsections = [] + @subsections.concat(shared_ownership_scheme_subsection) + @subsections << Form::Sales::Subsections::DiscountedOwnershipScheme.new(nil, nil, self) + @subsections << Form::Sales::Subsections::OutrightSale.new(nil, nil, self) end def shared_ownership_scheme_subsection if form.start_year_2025_or_later? - Form::Sales::Subsections::SharedOwnershipInitialPurchase.new(nil, nil, self) + [ + Form::Sales::Subsections::SharedOwnershipInitialPurchase.new(nil, nil, self), + Form::Sales::Subsections::SharedOwnershipStaircasingTransaction.new(nil, nil, self), + ] else - Form::Sales::Subsections::SharedOwnershipScheme.new(nil, nil, self) + [Form::Sales::Subsections::SharedOwnershipScheme.new(nil, nil, self)] end end end diff --git a/app/models/form/sales/subsections/shared_ownership_initial_purchase.rb b/app/models/form/sales/subsections/shared_ownership_initial_purchase.rb index 5dfb322a2..175994b0b 100644 --- a/app/models/form/sales/subsections/shared_ownership_initial_purchase.rb +++ b/app/models/form/sales/subsections/shared_ownership_initial_purchase.rb @@ -4,6 +4,7 @@ class Form::Sales::Subsections::SharedOwnershipInitialPurchase < ::Form::Subsect @id = "shared_ownership_initial_purchase" @label = "Shared ownership - initial purchase" @depends_on = [{ "ownershipsch" => 1, "setup_completed?" => true, "staircase" => 2 }] + @copy_key = "sale_information" end def pages @@ -18,9 +19,9 @@ class Form::Sales::Subsections::SharedOwnershipInitialPurchase < ::Form::Subsect Form::Sales::Pages::PreviousBedrooms.new(nil, nil, self), Form::Sales::Pages::PreviousPropertyType.new(nil, nil, self), Form::Sales::Pages::PreviousTenure.new(nil, nil, self), - Form::Sales::Pages::ValueSharedOwnership.new(nil, nil, self), + Form::Sales::Pages::ValueSharedOwnership.new("value_shared_ownership", nil, self), Form::Sales::Pages::AboutPriceValueCheck.new("about_price_shared_ownership_value_check", nil, self), - Form::Sales::Pages::Equity.new(nil, nil, self), + Form::Sales::Pages::Equity.new("initial_equity", nil, self), Form::Sales::Pages::SharedOwnershipDepositValueCheck.new("shared_ownership_equity_value_check", nil, self), Form::Sales::Pages::Mortgageused.new("mortgage_used_shared_ownership", nil, self, ownershipsch: 1), Form::Sales::Pages::MortgageValueCheck.new("mortgage_used_mortgage_value_check", nil, self), diff --git a/app/models/form/sales/subsections/shared_ownership_scheme.rb b/app/models/form/sales/subsections/shared_ownership_scheme.rb index 20a088eae..c0718e009 100644 --- a/app/models/form/sales/subsections/shared_ownership_scheme.rb +++ b/app/models/form/sales/subsections/shared_ownership_scheme.rb @@ -27,9 +27,9 @@ class Form::Sales::Subsections::SharedOwnershipScheme < ::Form::Subsection Form::Sales::Pages::PreviousBedrooms.new(nil, nil, self), Form::Sales::Pages::PreviousPropertyType.new(nil, nil, self), Form::Sales::Pages::PreviousTenure.new(nil, nil, self), - Form::Sales::Pages::ValueSharedOwnership.new(nil, nil, self), + Form::Sales::Pages::ValueSharedOwnership.new("value_shared_ownership", nil, self), Form::Sales::Pages::AboutPriceValueCheck.new("about_price_shared_ownership_value_check", nil, self), - Form::Sales::Pages::Equity.new(nil, nil, self), + Form::Sales::Pages::Equity.new("equity", nil, self), Form::Sales::Pages::SharedOwnershipDepositValueCheck.new("shared_ownership_equity_value_check", nil, self), Form::Sales::Pages::Mortgageused.new("mortgage_used_shared_ownership", nil, self, ownershipsch: 1), Form::Sales::Pages::MortgageValueCheck.new("mortgage_used_mortgage_value_check", nil, self), diff --git a/app/models/form/sales/subsections/shared_ownership_staircasing_transaction.rb b/app/models/form/sales/subsections/shared_ownership_staircasing_transaction.rb new file mode 100644 index 000000000..000a0c800 --- /dev/null +++ b/app/models/form/sales/subsections/shared_ownership_staircasing_transaction.rb @@ -0,0 +1,35 @@ +class Form::Sales::Subsections::SharedOwnershipStaircasingTransaction < ::Form::Subsection + def initialize(id, hsh, section) + super + @id = "shared_ownership_staircasing_transaction" + @label = "Shared ownership - staircasing transaction" + @depends_on = [{ "ownershipsch" => 1, "setup_completed?" => true, "staircase" => 1 }] + @copy_key = "sale_information" + end + + def pages + @pages ||= [ + Form::Sales::Pages::AboutStaircase.new("about_staircasing_joint_purchase", nil, self, joint_purchase: true), + Form::Sales::Pages::AboutStaircase.new("about_staircasing_not_joint_purchase", nil, self, joint_purchase: false), + Form::Sales::Pages::StaircaseSale.new(nil, nil, self), + Form::Sales::Pages::StaircaseBoughtValueCheck.new(nil, nil, self), + Form::Sales::Pages::StaircaseOwnedValueCheck.new("staircase_owned_value_check_joint_purchase", nil, self, joint_purchase: true), + Form::Sales::Pages::StaircaseOwnedValueCheck.new("staircase_owned_value_check_not_joint_purchase", nil, self, joint_purchase: false), + Form::Sales::Pages::StaircaseFirstTime.new(nil, nil, self), + Form::Sales::Pages::StaircasePrevious.new(nil, nil, self), + Form::Sales::Pages::StaircaseInitialDate.new(nil, nil, self), + Form::Sales::Pages::ValueSharedOwnership.new("value_shared_ownership_staircase", nil, self), + Form::Sales::Pages::AboutPriceValueCheck.new("about_price_shared_ownership_value_check", nil, self), + Form::Sales::Pages::Equity.new("staircase_equity", nil, self), + Form::Sales::Pages::SharedOwnershipDepositValueCheck.new("shared_ownership_equity_value_check", nil, self), + Form::Sales::Pages::Mortgageused.new("staircase_mortgage_used_shared_ownership", nil, self, ownershipsch: 1), + Form::Sales::Pages::MonthlyRentStaircasingOwned.new(nil, nil, self), + Form::Sales::Pages::MonthlyRentStaircasing.new(nil, nil, self), + Form::Sales::Pages::MonthlyChargesValueCheck.new("monthly_charges_shared_ownership_value_check", nil, self), + ].compact + end + + def displayed_in_tasklist?(log) + log.staircase == 1 && (log.ownershipsch.nil? || log.ownershipsch == 1) + end +end diff --git a/app/models/sales_log.rb b/app/models/sales_log.rb index 01741fbc5..361aab6f6 100644 --- a/app/models/sales_log.rb +++ b/app/models/sales_log.rb @@ -557,4 +557,8 @@ class SalesLog < Log def is_resale? resale == 1 end + + def is_firststair? + firststair == 1 + end end diff --git a/app/models/validations/sales/sale_information_validations.rb b/app/models/validations/sales/sale_information_validations.rb index fa095a5e2..3825271c5 100644 --- a/app/models/validations/sales/sale_information_validations.rb +++ b/app/models/validations/sales/sale_information_validations.rb @@ -35,6 +35,14 @@ module Validations::Sales::SaleInformationValidations end end + def validate_staircasing_initial_purchase_date(record) + return unless record.initialpurchase + + if record.initialpurchase < Time.zone.local(1980, 1, 1) + record.errors.add :initialpurchase, I18n.t("validations.sales.sale_information.initialpurchase.must_be_after_1980") + end + end + def validate_previous_property_unit_type(record) return unless record.fromprop && record.frombeds @@ -351,6 +359,15 @@ module Validations::Sales::SaleInformationValidations end end + def validate_number_of_staircase_transactions(record) + return unless record.numstair + + if record.firststair == 2 && record.numstair < 2 + record.errors.add :numstair, I18n.t("validations.sales.sale_information.numstair.must_be_greater_than_one") + record.errors.add :firststair, I18n.t("validations.sales.sale_information.firststair.cannot_be_no") + end + end + def over_tolerance?(expected, actual, tolerance, strict: false) if strict (expected - actual).abs > tolerance diff --git a/config/locales/forms/2025/sales/sale_information.en.yml b/config/locales/forms/2025/sales/sale_information.en.yml index 9a273d1c3..0535caca1 100644 --- a/config/locales/forms/2025/sales/sale_information.en.yml +++ b/config/locales/forms/2025/sales/sale_information.en.yml @@ -26,24 +26,47 @@ en: question_text: "Did the buyer live in the property before purchasing it?" about_staircasing: - page_header: "About the staircasing transaction" - stairbought: - check_answer_label: "Percentage bought in this staircasing transaction" + page_header: "About the staircasing transaction" + stairbought: + check_answer_label: "Percentage bought in this staircasing transaction" + hint_text: "" + question_text: "What percentage of the property has been bought in this staircasing transaction?" + stairowned: + joint_purchase: + check_answer_label: "Percentage the buyers now own in total" hint_text: "" - question_text: "What percentage of the property has been bought in this staircasing transaction?" - stairowned: - joint_purchase: - check_answer_label: "Percentage the buyers now own in total" - hint_text: "" - question_text: "What percentage of the property do the buyers now own in total?" - not_joint_purchase: - check_answer_label: "Percentage the buyer now owns in total" - hint_text: "" - question_text: "What percentage of the property does the buyer now own in total?" - staircasesale: - check_answer_label: "Part of a back-to-back staircasing transaction" + question_text: "What percentage of the property do the buyers now own in total?" + not_joint_purchase: + check_answer_label: "Percentage the buyer now owns in total" hint_text: "" - question_text: "Is this transaction part of a back-to-back staircasing transaction to facilitate sale of the home on the open market?" + question_text: "What percentage of the property does the buyer now own in total?" + + staircasesale: + page_header: "" + check_answer_label: "Part of a back-to-back staircasing transaction?" + hint_text: "" + question_text: "Is this transaction part of a back-to-back staircasing transaction to facilitate sale of the home on the open market?" + + firststair: + page_header: "" + check_answer_label: "First time staircasing?" + hint_text: "" + question_text: "Is this the first time the shared owner has engaged in staircasing in the home?" + + stairprevious: + page_header: "About previous staircasing transactions" + numstair: + check_answer_label: "Number of staircasing transactions" + hint_text: "" + question_text: "Including this time, how many times has the shared owner engaged in staircasing in the home?" + initialpurchase: + check_answer_label: "Initial staircasing transaction" + hint_text: "" + question_text: "What was the date of the initial purchase of a share in the property?" + lasttransaction: + check_answer_label: "Last staircasing transaction" + hint_text: "" + question_text: "What was the date of the last staircasing transaction?" resale: page_header: "" @@ -101,19 +124,29 @@ en: value: page_header: "About the price of the property" - check_answer_label: "Full purchase price" - hint_text: "Enter the full purchase price of the property before any discounts are applied. For shared ownership, enter the full purchase price paid for 100% equity (this is equal to the value of the share owned by the PRP plus the value bought by the purchaser)" - question_text: "What was the full purchase price?" + value_shared_ownership: + check_answer_label: "Full purchase price" + hint_text: "Enter the full purchase price of the property before any discounts are applied. For shared ownership, enter the full purchase price paid for 100% equity (this is equal to the value of the share owned by the PRP plus the value bought by the purchaser)." + question_text: "What was the full purchase price?" + value_shared_ownership_staircase: + check_answer_label: "Full purchase price" + hint_text: "Enter the full purchase price paid for the equity bought in this staircasing transaction (this is equal to the value of the share bought by the purchaser)." + question_text: "What was the full purchase price for this staircasing transaction?" equity: page_header: "About the price of the property" - check_answer_label: "Initial percentage equity share" - hint_text: "Enter the amount of initial equity share held by the purchaser (for example, 25% or 50%)" - question_text: "What was the initial percentage share purchased?" + initial_equity: + check_answer_label: "Initial percentage equity share" + hint_text: "Enter the amount of initial equity share held by the purchaser (for example, 25% or 50%)" + question_text: "What was the initial percentage share purchased?" + staircase_equity: + check_answer_label: "Initial percentage equity share" + hint_text: "Enter the amount of initial equity share held by the purchaser (for example, 25% or 50%)" + question_text: "What was the percentage shared purchased in the initial transaction?" mortgageused: page_header: "Mortgage Amount" - check_answer_label: "Mortgage used" + check_answer_label: "Mortgage used?" hint_text: "" question_text: "Was a mortgage used for the purchase of this property?" @@ -165,6 +198,17 @@ en: hint_text: "Amount paid before any charges" question_text: "What is the basic monthly rent?" + mrent_staircasing: + page_header: "Monthly rent" + prestaircasing: + check_answer_label: "Monthly rent prior to staircasing" + hint_text: "Amount paid before any charges" + question_text: "What was the basic monthly rent prior to staircasing?" + poststaircasing: + check_answer_label: "Monthly rent after staircasing" + hint_text: "Amount paid before any charges" + question_text: "What is the basic monthly rent after staircasing?" + leaseholdcharges: page_header: "" has_mscharge: @@ -199,7 +243,7 @@ en: check_answer_label: "Amount of any loan, grant or subsidy" hint_text: "For all schemes except Right to Buy (RTB), Preserved Right to Buy (PRTB), Voluntary Right to Buy (VRTB) and Rent to Buy" question_text: "What was the amount of any loan, grant, discount or subsidy given?" - + management_fee: page_header: "" has_management_fee: diff --git a/config/locales/validations/sales/sale_information.en.yml b/config/locales/validations/sales/sale_information.en.yml index 8fb7d02d4..ea17953fb 100644 --- a/config/locales/validations/sales/sale_information.en.yml +++ b/config/locales/validations/sales/sale_information.en.yml @@ -19,6 +19,8 @@ en: exdate: must_be_before_saledate: "Contract exchange date must be before sale completion date." must_be_less_than_1_year_from_saledate: "Contract exchange date must be less than 1 year before sale completion date." + initialpurchase: + must_be_after_1980: "The initial purchase date must be after January 1, 1980." fromprop: previous_property_type_bedsit: "A bedsit cannot have more than 1 bedroom." frombeds: @@ -125,3 +127,7 @@ en: postcode_full: value_over_discounted_london_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £136,400 for properties in London." value_over_discounted_max: "The percentage discount multiplied by the purchase price is %{discount_value}. This figure should not be more than £102,400 for properties outside of London." + numstair: + must_be_greater_than_one: "The number of staircasing transactions must be greater than 1 when this is not the first staircasing transaction." + firststair: + cannot_be_no: "The answer to 'Is this the first staircasing transaction?' cannot be 'no' if the number of staircasing transactions is 1." diff --git a/db/migrate/20241114173226_add_fields_to_sales_log.rb b/db/migrate/20241114173226_add_fields_to_sales_log.rb new file mode 100644 index 000000000..2f7dbbd2b --- /dev/null +++ b/db/migrate/20241114173226_add_fields_to_sales_log.rb @@ -0,0 +1,11 @@ +class AddFieldsToSalesLog < ActiveRecord::Migration[7.0] + def change + change_table :sales_logs, bulk: true do |t| + t.column :firststair, :integer + t.column :numstair, :integer + t.column :mrentprestaircasing, :decimal, precision: 10, scale: 2 + t.column :lasttransaction, :datetime + t.column :initialpurchase, :datetime + end + end +end diff --git a/db/schema.rb b/db/schema.rb index c53872020..0cdc15e9f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -761,6 +761,11 @@ ActiveRecord::Schema[7.0].define(version: 2024_11_22_154743) do t.bigint "created_by_id" t.integer "has_management_fee" t.decimal "management_fee", precision: 10, scale: 2 + t.integer "firststair" + t.integer "numstair" + t.decimal "mrentprestaircasing", precision: 10, scale: 2 + t.datetime "lasttransaction" + t.datetime "initialpurchase" t.index ["assigned_to_id"], name: "index_sales_logs_on_assigned_to_id" t.index ["bulk_upload_id"], name: "index_sales_logs_on_bulk_upload_id" t.index ["created_by_id"], name: "index_sales_logs_on_created_by_id" diff --git a/spec/models/form/sales/pages/about_staircase_spec.rb b/spec/models/form/sales/pages/about_staircase_spec.rb index 181c3f695..7d8254d4b 100644 --- a/spec/models/form/sales/pages/about_staircase_spec.rb +++ b/spec/models/form/sales/pages/about_staircase_spec.rb @@ -15,6 +15,10 @@ RSpec.describe Form::Sales::Pages::AboutStaircase, type: :model do describe "questions" do let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date:)) } + before do + allow(subsection.form).to receive(:start_year_2025_or_later?).and_return(false) + end + context "when 2022" do let(:start_date) { Time.utc(2022, 2, 8) } diff --git a/spec/models/form/sales/pages/equity_spec.rb b/spec/models/form/sales/pages/equity_spec.rb index a44085101..83a5dfaa3 100644 --- a/spec/models/form/sales/pages/equity_spec.rb +++ b/spec/models/form/sales/pages/equity_spec.rb @@ -15,10 +15,6 @@ RSpec.describe Form::Sales::Pages::Equity, type: :model do expect(page.questions.map(&:id)).to eq(%w[equity]) end - it "has the correct id" do - expect(page.id).to eq("equity") - end - it "has the correct description" do expect(page.description).to be_nil end diff --git a/spec/models/form/sales/pages/monthly_rent_staircasing_owned_spec.rb b/spec/models/form/sales/pages/monthly_rent_staircasing_owned_spec.rb new file mode 100644 index 000000000..21f0e0ee6 --- /dev/null +++ b/spec/models/form/sales/pages/monthly_rent_staircasing_owned_spec.rb @@ -0,0 +1,31 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::MonthlyRentStaircasingOwned, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) } + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[mrentprestaircasing]) + end + + it "has the correct id" do + expect(page.id).to eq("monthly_rent_staircasing_owned") + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has correct depends_on" do + expect(page.depends_on).to eq([ + { "stairowned_100?" => true }, + ]) + end +end diff --git a/spec/models/form/sales/pages/monthly_rent_staircasing_spec.rb b/spec/models/form/sales/pages/monthly_rent_staircasing_spec.rb new file mode 100644 index 000000000..347e105fd --- /dev/null +++ b/spec/models/form/sales/pages/monthly_rent_staircasing_spec.rb @@ -0,0 +1,31 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::MonthlyRentStaircasing, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) } + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[mrentprestaircasing mrent]) + end + + it "has the correct id" do + expect(page.id).to eq("monthly_rent_staircasing") + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has correct depends_on" do + expect(page.depends_on).to eq([ + { "stairowned_100?" => false }, + ]) + end +end diff --git a/spec/models/form/sales/pages/staircase_first_time_spec.rb b/spec/models/form/sales/pages/staircase_first_time_spec.rb new file mode 100644 index 000000000..9c7d713af --- /dev/null +++ b/spec/models/form/sales/pages/staircase_first_time_spec.rb @@ -0,0 +1,31 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::StaircaseFirstTime, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) } + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[firststair]) + end + + it "has the correct id" do + expect(page.id).to eq("staircase_first_time") + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has correct depends_on" do + expect(page.depends_on).to eq([ + { "staircase" => 1 }, + ]) + end +end diff --git a/spec/models/form/sales/pages/staircase_initial_date_spec.rb b/spec/models/form/sales/pages/staircase_initial_date_spec.rb new file mode 100644 index 000000000..de5806500 --- /dev/null +++ b/spec/models/form/sales/pages/staircase_initial_date_spec.rb @@ -0,0 +1,31 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::StaircaseInitialDate, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) } + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[initialpurchase]) + end + + it "has the correct id" do + expect(page.id).to eq("staircase_initial_date") + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has correct depends_on" do + expect(page.depends_on).to eq([ + { "is_firststair?" => true }, + ]) + end +end diff --git a/spec/models/form/sales/pages/staircase_previous_spec.rb b/spec/models/form/sales/pages/staircase_previous_spec.rb new file mode 100644 index 000000000..336cf0454 --- /dev/null +++ b/spec/models/form/sales/pages/staircase_previous_spec.rb @@ -0,0 +1,31 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::StaircasePrevious, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) } + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[numstair lasttransaction initialpurchase]) + end + + it "has the correct id" do + expect(page.id).to eq("staircase_previous") + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has correct depends_on" do + expect(page.depends_on).to eq([ + { "is_firststair?" => false }, + ]) + end +end diff --git a/spec/models/form/sales/pages/staircase_sale_spec.rb b/spec/models/form/sales/pages/staircase_sale_spec.rb new file mode 100644 index 000000000..17a140797 --- /dev/null +++ b/spec/models/form/sales/pages/staircase_sale_spec.rb @@ -0,0 +1,38 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Pages::StaircaseSale, type: :model do + subject(:page) { described_class.new(page_id, page_definition, subsection) } + + let(:page_id) { nil } + let(:page_definition) { nil } + let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1))) } + + before do + allow(subsection.form).to receive(:start_year_2025_or_later?).and_return(true) + end + + it "has correct subsection" do + expect(page.subsection).to eq(subsection) + end + + it "has correct questions" do + expect(page.questions.map(&:id)).to eq(%w[staircasesale]) + end + + it "has the correct id" do + expect(page.id).to eq("staircase_sale") + end + + it "has the correct description" do + expect(page.description).to be_nil + end + + it "has correct depends_on" do + expect(page.depends_on).to eq([ + { + "staircase" => 1, + "stairowned" => 100, + }, + ]) + end +end diff --git a/spec/models/form/sales/pages/value_shared_ownership_spec.rb b/spec/models/form/sales/pages/value_shared_ownership_spec.rb index f8232894b..eb1b1099f 100644 --- a/spec/models/form/sales/pages/value_shared_ownership_spec.rb +++ b/spec/models/form/sales/pages/value_shared_ownership_spec.rb @@ -3,7 +3,7 @@ require "rails_helper" RSpec.describe Form::Sales::Pages::ValueSharedOwnership, type: :model do subject(:page) { described_class.new(page_id, page_definition, subsection) } - let(:page_id) { nil } + let(:page_id) { "value_shared_ownership" } let(:page_definition) { nil } let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1))) } diff --git a/spec/models/form/sales/questions/equity_spec.rb b/spec/models/form/sales/questions/equity_spec.rb index f58c042f5..5083af9e8 100644 --- a/spec/models/form/sales/questions/equity_spec.rb +++ b/spec/models/form/sales/questions/equity_spec.rb @@ -5,7 +5,7 @@ RSpec.describe Form::Sales::Questions::Equity, type: :model do let(:question_id) { nil } let(:question_definition) { nil } - let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) } + let(:page) { instance_double(Form::Page, id: "initial_equity", subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) } it "has correct page" do expect(question.page).to eq(page) diff --git a/spec/models/form/sales/questions/monthly_rent_after_staircasing_spec.rb b/spec/models/form/sales/questions/monthly_rent_after_staircasing_spec.rb new file mode 100644 index 000000000..4ceb3df00 --- /dev/null +++ b/spec/models/form/sales/questions/monthly_rent_after_staircasing_spec.rb @@ -0,0 +1,37 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::MonthlyRentAfterStaircasing, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) } + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("mrent") + end + + it "has the correct type" do + expect(question.type).to eq("numeric") + end + + it "is not marked as derived" do + expect(question.derived?(nil)).to be false + end + + it "has correct width" do + expect(question.width).to eq(5) + end + + it "has correct prefix" do + expect(question.prefix).to eq("£") + end + + it "has correct min" do + expect(question.min).to eq(0) + end +end diff --git a/spec/models/form/sales/questions/monthly_rent_before_staircasing_spec.rb b/spec/models/form/sales/questions/monthly_rent_before_staircasing_spec.rb new file mode 100644 index 000000000..8d7d864a8 --- /dev/null +++ b/spec/models/form/sales/questions/monthly_rent_before_staircasing_spec.rb @@ -0,0 +1,37 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::MonthlyRentBeforeStaircasing, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) } + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("mrentprestaircasing") + end + + it "has the correct type" do + expect(question.type).to eq("numeric") + end + + it "is not marked as derived" do + expect(question.derived?(nil)).to be false + end + + it "has correct width" do + expect(question.width).to eq(5) + end + + it "has correct prefix" do + expect(question.prefix).to eq("£") + end + + it "has correct min" do + expect(question.min).to eq(0) + end +end diff --git a/spec/models/form/sales/questions/mortgageused_spec.rb b/spec/models/form/sales/questions/mortgageused_spec.rb index e85238a4d..971eb2909 100644 --- a/spec/models/form/sales/questions/mortgageused_spec.rb +++ b/spec/models/form/sales/questions/mortgageused_spec.rb @@ -3,28 +3,39 @@ require "rails_helper" RSpec.describe Form::Sales::Questions::Mortgageused, type: :model do subject(:question) { described_class.new(question_id, question_definition, page, ownershipsch:) } - let(:ownershipsch) { 1 } let(:question_id) { nil } let(:question_definition) { nil } - let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) } - let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form:)) } let(:stairowned) { nil } let(:staircase) { nil } let(:saledate) { Time.zone.today } let(:log) { build(:sales_log, :in_progress, ownershipsch:, stairowned:, staircase:) } - it "has the correct answer_options" do - expect(question.answer_options).to eq({ - "1" => { "value" => "Yes" }, - "2" => { "value" => "No" }, - "3" => { "value" => "Don’t know" }, - }) - end + context "when the form start year is 2024" do + let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1)) } + let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form:)) } + let(:saledate) { Time.zone.local(2024, 5, 1) } + let(:ownershipsch) { 1 } + + before do + allow(form).to receive(:start_year_2024_or_later?).and_return true + allow(form).to receive(:start_year_2025_or_later?).and_return false + end + + it "has the correct answer_options" do + expect(question.answer_options).to eq({ + "1" => { "value" => "Yes" }, + "2" => { "value" => "No" }, + "3" => { "value" => "Don’t know" }, + }) + end - describe "the displayed answer options" do context "when it is a discounted ownership sale" do let(:ownershipsch) { 2 } + it "shows the correct question number" do + expect(question.question_number).to eq 104 + end + it "does not show the don't know option" do expect_the_question_not_to_show_dont_know end @@ -34,20 +45,14 @@ RSpec.describe Form::Sales::Questions::Mortgageused, type: :model do let(:ownershipsch) { 3 } context "and the saledate is before 24/25" do - before do - allow(form).to receive(:start_year_2024_or_later?).and_return false - end + let(:saledate) { Time.zone.local(2023, 5, 1) }\ - it "does not show the don't know option" do - expect_the_question_not_to_show_dont_know + it "does show the don't know option" do + expect_the_question_to_show_dont_know end end - context "and the saledate is 24/25 or after" do - before do - allow(form).to receive(:start_year_2024_or_later?).and_return true - end - + context "and the saledate is 24/25" do it "shows the don't know option" do expect_the_question_to_show_dont_know end @@ -87,6 +92,57 @@ RSpec.describe Form::Sales::Questions::Mortgageused, type: :model do end end + context "when the form start year is 2025" do + let(:form) { instance_double(Form, start_date: Time.zone.local(2025, 4, 1)) } + let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form:)) } + let(:saledate) { Time.zone.local(2025, 5, 1) } + + before do + allow(form).to receive(:start_year_2024_or_later?).and_return true + allow(form).to receive(:start_year_2025_or_later?).and_return true + end + + context "when it is a discounted ownership sale" do + let(:ownershipsch) { 2 } + + it "shows the correct question number" do + expect(question.question_number).to eq 104 + end + + it "does not show the don't know option" do + expect_the_question_not_to_show_dont_know + end + end + + context "when it is a shared ownership scheme" do + let(:ownershipsch) { 1 } + + context "and it is a staircasing transaction" do + let(:staircase) { 1 } + + it "does show the don't know option" do + expect_the_question_to_show_dont_know + end + + context "and stairowned is 100" do + let(:stairowned) { 100 } + + it "shows the don't know option" do + expect_the_question_to_show_dont_know + end + end + end + + context "and it is not a staircasing transaction" do + let(:staircase) { 2 } + + it "does not show the don't know option" do + expect_the_question_not_to_show_dont_know + end + end + end + end + private def expect_the_question_not_to_show_dont_know diff --git a/spec/models/form/sales/questions/staircase_count_spec.rb b/spec/models/form/sales/questions/staircase_count_spec.rb new file mode 100644 index 000000000..06f39b3d0 --- /dev/null +++ b/spec/models/form/sales/questions/staircase_count_spec.rb @@ -0,0 +1,33 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::StaircaseCount, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1)))) } + + before do + allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(true) + end + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("numstair") + end + + it "has the correct type" do + expect(question.type).to eq("numeric") + end + + it "is not marked as derived" do + expect(question.derived?(nil)).to be false + end + + it "has correct conditional for" do + expect(question.conditional_for).to eq(nil) + end +end diff --git a/spec/models/form/sales/questions/staircase_first_time_spec.rb b/spec/models/form/sales/questions/staircase_first_time_spec.rb new file mode 100644 index 000000000..59d0281a2 --- /dev/null +++ b/spec/models/form/sales/questions/staircase_first_time_spec.rb @@ -0,0 +1,40 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::StaircaseFirstTime, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1)))) } + + before do + allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(true) + end + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("firststair") + end + + it "has the correct type" do + expect(question.type).to eq("radio") + end + + it "is not marked as derived" do + expect(question.derived?(nil)).to be false + end + + it "has the correct answer_options" do + expect(question.answer_options).to eq({ + "1" => { "value" => "Yes" }, + "2" => { "value" => "No" }, + }) + end + + it "has correct conditional for" do + expect(question.conditional_for).to eq(nil) + end +end diff --git a/spec/models/form/sales/questions/staircase_initial_date_spec.rb b/spec/models/form/sales/questions/staircase_initial_date_spec.rb new file mode 100644 index 000000000..3c244e512 --- /dev/null +++ b/spec/models/form/sales/questions/staircase_initial_date_spec.rb @@ -0,0 +1,33 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::StaircaseInitialDate, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1)))) } + + before do + allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(true) + end + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("initialpurchase") + end + + it "has the correct type" do + expect(question.type).to eq("date") + end + + it "is not marked as derived" do + expect(question.derived?(nil)).to be false + end + + it "has correct conditional for" do + expect(question.conditional_for).to eq(nil) + end +end diff --git a/spec/models/form/sales/questions/staircase_last_date_spec.rb b/spec/models/form/sales/questions/staircase_last_date_spec.rb new file mode 100644 index 000000000..2efa5ccc7 --- /dev/null +++ b/spec/models/form/sales/questions/staircase_last_date_spec.rb @@ -0,0 +1,33 @@ +require "rails_helper" + +RSpec.describe Form::Sales::Questions::StaircaseLastDate, type: :model do + subject(:question) { described_class.new(question_id, question_definition, page) } + + let(:question_id) { nil } + let(:question_definition) { nil } + let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2025, 4, 1)))) } + + before do + allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(true) + end + + it "has correct page" do + expect(question.page).to eq(page) + end + + it "has the correct id" do + expect(question.id).to eq("lasttransaction") + end + + it "has the correct type" do + expect(question.type).to eq("date") + end + + it "is not marked as derived" do + expect(question.derived?(nil)).to be false + end + + it "has correct conditional for" do + expect(question.conditional_for).to eq(nil) + end +end diff --git a/spec/models/form/sales/questions/staircase_sale_spec.rb b/spec/models/form/sales/questions/staircase_sale_spec.rb index d330aecb3..8d3d1e5da 100644 --- a/spec/models/form/sales/questions/staircase_sale_spec.rb +++ b/spec/models/form/sales/questions/staircase_sale_spec.rb @@ -7,6 +7,10 @@ RSpec.describe Form::Sales::Questions::StaircaseSale, type: :model do let(:question_definition) { nil } let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) } + before do + allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(false) + end + it "has correct page" do expect(question.page).to eq(page) end diff --git a/spec/models/form/sales/questions/value_spec.rb b/spec/models/form/sales/questions/value_spec.rb index 771607c16..10f281e63 100644 --- a/spec/models/form/sales/questions/value_spec.rb +++ b/spec/models/form/sales/questions/value_spec.rb @@ -5,7 +5,11 @@ RSpec.describe Form::Sales::Questions::Value, type: :model do let(:question_id) { nil } let(:question_definition) { nil } - let(:page) { instance_double(Form::Page, subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) } + let(:page) { instance_double(Form::Page, id: "value_shared_ownership", subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) } + + before do + allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(false) + end it "has correct page" do expect(question.page).to eq(page) diff --git a/spec/models/form/sales/sections/sale_information_spec.rb b/spec/models/form/sales/sections/sale_information_spec.rb index 8167c92a3..191bca4bb 100644 --- a/spec/models/form/sales/sections/sale_information_spec.rb +++ b/spec/models/form/sales/sections/sale_information_spec.rb @@ -7,6 +7,10 @@ RSpec.describe Form::Sales::Sections::SaleInformation, type: :model do let(:section_definition) { nil } let(:form) { instance_double(Form, start_year_2025_or_later?: false) } + before do + allow(form).to receive(:start_year_2025_or_later?).and_return(false) + end + it "has correct form" do expect(sale_information.form).to eq(form) end @@ -22,11 +26,16 @@ RSpec.describe Form::Sales::Sections::SaleInformation, type: :model do end context "when form is 2025 or later" do - let(:form) { instance_double(Form, start_year_2025_or_later?: true) } + let(:form) { instance_double(Form) } + + before do + allow(form).to receive(:start_year_2025_or_later?).and_return(true) + end it "has correct subsections" do expect(sale_information.subsections.map(&:id)).to eq(%w[ shared_ownership_initial_purchase + shared_ownership_staircasing_transaction discounted_ownership_scheme outright_sale ]) diff --git a/spec/models/form/sales/subsections/shared_ownership_initial_purchase_spec.rb b/spec/models/form/sales/subsections/shared_ownership_initial_purchase_spec.rb index 3b2d72b01..3e473a162 100644 --- a/spec/models/form/sales/subsections/shared_ownership_initial_purchase_spec.rb +++ b/spec/models/form/sales/subsections/shared_ownership_initial_purchase_spec.rb @@ -30,7 +30,7 @@ RSpec.describe Form::Sales::Subsections::SharedOwnershipInitialPurchase, type: : shared_ownership_previous_tenure value_shared_ownership about_price_shared_ownership_value_check - equity + initial_equity shared_ownership_equity_value_check mortgage_used_shared_ownership mortgage_used_mortgage_value_check diff --git a/spec/models/validations/sales/sale_information_validations_spec.rb b/spec/models/validations/sales/sale_information_validations_spec.rb index 5cc0cdf07..fabe3c8c5 100644 --- a/spec/models/validations/sales/sale_information_validations_spec.rb +++ b/spec/models/validations/sales/sale_information_validations_spec.rb @@ -1407,4 +1407,44 @@ RSpec.describe Validations::Sales::SaleInformationValidations do end end end + + describe "#validate_number_of_staircase_transactions" do + let(:record) { build(:sales_log, numstair:, firststair:) } + + before do + sale_information_validator.validate_number_of_staircase_transactions(record) + end + + context "when it is not the first staircasing transaction" do + context "and the number of staircasing transactions is between 2 and 10" do + let(:numstair) { 6 } + let(:firststair) { 2 } + + it "does not add an error" do + expect(record.errors).to be_empty + end + end + + context "and the number of staircasing transactions is less than 2" do + let(:numstair) { 1 } + let(:firststair) { 2 } + + it "adds an error" do + expect(record.errors[:numstair]).to include(I18n.t("validations.sales.sale_information.numstair.must_be_greater_than_one")) + expect(record.errors[:firststair]).to include(I18n.t("validations.sales.sale_information.firststair.cannot_be_no")) + end + end + end + + context "when it is the first staircasing transaction" do + context "and numstair is also 1" do + let(:numstair) { 1 } + let(:firststair) { 1 } + + it "does not add an error" do + expect(record.errors).to be_empty + end + end + end + end end From 5aff4d9989d09b9ab89bfe2bc2d2cd847ff9b608 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:24:36 +0000 Subject: [PATCH 09/36] CLDC-1180 Link to radio/checkbox errors from summary (#2835) * try linking errors * Change question ids to symbols and update checkboxes --- app/views/form/_checkbox_question.html.erb | 5 +++-- app/views/form/_radio_question.html.erb | 8 +++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/views/form/_checkbox_question.html.erb b/app/views/form/_checkbox_question.html.erb index 2e8585e15..b4feb12bd 100644 --- a/app/views/form/_checkbox_question.html.erb +++ b/app/views/form/_checkbox_question.html.erb @@ -6,16 +6,17 @@ hint: { text: question.hint_text&.html_safe } do %> <% after_divider = false %> - <% question.displayed_answer_options(@log).map do |key, option| %> + <% question.displayed_answer_options(@log).each_with_index do |(key, option), index| %> <% if key.starts_with?("divider") %> <% after_divider = true %> <%= f.govuk_check_box_divider %> <% else %> - <%= f.govuk_check_box question.id, key, + <%= f.govuk_check_box question.id.to_sym, key, label: { text: option["value"] }, hint: { text: option["hint"] }, checked: @log[key] == 1, exclusive: after_divider, + link_errors: index.zero? ? true : nil, **stimulus_html_attributes(question) %> <% end %> <% end %> diff --git a/app/views/form/_radio_question.html.erb b/app/views/form/_radio_question.html.erb index 41e98a1cc..bf6abb0d0 100644 --- a/app/views/form/_radio_question.html.erb +++ b/app/views/form/_radio_question.html.erb @@ -18,22 +18,24 @@ legend: legend(question, page_header, conditional), hint: { text: question.hint_text&.html_safe } do %> - <% question.displayed_answer_options(@log, current_user).map do |key, options| %> + <% question.displayed_answer_options(@log, current_user).each_with_index do |(key, options), index| %> <% if key.starts_with?("divider") %> <%= f.govuk_radio_divider %> <% else %> <% conditional_question = find_conditional_question(@page, question, key) %> <% if conditional_question.nil? %> - <%= f.govuk_radio_button question.id, + <%= f.govuk_radio_button question.id.to_sym, key, label: { text: options["value"] }, hint: { text: options["hint"] }, + link_errors: index.zero? ? true : nil, **stimulus_html_attributes(question) %> <% else %> - <%= f.govuk_radio_button question.id, + <%= f.govuk_radio_button question.id.to_sym, key, label: { text: options["value"] }, hint: { text: options["hint"] }, + link_errors: index.zero? ? true : nil, **stimulus_html_attributes(question) do %> <%= render partial: "#{conditional_question.type}_question", locals: { question: conditional_question, From 838ad04e631419b0dbf3b459c47e9a53d5df63f0 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:08:54 +0000 Subject: [PATCH 10/36] CLDC-2141 Update form submit flow (#2838) * Redirect after update * Fix world * Only check type if question is found * Remove empty line * Clear cache --- Dockerfile | 2 +- app/controllers/form_controller.rb | 36 +++++++++++++++++++++------ spec/requests/form_controller_spec.rb | 11 ++++++-- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 74faebfd8..814582011 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ RUN apk add --update --no-cache tzdata && \ # build-base: compilation tools for bundle # yarn: node package manager # postgresql-dev: postgres driver and libraries -RUN apk add --no-cache build-base=0.5-r3 busybox=1.36.1-r7 nodejs-current=20.8.1-r0 yarn=1.22.19-r0 postgresql13-dev=13.17-r0 git=2.40.3-r0 bash=5.2.15-r5 +RUN apk add --no-cache build-base=0.5-r3 busybox=1.36.1-r7 nodejs-current=20.8.1-r0 yarn=1.22.19-r0 postgresql13-dev=13.18-r0 git=2.40.3-r0 bash=5.2.15-r5 # Bundler version should be the same version as what the Gemfile.lock was bundled with RUN gem install bundler:2.3.14 --no-document diff --git a/app/controllers/form_controller.rb b/app/controllers/form_controller.rb index 70b6f892b..7ce63e609 100644 --- a/app/controllers/form_controller.rb +++ b/app/controllers/form_controller.rb @@ -5,6 +5,7 @@ class FormController < ApplicationController before_action :find_resource, only: %i[review] before_action :find_resource_by_named_id, except: %i[review] before_action :check_collection_period, only: %i[submit_form show_page] + before_action :set_cache_headers, only: [:show_page] def submit_form if @log @@ -37,8 +38,9 @@ class FormController < ApplicationController error_attributes = @log.errors.map(&:attribute) Rails.logger.info "User triggered validation(s) on: #{error_attributes.join(', ')}" @subsection = form.subsection_for_page(@page) - restore_error_field_values(@page&.questions) - render "form/page" + flash[:errors] = @log.errors + flash[:log_data] = responses_for_page + redirect_to send("#{@log.class.name.underscore}_#{@page.id}_path", @log, { referrer: request.params["referrer"], original_page_id: request.params["original_page_id"], related_question_ids: request.params["related_question_ids"] }) end else render_not_found @@ -84,6 +86,10 @@ class FormController < ApplicationController @questions = request.params["related_question_ids"].map { |id| @log.form.get_question(id, @log) } render "form/check_errors" else + if flash[:errors].present? + restore_previous_errors(flash[:errors]) + restore_error_field_values(flash[:log_data]) + end render "form/page" end else @@ -96,13 +102,21 @@ class FormController < ApplicationController private - def restore_error_field_values(questions) - return unless questions + def restore_error_field_values(previous_responses) + return unless previous_responses - questions.each do |question| - if question&.type == "date" && @log.attributes.key?(question.id) - @log[question.id] = @log.send("#{question.id}_was") - end + previous_responses_to_reset = previous_responses.reject do |key, _| + @log.form.get_question(key, @log)&.type == "date" + end + + @log.assign_attributes(previous_responses_to_reset) + end + + def restore_previous_errors(previous_errors) + return unless previous_errors + + previous_errors.each do |attribute, message| + @log.errors.add attribute, message.first end end @@ -431,4 +445,10 @@ private def updated_answer_from_check_errors_page? params["check_errors"] end + + def set_cache_headers + response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0" + response.headers["Pragma"] = "no-cache" + response.headers["Expires"] = "Mon, 01 Jan 1990 00:00:00 GMT" + end end diff --git a/spec/requests/form_controller_spec.rb b/spec/requests/form_controller_spec.rb index 13d711c20..e727d8acc 100644 --- a/spec/requests/form_controller_spec.rb +++ b/spec/requests/form_controller_spec.rb @@ -582,8 +582,9 @@ RSpec.describe FormController, type: :request do allow(Rails.logger).to receive(:info) end - it "re-renders the same page with errors if validation fails" do + it "redirects to the same page with errors if validation fails" do post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params + follow_redirect! expect(page).to have_content("There is a problem") expect(page).to have_content("Error: What is the tenant’s age?") end @@ -591,6 +592,8 @@ RSpec.describe FormController, type: :request do it "resets errors when fixed" do post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: valid_params + follow_redirect! + expect(page).not_to have_content("There is a problem") get "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}" expect(page).not_to have_content("There is a problem") end @@ -616,6 +619,7 @@ RSpec.describe FormController, type: :request do it "validates the date correctly" do post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params + follow_redirect! expect(page).to have_content("There is a problem") end end @@ -693,6 +697,7 @@ RSpec.describe FormController, type: :request do it "validates the date correctly" do post "/lettings-logs/#{lettings_log.id}/#{page_id.dasherize}", params: params + follow_redirect! expect(page).to have_content("There is a problem") end end @@ -713,6 +718,7 @@ RSpec.describe FormController, type: :request do it "validates the date correctly" do post "/sales-logs/#{sales_log.id}/#{page_id.dasherize}", params: params + follow_redirect! expect(page).to have_content("There is a problem") end end @@ -748,8 +754,9 @@ RSpec.describe FormController, type: :request do Timecop.unfreeze end - it "re-renders the same page with errors if validation fails" do + it "redirects the same page with errors if validation fails" do post "/lettings-logs/#{lettings_log.id}/managing-organisation", params: params + follow_redirect! expect(page).to have_content("There is a problem") expect(page).to have_content("Error: Which organisation manages this letting?") end From 4f51cf4da80554bfbf24ffbcc0de9786777fec94 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 08:18:19 +0000 Subject: [PATCH 12/36] Bump rails-html-sanitizer from 1.6.0 to 1.6.1 (#2847) Bumps [rails-html-sanitizer](https://github.com/rails/rails-html-sanitizer) from 1.6.0 to 1.6.1. - [Release notes](https://github.com/rails/rails-html-sanitizer/releases) - [Changelog](https://github.com/rails/rails-html-sanitizer/blob/main/CHANGELOG.md) - [Commits](https://github.com/rails/rails-html-sanitizer/compare/v1.6.0...v1.6.1) --- updated-dependencies: - dependency-name: rails-html-sanitizer dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1a98463b5..d2a5de05d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -246,7 +246,7 @@ GEM listen (3.9.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - loofah (2.22.0) + loofah (2.23.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -274,11 +274,11 @@ GEM net-smtp (0.5.0) net-protocol nio4r (2.7.3) - nokogiri (1.16.7-arm64-darwin) + nokogiri (1.16.8-arm64-darwin) racc (~> 1.4) - nokogiri (1.16.7-x86_64-darwin) + nokogiri (1.16.8-x86_64-darwin) racc (~> 1.4) - nokogiri (1.16.7-x86_64-linux) + nokogiri (1.16.8-x86_64-linux) racc (~> 1.4) notifications-ruby-client (6.0.0) jwt (>= 1.5, < 3) @@ -345,9 +345,9 @@ GEM activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.0) + rails-html-sanitizer (1.6.1) loofah (~> 2.21) - nokogiri (~> 1.14) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) rails_admin (3.1.3) activemodel-serializers-xml (>= 1.0) kaminari (>= 0.14, < 2.0) From a137999d294585a6b8cd1995556619326709da45 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Tue, 3 Dec 2024 08:19:59 +0000 Subject: [PATCH 13/36] Add addresses flow feature tests and update the flow a bit (#2805) --- .../lettings_log_variables.rb | 9 + .../derived_variables/sales_log_variables.rb | 9 + app/models/log.rb | 4 +- spec/features/lettings_log_spec.rb | 348 ++++++++++++++++++ spec/features/sales_log_spec.rb | 348 ++++++++++++++++++ 5 files changed, 717 insertions(+), 1 deletion(-) diff --git a/app/models/derived_variables/lettings_log_variables.rb b/app/models/derived_variables/lettings_log_variables.rb index 4692a0e6b..ced530b17 100644 --- a/app/models/derived_variables/lettings_log_variables.rb +++ b/app/models/derived_variables/lettings_log_variables.rb @@ -84,6 +84,15 @@ module DerivedVariables::LettingsLogVariables if uprn_known&.zero? self.uprn = nil + if uprn_known_was == 1 + self.address_line1 = nil + self.address_line2 = nil + self.town_or_city = nil + self.county = nil + self.postcode_known = nil + self.postcode_full = nil + self.la = nil + end end if uprn_known == 1 && uprn_confirmed&.zero? diff --git a/app/models/derived_variables/sales_log_variables.rb b/app/models/derived_variables/sales_log_variables.rb index 4f4c3105d..6e12ec488 100644 --- a/app/models/derived_variables/sales_log_variables.rb +++ b/app/models/derived_variables/sales_log_variables.rb @@ -53,6 +53,15 @@ module DerivedVariables::SalesLogVariables if uprn_known&.zero? self.uprn = nil + if uprn_known_was == 1 + self.address_line1 = nil + self.address_line2 = nil + self.town_or_city = nil + self.county = nil + self.pcodenk = nil + self.postcode_full = nil + self.la = nil + end end if uprn_known == 1 && uprn_confirmed&.zero? diff --git a/app/models/log.rb b/app/models/log.rb index dd4301550..bcbea9c92 100644 --- a/app/models/log.rb +++ b/app/models/log.rb @@ -126,9 +126,11 @@ class Log < ApplicationRecord end def address_options - return @address_options if @address_options + return @address_options if @address_options && @last_searched_address_string == address_string if [address_line1_input, postcode_full_input].all?(&:present?) + @last_searched_address_string = address_string + service = AddressClient.new(address_string) service.call if service.result.blank? || service.error.present? diff --git a/spec/features/lettings_log_spec.rb b/spec/features/lettings_log_spec.rb index efb7e7665..b10dfc6e5 100644 --- a/spec/features/lettings_log_spec.rb +++ b/spec/features/lettings_log_spec.rb @@ -729,5 +729,353 @@ RSpec.describe "Lettings Log Features" do expect(duplicate_log.duplicate_set_id).to be_nil end end + + context "when filling out address fields" do + let(:lettings_log) { create(:lettings_log, :setup_completed, assigned_to: user) } + + before do + body = { + results: [ + { + DPA: { + "POSTCODE": "AA1 1AA", + "POST_TOWN": "Bristol", + "ORGANISATION_NAME": "Some place", + }, + }, + ], + }.to_json + + WebMock.stub_request(:get, "https://api.os.uk/search/places/v1/uprn?dataset=DPA,LPI&key=OS_DATA_KEY&uprn=111") + .to_return(status: 200, body:, headers: {}) + + body = { results: [{ DPA: { UPRN: "111" } }] }.to_json + WebMock.stub_request(:get, "https://api.os.uk/search/places/v1/find?query=Address+line+1%2C+AA1+1AA&key=OS_DATA_KEY&maxresults=10&minmatch=0.4") + .to_return(status: 200, body:, headers: {}) + + WebMock.stub_request(:get, "https://api.postcodes.io/postcodes/AA11AA") + .to_return(status: 200, body: "{\"status\":200,\"result\":{\"postcode\":\"AA1 1AA\",\"admin_district\":\"Westminster\",\"codes\":{\"admin_district\":\"E09000033\"}}}", headers: {}) + + WebMock.stub_request(:get, "https://api.postcodes.io/postcodes/AA12AA") + .to_return(status: 200, body: "{\"status\":200,\"result\":{\"postcode\":\"AA1 2AA\",\"admin_district\":\"Wigan\",\"codes\":{\"admin_district\":\"E08000010\"}}}", headers: {}) + + body = { results: [] }.to_json + WebMock.stub_request(:get, "https://api.os.uk/search/places/v1/find?query=Address+line+1%2C+AA1+1AB&key=OS_DATA_KEY&maxresults=10&minmatch=0.4") + .to_return(status: 200, body:, headers: {}) + + visit("/lettings-logs/#{lettings_log.id}/uprn") + end + + context "and uprn is known and answered" do + before do + choose "Yes" + fill_in("lettings_log[uprn]", with: "111") + click_button("Save and continue") + end + + context "and uprn is confirmed" do + it "sets correct address fields" do + lettings_log.reload + expect(lettings_log.uprn_known).to eq(1) # yes + expect(lettings_log.uprn).to eq("111") + expect(lettings_log.uprn_confirmed).to eq(nil) + expect(lettings_log.uprn_selection).to eq(nil) + expect(lettings_log.postcode_known).to eq(1) + expect(lettings_log.postcode_full).to eq("AA1 1AA") + expect(lettings_log.address_line1).to eq("Some Place") + expect(lettings_log.address_line2).to eq(nil) + expect(lettings_log.town_or_city).to eq("Bristol") + expect(lettings_log.address_line1_input).to eq(nil) + expect(lettings_log.postcode_full_input).to eq(nil) + expect(lettings_log.address_search_value_check).to eq(nil) + expect(lettings_log.la).to eq("E09000033") + + choose "Yes" + click_button("Save and continue") + + lettings_log.reload + expect(lettings_log.uprn_known).to eq(1) # yes + expect(lettings_log.uprn).to eq("111") + expect(lettings_log.uprn_confirmed).to eq(1) # yes + expect(lettings_log.uprn_selection).to eq(nil) + expect(lettings_log.postcode_known).to eq(1) + expect(lettings_log.postcode_full).to eq("AA1 1AA") + expect(lettings_log.address_line1).to eq("Some Place") + expect(lettings_log.address_line2).to eq(nil) + expect(lettings_log.town_or_city).to eq("Bristol") + expect(lettings_log.address_line1_input).to eq(nil) + expect(lettings_log.postcode_full_input).to eq(nil) + expect(lettings_log.address_search_value_check).to eq(nil) + expect(lettings_log.la).to eq("E09000033") + end + + context "and changes to uprn not known" do + it "sets correct address fields" do + visit("/lettings-logs/#{lettings_log.id}/uprn") + + choose "No" + click_button("Save and continue") + + lettings_log.reload + expect(lettings_log.uprn_known).to eq(0) # no + expect(lettings_log.uprn).to eq(nil) + expect(lettings_log.uprn_confirmed).to eq(nil) + expect(lettings_log.uprn_selection).to eq(nil) + expect(lettings_log.postcode_known).to eq(nil) + expect(lettings_log.postcode_full).to eq(nil) + expect(lettings_log.address_line1).to eq(nil) + expect(lettings_log.address_line2).to eq(nil) + expect(lettings_log.town_or_city).to eq(nil) + expect(lettings_log.address_line1_input).to eq(nil) + expect(lettings_log.postcode_full_input).to eq(nil) + expect(lettings_log.address_search_value_check).to eq(nil) + expect(lettings_log.la).to eq(nil) + end + end + end + + context "and uprn is not confirmed" do + before do + choose "No, I want to search for the address instead" + click_button("Save and continue") + end + + it "sets correct address fields" do + lettings_log.reload + + expect(lettings_log.uprn_known).to eq(0) # no + expect(lettings_log.uprn).to eq(nil) + expect(lettings_log.uprn_confirmed).to eq(nil) + expect(lettings_log.uprn_selection).to eq(nil) + expect(lettings_log.postcode_known).to eq(nil) + expect(lettings_log.postcode_full).to eq(nil) + expect(lettings_log.address_line1).to eq(nil) + expect(lettings_log.address_line2).to eq(nil) + expect(lettings_log.town_or_city).to eq(nil) + expect(lettings_log.address_line1_input).to eq(nil) + expect(lettings_log.postcode_full_input).to eq(nil) + expect(lettings_log.address_search_value_check).to eq(nil) + expect(lettings_log.la).to eq(nil) + end + end + end + + context "and uprn is not known" do + before do + choose "No" + click_button("Save and continue") + end + + it "sets correct address fields" do + lettings_log.reload + expect(lettings_log.uprn_known).to eq(0) # no + expect(lettings_log.uprn).to eq(nil) + expect(lettings_log.uprn_confirmed).to eq(nil) + expect(lettings_log.uprn_selection).to eq(nil) + expect(lettings_log.postcode_known).to eq(nil) + expect(lettings_log.postcode_full).to eq(nil) + expect(lettings_log.address_line1).to eq(nil) + expect(lettings_log.address_line2).to eq(nil) + expect(lettings_log.town_or_city).to eq(nil) + expect(lettings_log.address_line1_input).to eq(nil) + expect(lettings_log.postcode_full_input).to eq(nil) + expect(lettings_log.address_search_value_check).to eq(nil) + expect(lettings_log.la).to eq(nil) + end + + context "and the address is not found" do + it "sets correct address fields" do + fill_in("lettings_log[address_line1_input]", with: "Address line 1") + fill_in("lettings_log[postcode_full_input]", with: "AA1 1AB") + click_button("Search") + + lettings_log.reload + expect(lettings_log.uprn_known).to eq(0) # no + expect(lettings_log.uprn).to eq(nil) + expect(lettings_log.uprn_confirmed).to eq(nil) + expect(lettings_log.uprn_selection).to eq(nil) + expect(lettings_log.postcode_known).to eq(nil) + expect(lettings_log.postcode_full).to eq(nil) + expect(lettings_log.address_line1).to eq(nil) + expect(lettings_log.address_line2).to eq(nil) + expect(lettings_log.town_or_city).to eq(nil) + expect(lettings_log.address_line1_input).to eq("Address line 1") + expect(lettings_log.postcode_full_input).to eq("AA1 1AB") + expect(lettings_log.address_search_value_check).to eq(nil) + expect(lettings_log.la).to eq(nil) + + click_button("Confirm and continue") + + lettings_log.reload + expect(lettings_log.uprn_known).to eq(0) # no + expect(lettings_log.uprn).to eq(nil) + expect(lettings_log.uprn_confirmed).to eq(nil) + expect(lettings_log.uprn_selection).to eq(nil) + expect(lettings_log.postcode_known).to eq(nil) + expect(lettings_log.postcode_full).to eq(nil) + expect(lettings_log.address_line1).to eq(nil) + expect(lettings_log.address_line2).to eq(nil) + expect(lettings_log.town_or_city).to eq(nil) + expect(lettings_log.address_line1_input).to eq("Address line 1") + expect(lettings_log.postcode_full_input).to eq("AA1 1AB") + expect(lettings_log.address_search_value_check).to eq(0) + expect(lettings_log.la).to eq(nil) + end + end + + context "and address is found, re-searched and not found" do + before do + fill_in("lettings_log[address_line1_input]", with: "Address line 1") + fill_in("lettings_log[postcode_full_input]", with: "AA1 1AA") + click_button("Search") + visit("/lettings-logs/#{lettings_log.id}/address-matcher") + + fill_in("lettings_log[address_line1_input]", with: "Address line 1") + fill_in("lettings_log[postcode_full_input]", with: "AA1 1AB") + click_button("Search") + end + + it "routes to the correct page" do + expect(page).to have_current_path("/lettings-logs/#{lettings_log.id}/no-address-found") + end + end + + context "and the user selects 'address_not_listed'" do + before do + fill_in("lettings_log[address_line1_input]", with: "Address line 1") + fill_in("lettings_log[postcode_full_input]", with: "AA1 1AA") + click_button("Search") + choose "The address is not listed, I want to enter the address manually" + click_button("Save and continue") + end + + it "sets correct address fields" do + lettings_log.reload + expect(lettings_log.uprn_known).to eq(0) # no + expect(lettings_log.uprn).to eq(nil) + expect(lettings_log.uprn_confirmed).to eq(nil) + expect(lettings_log.uprn_selection).to eq("uprn_not_listed") + expect(lettings_log.postcode_known).to eq(1) + expect(lettings_log.postcode_full).to eq("AA1 1AA") + expect(lettings_log.address_line1).to eq("Address line 1") + expect(lettings_log.address_line2).to eq(nil) + expect(lettings_log.town_or_city).to eq(nil) + expect(lettings_log.address_line1_input).to eq("Address line 1") + expect(lettings_log.postcode_full_input).to eq("AA1 1AA") + expect(lettings_log.address_search_value_check).to eq(nil) + expect(lettings_log.la).to eq("E09000033") + end + + context "and the user enters a new address manually" do + context "without changing a valid postcode" do + before do + fill_in("lettings_log[town_or_city]", with: "Town") + click_button("Save and continue") + end + + it "sets correct address fields" do + lettings_log.reload + expect(lettings_log.uprn_known).to eq(0) # no + expect(lettings_log.uprn).to eq(nil) + expect(lettings_log.uprn_confirmed).to eq(nil) + expect(lettings_log.uprn_selection).to eq("uprn_not_listed") + expect(lettings_log.postcode_known).to eq(1) + expect(lettings_log.postcode_full).to eq("AA1 1AA") + expect(lettings_log.address_line1).to eq("Address line 1") + expect(lettings_log.address_line2).to eq("") + expect(lettings_log.town_or_city).to eq("Town") + expect(lettings_log.address_line1_input).to eq("Address line 1") + expect(lettings_log.postcode_full_input).to eq("AA1 1AA") + expect(lettings_log.address_search_value_check).to eq(nil) + expect(lettings_log.la).to eq("E09000033") + end + end + + context "with changing the postcode" do + before do + fill_in("lettings_log[town_or_city]", with: "Town") + fill_in("lettings_log[postcode_full]", with: "AA12AA") + click_button("Save and continue") + end + + it "sets correct address fields" do + lettings_log.reload + expect(lettings_log.uprn_known).to eq(0) # no + expect(lettings_log.uprn).to eq(nil) + expect(lettings_log.uprn_confirmed).to eq(nil) + expect(lettings_log.uprn_selection).to eq("uprn_not_listed") + expect(lettings_log.postcode_known).to eq(1) + expect(lettings_log.postcode_full).to eq("AA1 2AA") + expect(lettings_log.address_line1).to eq("Address line 1") + expect(lettings_log.address_line2).to eq("") + expect(lettings_log.town_or_city).to eq("Town") + expect(lettings_log.address_line1_input).to eq("Address line 1") + expect(lettings_log.postcode_full_input).to eq("AA1 1AA") + expect(lettings_log.address_search_value_check).to eq(nil) + expect(lettings_log.la).to eq("E08000010") + end + end + end + end + + context "and the user selects 'address_not_listed' and then changes their mind and selects an address" do + before do + fill_in("lettings_log[address_line1_input]", with: "Address line 1") + fill_in("lettings_log[postcode_full_input]", with: "AA1 1AA") + click_button("Search") + choose "The address is not listed, I want to enter the address manually" + click_button("Save and continue") + + visit("/lettings-logs/#{lettings_log.id}/uprn-selection") + choose("lettings-log-uprn-selection-111-field", allow_label_click: true) + click_button("Save and continue") + end + + it "sets correct address fields" do + lettings_log.reload + expect(lettings_log.uprn_known).to eq(1) + expect(lettings_log.uprn).to eq("111") + expect(lettings_log.uprn_confirmed).to eq(1) + expect(lettings_log.uprn_selection).to eq(nil) + expect(lettings_log.postcode_known).to eq(1) + expect(lettings_log.postcode_full).to eq("AA1 1AA") + expect(lettings_log.address_line1).to eq("Some Place") + expect(lettings_log.address_line2).to eq(nil) + expect(lettings_log.town_or_city).to eq("Bristol") + expect(lettings_log.address_line1_input).to eq("Address line 1") + expect(lettings_log.postcode_full_input).to eq("AA1 1AA") + expect(lettings_log.address_search_value_check).to eq(nil) + expect(lettings_log.la).to eq("E09000033") + end + end + + context "and possible addresses found and selected" do + before do + fill_in("lettings_log[address_line1_input]", with: "Address line 1") + fill_in("lettings_log[postcode_full_input]", with: "AA1 1AA") + click_button("Search") + choose("lettings-log-uprn-selection-111-field", allow_label_click: true) + click_button("Save and continue") + end + + it "sets correct address fields" do + lettings_log.reload + expect(lettings_log.uprn_known).to eq(1) + expect(lettings_log.uprn).to eq("111") + expect(lettings_log.uprn_confirmed).to eq(1) + expect(lettings_log.uprn_selection).to eq(nil) + expect(lettings_log.postcode_known).to eq(1) + expect(lettings_log.postcode_full).to eq("AA1 1AA") + expect(lettings_log.address_line1).to eq("Some Place") + expect(lettings_log.address_line2).to eq(nil) + expect(lettings_log.town_or_city).to eq("Bristol") + expect(lettings_log.address_line1_input).to eq("Address line 1") + expect(lettings_log.postcode_full_input).to eq("AA1 1AA") + expect(lettings_log.address_search_value_check).to eq(nil) + expect(lettings_log.la).to eq("E09000033") + end + end + end + end end end diff --git a/spec/features/sales_log_spec.rb b/spec/features/sales_log_spec.rb index 879f2b5c8..d418bcb37 100644 --- a/spec/features/sales_log_spec.rb +++ b/spec/features/sales_log_spec.rb @@ -310,6 +310,354 @@ RSpec.describe "Sales Log Features" do expect(page).to have_current_path("/sales-logs/bulk-uploads") end end + + context "when filling out address fields" do + let(:sales_log) { create(:sales_log, :shared_ownership_setup_complete, assigned_to: user) } + + before do + body = { + results: [ + { + DPA: { + "POSTCODE": "AA1 1AA", + "POST_TOWN": "Bristol", + "ORGANISATION_NAME": "Some place", + }, + }, + ], + }.to_json + + WebMock.stub_request(:get, "https://api.os.uk/search/places/v1/uprn?dataset=DPA,LPI&key=OS_DATA_KEY&uprn=111") + .to_return(status: 200, body:, headers: {}) + + body = { results: [{ DPA: { UPRN: "111" } }] }.to_json + WebMock.stub_request(:get, "https://api.os.uk/search/places/v1/find?query=Address+line+1%2C+AA1+1AA&key=OS_DATA_KEY&maxresults=10&minmatch=0.4") + .to_return(status: 200, body:, headers: {}) + + WebMock.stub_request(:get, "https://api.postcodes.io/postcodes/AA11AA") + .to_return(status: 200, body: "{\"status\":200,\"result\":{\"postcode\":\"AA1 1AA\",\"admin_district\":\"Westminster\",\"codes\":{\"admin_district\":\"E09000033\"}}}", headers: {}) + + WebMock.stub_request(:get, "https://api.postcodes.io/postcodes/AA12AA") + .to_return(status: 200, body: "{\"status\":200,\"result\":{\"postcode\":\"AA1 2AA\",\"admin_district\":\"Wigan\",\"codes\":{\"admin_district\":\"E08000010\"}}}", headers: {}) + + body = { results: [] }.to_json + WebMock.stub_request(:get, "https://api.os.uk/search/places/v1/find?query=Address+line+1%2C+AA1+1AB&key=OS_DATA_KEY&maxresults=10&minmatch=0.4") + .to_return(status: 200, body:, headers: {}) + + visit("/sales-logs/#{sales_log.id}/uprn") + end + + context "and uprn is known and answered" do + before do + choose "Yes" + fill_in("sales_log[uprn]", with: "111") + click_button("Save and continue") + end + + context "and uprn is confirmed" do + it "sets correct address fields" do + sales_log.reload + expect(sales_log.uprn_known).to eq(1) # yes + expect(sales_log.uprn).to eq("111") + expect(sales_log.uprn_confirmed).to eq(nil) + expect(sales_log.uprn_selection).to eq(nil) + expect(sales_log.pcodenk).to eq(0) + expect(sales_log.postcode_full).to eq("AA1 1AA") + expect(sales_log.address_line1).to eq("Some Place") + expect(sales_log.address_line2).to eq(nil) + expect(sales_log.town_or_city).to eq("Bristol") + expect(sales_log.address_line1_input).to eq(nil) + expect(sales_log.postcode_full_input).to eq(nil) + expect(sales_log.address_search_value_check).to eq(nil) + expect(sales_log.la).to eq("E09000033") + + choose "Yes" + click_button("Save and continue") + + sales_log.reload + expect(sales_log.uprn_known).to eq(1) # yes + expect(sales_log.uprn).to eq("111") + expect(sales_log.uprn_confirmed).to eq(1) # yes + expect(sales_log.uprn_selection).to eq(nil) + expect(sales_log.pcodenk).to eq(0) + expect(sales_log.postcode_full).to eq("AA1 1AA") + expect(sales_log.address_line1).to eq("Some Place") + expect(sales_log.address_line2).to eq(nil) + expect(sales_log.town_or_city).to eq("Bristol") + expect(sales_log.address_line1_input).to eq(nil) + expect(sales_log.postcode_full_input).to eq(nil) + expect(sales_log.address_search_value_check).to eq(nil) + expect(sales_log.la).to eq("E09000033") + end + + context "and changes to uprn not known" do + it "sets correct address fields" do + visit("/sales-logs/#{sales_log.id}/uprn") + + choose "No" + click_button("Save and continue") + + sales_log.reload + expect(sales_log.uprn_known).to eq(0) # no + expect(sales_log.uprn).to eq(nil) + expect(sales_log.uprn_confirmed).to eq(nil) + expect(sales_log.uprn_selection).to eq(nil) + expect(sales_log.pcodenk).to eq(nil) + expect(sales_log.postcode_full).to eq(nil) + expect(sales_log.address_line1).to eq(nil) + expect(sales_log.address_line2).to eq(nil) + expect(sales_log.town_or_city).to eq(nil) + expect(sales_log.address_line1_input).to eq(nil) + expect(sales_log.postcode_full_input).to eq(nil) + expect(sales_log.address_search_value_check).to eq(nil) + expect(sales_log.la).to eq(nil) + end + end + end + + context "and uprn is not confirmed" do + before do + choose "No, I want to search for the address instead" + click_button("Save and continue") + end + + it "sets correct address fields" do + sales_log.reload + + expect(sales_log.uprn_known).to eq(0) # no + expect(sales_log.uprn).to eq(nil) + expect(sales_log.uprn_confirmed).to eq(nil) + expect(sales_log.uprn_selection).to eq(nil) + expect(sales_log.pcodenk).to eq(nil) + expect(sales_log.postcode_full).to eq(nil) + expect(sales_log.address_line1).to eq(nil) + expect(sales_log.address_line2).to eq(nil) + expect(sales_log.town_or_city).to eq(nil) + expect(sales_log.address_line1_input).to eq(nil) + expect(sales_log.postcode_full_input).to eq(nil) + expect(sales_log.address_search_value_check).to eq(nil) + expect(sales_log.la).to eq(nil) + end + end + end + + context "and uprn is not known" do + before do + choose "No" + click_button("Save and continue") + end + + it "sets correct address fields" do + sales_log.reload + expect(sales_log.uprn_known).to eq(0) # no + expect(sales_log.uprn).to eq(nil) + expect(sales_log.uprn_confirmed).to eq(nil) + expect(sales_log.uprn_selection).to eq(nil) + expect(sales_log.pcodenk).to eq(nil) + expect(sales_log.postcode_full).to eq(nil) + expect(sales_log.address_line1).to eq(nil) + expect(sales_log.address_line2).to eq(nil) + expect(sales_log.town_or_city).to eq(nil) + expect(sales_log.address_line1_input).to eq(nil) + expect(sales_log.postcode_full_input).to eq(nil) + expect(sales_log.address_search_value_check).to eq(nil) + expect(sales_log.la).to eq(nil) + end + + context "and the address is not found" do + it "sets correct address fields" do + fill_in("sales_log[address_line1_input]", with: "Address line 1") + fill_in("sales_log[postcode_full_input]", with: "AA1 1AB") + click_button("Search") + + sales_log.reload + expect(sales_log.uprn_known).to eq(0) # no + expect(sales_log.uprn).to eq(nil) + expect(sales_log.uprn_confirmed).to eq(nil) + expect(sales_log.uprn_selection).to eq(nil) + expect(sales_log.pcodenk).to eq(nil) + expect(sales_log.postcode_full).to eq(nil) + expect(sales_log.address_line1).to eq(nil) + expect(sales_log.address_line2).to eq(nil) + expect(sales_log.town_or_city).to eq(nil) + expect(sales_log.address_line1_input).to eq("Address line 1") + expect(sales_log.postcode_full_input).to eq("AA1 1AB") + expect(sales_log.address_search_value_check).to eq(nil) + expect(sales_log.la).to eq(nil) + + click_button("Confirm and continue") + + sales_log.reload + expect(sales_log.uprn_known).to eq(0) # no + expect(sales_log.uprn).to eq(nil) + expect(sales_log.uprn_confirmed).to eq(nil) + expect(sales_log.uprn_selection).to eq(nil) + expect(sales_log.pcodenk).to eq(nil) + expect(sales_log.postcode_full).to eq(nil) + expect(sales_log.address_line1).to eq(nil) + expect(sales_log.address_line2).to eq(nil) + expect(sales_log.town_or_city).to eq(nil) + expect(sales_log.address_line1_input).to eq("Address line 1") + expect(sales_log.postcode_full_input).to eq("AA1 1AB") + expect(sales_log.address_search_value_check).to eq(0) + expect(sales_log.la).to eq(nil) + end + end + + context "and address is found, re-searched and not found" do + before do + fill_in("sales_log[address_line1_input]", with: "Address line 1") + fill_in("sales_log[postcode_full_input]", with: "AA1 1AA") + click_button("Search") + visit("/sales-logs/#{sales_log.id}/address-matcher") + + fill_in("sales_log[address_line1_input]", with: "Address line 1") + fill_in("sales_log[postcode_full_input]", with: "AA1 1AB") + click_button("Search") + end + + it "routes to the correct page" do + expect(page).to have_current_path("/sales-logs/#{sales_log.id}/no-address-found") + end + end + + context "and the user selects 'address_not_listed'" do + before do + fill_in("sales_log[address_line1_input]", with: "Address line 1") + fill_in("sales_log[postcode_full_input]", with: "AA1 1AA") + click_button("Search") + choose "The address is not listed, I want to enter the address manually" + click_button("Save and continue") + end + + it "sets correct address fields" do + sales_log.reload + expect(sales_log.uprn_known).to eq(0) # no + expect(sales_log.uprn).to eq(nil) + expect(sales_log.uprn_confirmed).to eq(nil) + expect(sales_log.uprn_selection).to eq("uprn_not_listed") + expect(sales_log.pcodenk).to eq(0) + expect(sales_log.postcode_full).to eq("AA1 1AA") + expect(sales_log.address_line1).to eq("Address line 1") + expect(sales_log.address_line2).to eq(nil) + expect(sales_log.town_or_city).to eq(nil) + expect(sales_log.address_line1_input).to eq("Address line 1") + expect(sales_log.postcode_full_input).to eq("AA1 1AA") + expect(sales_log.address_search_value_check).to eq(nil) + expect(sales_log.la).to eq("E09000033") + end + + context "and the user enters a new address manually" do + context "without changing a valid postcode" do + before do + fill_in("sales_log[town_or_city]", with: "Town") + click_button("Save and continue") + end + + it "sets correct address fields" do + sales_log.reload + expect(sales_log.uprn_known).to eq(0) # no + expect(sales_log.uprn).to eq(nil) + expect(sales_log.uprn_confirmed).to eq(nil) + expect(sales_log.uprn_selection).to eq("uprn_not_listed") + expect(sales_log.pcodenk).to eq(0) + expect(sales_log.postcode_full).to eq("AA1 1AA") + expect(sales_log.address_line1).to eq("Address line 1") + expect(sales_log.address_line2).to eq("") + expect(sales_log.town_or_city).to eq("Town") + expect(sales_log.address_line1_input).to eq("Address line 1") + expect(sales_log.postcode_full_input).to eq("AA1 1AA") + expect(sales_log.address_search_value_check).to eq(nil) + expect(sales_log.la).to eq("E09000033") + end + end + + context "with changing the postcode" do + before do + fill_in("sales_log[town_or_city]", with: "Town") + fill_in("sales_log[postcode_full]", with: "AA12AA") + click_button("Save and continue") + end + + it "sets correct address fields" do + sales_log.reload + expect(sales_log.uprn_known).to eq(0) # no + expect(sales_log.uprn).to eq(nil) + expect(sales_log.uprn_confirmed).to eq(nil) + expect(sales_log.uprn_selection).to eq("uprn_not_listed") + expect(sales_log.pcodenk).to eq(0) + expect(sales_log.postcode_full).to eq("AA1 2AA") + expect(sales_log.address_line1).to eq("Address line 1") + expect(sales_log.address_line2).to eq("") + expect(sales_log.town_or_city).to eq("Town") + expect(sales_log.address_line1_input).to eq("Address line 1") + expect(sales_log.postcode_full_input).to eq("AA1 1AA") + expect(sales_log.address_search_value_check).to eq(nil) + expect(sales_log.la).to eq("E08000010") + end + end + end + end + + context "and the user selects 'address_not_listed' and then changes their mind and selects an address" do + before do + fill_in("sales_log[address_line1_input]", with: "Address line 1") + fill_in("sales_log[postcode_full_input]", with: "AA1 1AA") + click_button("Search") + choose "The address is not listed, I want to enter the address manually" + click_button("Save and continue") + + visit("/sales-logs/#{sales_log.id}/uprn-selection") + choose("sales-log-uprn-selection-111-field", allow_label_click: true) + click_button("Save and continue") + end + + it "sets correct address fields" do + sales_log.reload + expect(sales_log.uprn_known).to eq(1) + expect(sales_log.uprn).to eq("111") + expect(sales_log.uprn_confirmed).to eq(1) + expect(sales_log.uprn_selection).to eq(nil) + expect(sales_log.pcodenk).to eq(0) + expect(sales_log.postcode_full).to eq("AA1 1AA") + expect(sales_log.address_line1).to eq("Some Place") + expect(sales_log.address_line2).to eq(nil) + expect(sales_log.town_or_city).to eq("Bristol") + expect(sales_log.address_line1_input).to eq("Address line 1") + expect(sales_log.postcode_full_input).to eq("AA1 1AA") + expect(sales_log.address_search_value_check).to eq(nil) + expect(sales_log.la).to eq("E09000033") + end + end + + context "and possible addresses found and selected" do + before do + fill_in("sales_log[address_line1_input]", with: "Address line 1") + fill_in("sales_log[postcode_full_input]", with: "AA1 1AA") + click_button("Search") + choose("sales-log-uprn-selection-111-field", allow_label_click: true) + click_button("Save and continue") + end + + it "sets correct address fields" do + sales_log.reload + expect(sales_log.uprn_known).to eq(1) + expect(sales_log.uprn).to eq("111") + expect(sales_log.uprn_confirmed).to eq(1) + expect(sales_log.uprn_selection).to eq(nil) + expect(sales_log.pcodenk).to eq(0) + expect(sales_log.postcode_full).to eq("AA1 1AA") + expect(sales_log.address_line1).to eq("Some Place") + expect(sales_log.address_line2).to eq(nil) + expect(sales_log.town_or_city).to eq("Bristol") + expect(sales_log.address_line1_input).to eq("Address line 1") + expect(sales_log.postcode_full_input).to eq("AA1 1AA") + expect(sales_log.address_search_value_check).to eq(nil) + expect(sales_log.la).to eq("E09000033") + end + end + end + end end context "when a log becomes a duplicate" do From 84177349f0d13b12463a07cfdf953c37d42df075 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:21:09 +0000 Subject: [PATCH 14/36] Refactor exports to have years (#2849) * Refactor exports to have years * Update existing exports * Fix flaky test * Remove a comment --- app/models/export.rb | 3 ++ app/services/exports/export_service.rb | 10 ++--- .../exports/lettings_log_export_service.rb | 22 +++++----- .../exports/organisation_export_service.rb | 12 +++--- app/services/exports/user_export_service.rb | 12 +++--- app/services/exports/xml_export_service.rb | 20 ++++----- .../20241204100518_add_year_to_export.rb | 5 +++ db/schema.rb | 3 +- lib/tasks/data_export.rake | 6 +-- lib/tasks/set_export_collection_years.rake | 8 ++++ spec/helpers/tab_nav_helper_spec.rb | 2 +- spec/lib/tasks/data_export_spec.rb | 6 +-- .../tasks/set_export_collection_years_spec.rb | 41 +++++++++++++++++++ .../lettings_log_export_service_spec.rb | 18 ++++---- 14 files changed, 111 insertions(+), 57 deletions(-) create mode 100644 db/migrate/20241204100518_add_year_to_export.rb create mode 100644 lib/tasks/set_export_collection_years.rake create mode 100644 spec/lib/tasks/set_export_collection_years_spec.rb diff --git a/app/models/export.rb b/app/models/export.rb index 2ee04fe3d..27e574c72 100644 --- a/app/models/export.rb +++ b/app/models/export.rb @@ -1,2 +1,5 @@ class Export < ApplicationRecord + scope :lettings, -> { where(collection: "lettings") } + scope :organisations, -> { where(collection: "organisations") } + scope :users, -> { where(collection: "users") } end diff --git a/app/services/exports/export_service.rb b/app/services/exports/export_service.rb index bf98a4edf..40c10055b 100644 --- a/app/services/exports/export_service.rb +++ b/app/services/exports/export_service.rb @@ -7,7 +7,7 @@ module Exports @logger = logger end - def export_xml(full_update: false, collection: nil) + def export_xml(full_update: false, collection: nil, year: nil) start_time = Time.zone.now daily_run_number = get_daily_run_number lettings_archives_for_manifest = {} @@ -21,12 +21,12 @@ module Exports when "organisations" organisations_archives_for_manifest = get_organisation_archives(start_time, full_update) else - lettings_archives_for_manifest = get_lettings_archives(start_time, full_update, collection) + lettings_archives_for_manifest = get_lettings_archives(start_time, full_update, year) end else users_archives_for_manifest = get_user_archives(start_time, full_update) organisations_archives_for_manifest = get_organisation_archives(start_time, full_update) - lettings_archives_for_manifest = get_lettings_archives(start_time, full_update, collection) + lettings_archives_for_manifest = get_lettings_archives(start_time, full_update, year) end write_master_manifest(daily_run_number, lettings_archives_for_manifest.merge(users_archives_for_manifest).merge(organisations_archives_for_manifest)) @@ -70,9 +70,9 @@ module Exports organisations_export_service.export_xml_organisations(full_update:) end - def get_lettings_archives(start_time, full_update, collection) + def get_lettings_archives(start_time, full_update, collection_year) lettings_export_service = Exports::LettingsLogExportService.new(@storage_service, start_time) - lettings_export_service.export_xml_lettings_logs(full_update:, collection_year: collection) + lettings_export_service.export_xml_lettings_logs(full_update:, collection_year:) end end end diff --git a/app/services/exports/lettings_log_export_service.rb b/app/services/exports/lettings_log_export_service.rb index 97f495e0c..a8877eac3 100644 --- a/app/services/exports/lettings_log_export_service.rb +++ b/app/services/exports/lettings_log_export_service.rb @@ -5,11 +5,11 @@ module Exports def export_xml_lettings_logs(full_update: false, collection_year: nil) archives_for_manifest = {} - collection_years_to_export(collection_year).each do |collection| - recent_export = Export.where(collection:).order("started_at").last - base_number = Export.where(empty_export: false, collection:).maximum(:base_number) || 1 - export = build_export_run(collection, base_number, full_update) - archives = write_export_archive(export, collection, recent_export, full_update) + collection_years_to_export(collection_year).each do |year| + recent_export = Export.lettings.where(year:).order("started_at").last + base_number = Export.lettings.where(empty_export: false, year:).maximum(:base_number) || 1 + export = build_export_run("lettings", base_number, full_update, year) + archives = write_export_archive(export, year, recent_export, full_update) archives_for_manifest.merge!(archives) @@ -22,21 +22,21 @@ module Exports private - def get_archive_name(collection, base_number, increment) - return unless collection + def get_archive_name(year, base_number, increment) + return unless year base_number_str = "f#{base_number.to_s.rjust(4, '0')}" increment_str = "inc#{increment.to_s.rjust(4, '0')}" - "core_#{collection}_#{collection + 1}_apr_mar_#{base_number_str}_#{increment_str}".downcase + "core_#{year}_#{year + 1}_apr_mar_#{base_number_str}_#{increment_str}".downcase end - def retrieve_resources(recent_export, full_update, collection) + def retrieve_resources(recent_export, full_update, year) if !full_update && recent_export params = { from: recent_export.started_at, to: @start_time } - LettingsLog.exportable.where("(updated_at >= :from AND updated_at <= :to) OR (values_updated_at IS NOT NULL AND values_updated_at >= :from AND values_updated_at <= :to)", params).filter_by_year(collection) + LettingsLog.exportable.where("(updated_at >= :from AND updated_at <= :to) OR (values_updated_at IS NOT NULL AND values_updated_at >= :from AND values_updated_at <= :to)", params).filter_by_year(year) else params = { to: @start_time } - LettingsLog.exportable.where("updated_at <= :to", params).filter_by_year(collection) + LettingsLog.exportable.where("updated_at <= :to", params).filter_by_year(year) end end diff --git a/app/services/exports/organisation_export_service.rb b/app/services/exports/organisation_export_service.rb index 71eccf60a..2b2da71c8 100644 --- a/app/services/exports/organisation_export_service.rb +++ b/app/services/exports/organisation_export_service.rb @@ -5,9 +5,9 @@ module Exports def export_xml_organisations(full_update: false) collection = "organisations" - recent_export = Export.where(collection:).order("started_at").last + recent_export = Export.organisations.order("started_at").last - base_number = Export.where(empty_export: false, collection:).maximum(:base_number) || 1 + base_number = Export.organisations.where(empty_export: false).maximum(:base_number) || 1 export = build_export_run(collection, base_number, full_update) archives_for_manifest = write_export_archive(export, collection, recent_export, full_update) @@ -19,15 +19,13 @@ module Exports private - def get_archive_name(collection, base_number, increment) - return unless collection - + def get_archive_name(_year, base_number, increment) base_number_str = "f#{base_number.to_s.rjust(4, '0')}" increment_str = "inc#{increment.to_s.rjust(4, '0')}" - "#{collection}_2024_2025_apr_mar_#{base_number_str}_#{increment_str}".downcase + "organisations_2024_2025_apr_mar_#{base_number_str}_#{increment_str}".downcase end - def retrieve_resources(recent_export, full_update, _collection) + def retrieve_resources(recent_export, full_update, _year) if !full_update && recent_export params = { from: recent_export.started_at, to: @start_time } Organisation.where("(updated_at >= :from AND updated_at <= :to)", params) diff --git a/app/services/exports/user_export_service.rb b/app/services/exports/user_export_service.rb index 907a1cc86..aaa30c424 100644 --- a/app/services/exports/user_export_service.rb +++ b/app/services/exports/user_export_service.rb @@ -5,9 +5,9 @@ module Exports def export_xml_users(full_update: false) collection = "users" - recent_export = Export.where(collection:).order("started_at").last + recent_export = Export.users.order("started_at").last - base_number = Export.where(empty_export: false, collection:).maximum(:base_number) || 1 + base_number = Export.users.where(empty_export: false).maximum(:base_number) || 1 export = build_export_run(collection, base_number, full_update) archives_for_manifest = write_export_archive(export, collection, recent_export, full_update) @@ -19,15 +19,13 @@ module Exports private - def get_archive_name(collection, base_number, increment) - return unless collection - + def get_archive_name(_year, base_number, increment) base_number_str = "f#{base_number.to_s.rjust(4, '0')}" increment_str = "inc#{increment.to_s.rjust(4, '0')}" - "#{collection}_2024_2025_apr_mar_#{base_number_str}_#{increment_str}".downcase + "users_2024_2025_apr_mar_#{base_number_str}_#{increment_str}".downcase end - def retrieve_resources(recent_export, full_update, _collection) + def retrieve_resources(recent_export, full_update, _year) if !full_update && recent_export params = { from: recent_export.started_at, to: @start_time } User.where("(updated_at >= :from AND updated_at <= :to)", params) diff --git a/app/services/exports/xml_export_service.rb b/app/services/exports/xml_export_service.rb index 009a1b306..d499dd518 100644 --- a/app/services/exports/xml_export_service.rb +++ b/app/services/exports/xml_export_service.rb @@ -11,9 +11,9 @@ module Exports private - def build_export_run(collection, base_number, full_update) - @logger.info("Building export run for #{collection}") - previous_exports_with_data = Export.where(collection:, empty_export: false) + def build_export_run(collection, base_number, full_update, year = nil) + @logger.info("Building export run for #{[collection, year].join(' ')}") + previous_exports_with_data = Export.where(collection:, year:, empty_export: false) increment_number = previous_exports_with_data.where(base_number:).maximum(:increment_number) || 1 @@ -25,16 +25,16 @@ module Exports end if previous_exports_with_data.empty? - return Export.new(collection:, base_number:, started_at: @start_time) + return Export.new(collection:, year:, base_number:, started_at: @start_time) end - Export.new(collection:, started_at: @start_time, base_number:, increment_number:) + Export.new(collection:, year:, started_at: @start_time, base_number:, increment_number:) end - def write_export_archive(export, collection, recent_export, full_update) - archive = get_archive_name(collection, export.base_number, export.increment_number) # archive name would be the same for all logs because they're already filtered by year (?) + def write_export_archive(export, year, recent_export, full_update) + archive = get_archive_name(year, export.base_number, export.increment_number) - initial_count = retrieve_resources(recent_export, full_update, collection).count + initial_count = retrieve_resources(recent_export, full_update, year).count @logger.info("Creating #{archive} - #{initial_count} resources") return {} if initial_count.zero? @@ -46,12 +46,12 @@ module Exports loop do slice = if last_processed_marker.present? - retrieve_resources(recent_export, full_update, collection) + retrieve_resources(recent_export, full_update, year) .where("created_at > ?", last_processed_marker) .order(:created_at) .limit(MAX_XML_RECORDS).to_a else - retrieve_resources(recent_export, full_update, collection) + retrieve_resources(recent_export, full_update, year) .order(:created_at) .limit(MAX_XML_RECORDS).to_a end diff --git a/db/migrate/20241204100518_add_year_to_export.rb b/db/migrate/20241204100518_add_year_to_export.rb new file mode 100644 index 000000000..784754888 --- /dev/null +++ b/db/migrate/20241204100518_add_year_to_export.rb @@ -0,0 +1,5 @@ +class AddYearToExport < ActiveRecord::Migration[7.0] + def change + add_column :exports, :year, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 0cdc15e9f..d31a54da2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_11_22_154743) do +ActiveRecord::Schema[7.0].define(version: 2024_12_04_100518) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -113,6 +113,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_11_22_154743) do t.integer "increment_number", default: 1, null: false t.boolean "empty_export", default: false, null: false t.string "collection" + t.integer "year" end create_table "la_rent_ranges", force: :cascade do |t| diff --git a/lib/tasks/data_export.rake b/lib/tasks/data_export.rake index 7a9a90bc8..43ef06374 100644 --- a/lib/tasks/data_export.rake +++ b/lib/tasks/data_export.rake @@ -7,13 +7,13 @@ namespace :core do end desc "Export all data XMLs for import into Central Data System (CDS)" - task :full_data_export_xml, %i[collection] => :environment do |_task, args| + task :full_data_export_xml, %i[collection year] => :environment do |_task, args| collection = args[:collection].presence - collection = collection.to_i if collection.present? && collection.scan(/\D/).empty? + year = args[:year]&.to_i storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["EXPORT_BUCKET"]) export_service = Exports::ExportService.new(storage_service) - export_service.export_xml(full_update: true, collection:) + export_service.export_xml(full_update: true, collection:, year:) end end diff --git a/lib/tasks/set_export_collection_years.rake b/lib/tasks/set_export_collection_years.rake new file mode 100644 index 000000000..badaab0eb --- /dev/null +++ b/lib/tasks/set_export_collection_years.rake @@ -0,0 +1,8 @@ +desc "Set export collection years for lettings exports" +task set_export_collection_years: :environment do + Export.where(collection: %w[2022 2023 2024 2025]).find_each do |export| + export.year = export.collection.to_i + export.collection = "lettings" + export.save! + end +end diff --git a/spec/helpers/tab_nav_helper_spec.rb b/spec/helpers/tab_nav_helper_spec.rb index 89f867775..bd29f4c46 100644 --- a/spec/helpers/tab_nav_helper_spec.rb +++ b/spec/helpers/tab_nav_helper_spec.rb @@ -9,7 +9,7 @@ RSpec.describe TabNavHelper do describe "#user_cell" do it "returns user link and email separated by a newline character" do expected_html = "#{current_user.name}\nUser #{current_user.email}" - expect(user_cell(current_user)).to match(expected_html) + expect(CGI.unescapeHTML(user_cell(current_user))).to match(expected_html) end end diff --git a/spec/lib/tasks/data_export_spec.rb b/spec/lib/tasks/data_export_spec.rb index 8b5dd5fbe..2f6127512 100644 --- a/spec/lib/tasks/data_export_spec.rb +++ b/spec/lib/tasks/data_export_spec.rb @@ -30,7 +30,7 @@ describe "rake core:data_export", type: task do context "with all available years" do it "calls the export service" do - expect(export_service).to receive(:export_xml).with(full_update: true, collection: nil) + expect(export_service).to receive(:export_xml).with(full_update: true, collection: nil, year: nil) task.invoke end @@ -38,9 +38,9 @@ describe "rake core:data_export", type: task do context "with a specific collection" do it "calls the export service" do - expect(export_service).to receive(:export_xml).with(full_update: true, collection: 2022) + expect(export_service).to receive(:export_xml).with(full_update: true, collection: "lettings", year: 2022) - task.invoke("2022") + task.invoke("lettings", "2022") end end end diff --git a/spec/lib/tasks/set_export_collection_years_spec.rb b/spec/lib/tasks/set_export_collection_years_spec.rb new file mode 100644 index 000000000..76ff779b0 --- /dev/null +++ b/spec/lib/tasks/set_export_collection_years_spec.rb @@ -0,0 +1,41 @@ +require "rails_helper" +require "rake" + +RSpec.describe "set_export_collection_years" do + describe ":set_export_collection_years", type: :task do + subject(:task) { Rake::Task["set_export_collection_years"] } + + before do + Rake.application.rake_require("tasks/set_export_collection_years") + Rake::Task.define_task(:environment) + task.reenable + end + + context "when the rake task is run" do + let!(:lettings_export_2023) { Export.create(collection: "2023", year: nil, started_at: Time.zone.now) } + let!(:lettings_export_2024) { Export.create(collection: "2024", year: nil, started_at: Time.zone.now) } + let!(:updated_lettings_export) { Export.create(collection: "lettings", year: 2023, started_at: Time.zone.now) } + let!(:organisations_export) { Export.create(collection: "organisations", year: nil, started_at: Time.zone.now) } + let!(:users_export) { Export.create(collection: "users", year: nil, started_at: Time.zone.now) } + + it "correctly updates collection years" do + task.invoke + + expect(lettings_export_2023.reload.collection).to eq("lettings") + expect(lettings_export_2023.year).to eq(2023) + + expect(lettings_export_2024.reload.collection).to eq("lettings") + expect(lettings_export_2024.year).to eq(2024) + + expect(updated_lettings_export.reload.collection).to eq("lettings") + expect(updated_lettings_export.year).to eq(2023) + + expect(organisations_export.reload.collection).to eq("organisations") + expect(organisations_export.year).to eq(nil) + + expect(users_export.reload.collection).to eq("users") + expect(users_export.year).to eq(nil) + end + end + end +end diff --git a/spec/services/exports/lettings_log_export_service_spec.rb b/spec/services/exports/lettings_log_export_service_spec.rb index 6a07af8dd..8c123b47e 100644 --- a/spec/services/exports/lettings_log_export_service_spec.rb +++ b/spec/services/exports/lettings_log_export_service_spec.rb @@ -207,15 +207,15 @@ RSpec.describe Exports::LettingsLogExportService do it "generates multiple ZIP export files with the expected filenames" do expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) expect(storage_service).to receive(:write_file).with(expected_zip_filename2, any_args) - expect(Rails.logger).to receive(:info).with("Building export run for 2021") + expect(Rails.logger).to receive(:info).with("Building export run for lettings 2021") expect(Rails.logger).to receive(:info).with("Creating core_2021_2022_apr_mar_f0001_inc0001 - 1 resources") expect(Rails.logger).to receive(:info).with("Added core_2021_2022_apr_mar_f0001_inc0001_pt001.xml") expect(Rails.logger).to receive(:info).with("Writing core_2021_2022_apr_mar_f0001_inc0001.zip") - expect(Rails.logger).to receive(:info).with("Building export run for 2022") + expect(Rails.logger).to receive(:info).with("Building export run for lettings 2022") expect(Rails.logger).to receive(:info).with("Creating core_2022_2023_apr_mar_f0001_inc0001 - 1 resources") expect(Rails.logger).to receive(:info).with("Added core_2022_2023_apr_mar_f0001_inc0001_pt001.xml") expect(Rails.logger).to receive(:info).with("Writing core_2022_2023_apr_mar_f0001_inc0001.zip") - expect(Rails.logger).to receive(:info).with("Building export run for 2023") + expect(Rails.logger).to receive(:info).with("Building export run for lettings 2023") expect(Rails.logger).to receive(:info).with("Creating core_2023_2024_apr_mar_f0001_inc0001 - 0 resources") export_service.export_xml_lettings_logs @@ -223,7 +223,7 @@ RSpec.describe Exports::LettingsLogExportService do it "generates zip export files only for specified year" do expect(storage_service).to receive(:write_file).with(expected_zip_filename2, any_args) - expect(Rails.logger).to receive(:info).with("Building export run for 2022") + expect(Rails.logger).to receive(:info).with("Building export run for lettings 2022") expect(Rails.logger).to receive(:info).with("Creating core_2022_2023_apr_mar_f0001_inc0001 - 1 resources") expect(Rails.logger).to receive(:info).with("Added core_2022_2023_apr_mar_f0001_inc0001_pt001.xml") expect(Rails.logger).to receive(:info).with("Writing core_2022_2023_apr_mar_f0001_inc0001.zip") @@ -236,21 +236,21 @@ RSpec.describe Exports::LettingsLogExportService do let(:expected_zip_filename2) { "core_2022_2023_apr_mar_f0001_inc0001.zip" } before do - Export.new(started_at: Time.zone.yesterday, base_number: 7, increment_number: 3, collection: 2021).save! + Export.new(started_at: Time.zone.yesterday, base_number: 7, increment_number: 3, collection: "lettings", year: 2021).save! end it "generates multiple ZIP export files with different base numbers in the filenames" do expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) expect(storage_service).to receive(:write_file).with(expected_zip_filename2, any_args) - expect(Rails.logger).to receive(:info).with("Building export run for 2021") + expect(Rails.logger).to receive(:info).with("Building export run for lettings 2021") expect(Rails.logger).to receive(:info).with("Creating core_2021_2022_apr_mar_f0007_inc0004 - 1 resources") expect(Rails.logger).to receive(:info).with("Added core_2021_2022_apr_mar_f0007_inc0004_pt001.xml") expect(Rails.logger).to receive(:info).with("Writing core_2021_2022_apr_mar_f0007_inc0004.zip") - expect(Rails.logger).to receive(:info).with("Building export run for 2022") + expect(Rails.logger).to receive(:info).with("Building export run for lettings 2022") expect(Rails.logger).to receive(:info).with("Creating core_2022_2023_apr_mar_f0001_inc0001 - 1 resources") expect(Rails.logger).to receive(:info).with("Added core_2022_2023_apr_mar_f0001_inc0001_pt001.xml") expect(Rails.logger).to receive(:info).with("Writing core_2022_2023_apr_mar_f0001_inc0001.zip") - expect(Rails.logger).to receive(:info).with("Building export run for 2023") + expect(Rails.logger).to receive(:info).with("Building export run for lettings 2023") expect(Rails.logger).to receive(:info).with("Creating core_2023_2024_apr_mar_f0001_inc0001 - 0 resources") export_service.export_xml_lettings_logs @@ -329,7 +329,7 @@ RSpec.describe Exports::LettingsLogExportService do context "when this is a second export (partial)" do before do start_time = Time.zone.local(2022, 6, 1) - Export.new(started_at: start_time, collection: 2021).save! + Export.new(started_at: start_time, collection: "lettings", year: 2021).save! end it "does not add any entry for the master manifest (no lettings logs)" do From 9343d22768d2496912f76a9fdcbcb8be6a60b124 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Wed, 4 Dec 2024 14:00:55 +0000 Subject: [PATCH 15/36] CLDC-3335 Adjust BU errors for addresses (#2829) * Adjust BU errors for addresses * typo * Update error messages --- .../lettings/year2024/row_parser.rb | 20 +++++++++++++------ .../bulk_upload/sales/year2024/row_parser.rb | 20 +++++++++++++------ .../lettings/2024/bulk_upload.en.yml | 2 ++ .../validations/sales/2024/bulk_upload.en.yml | 2 ++ .../lettings/year2024/row_parser_spec.rb | 16 +++++++-------- .../sales/year2024/row_parser_spec.rb | 18 ++++++++--------- 6 files changed, 49 insertions(+), 29 deletions(-) diff --git a/app/services/bulk_upload/lettings/year2024/row_parser.rb b/app/services/bulk_upload/lettings/year2024/row_parser.rb index ef7433614..6315274ce 100644 --- a/app/services/bulk_upload/lettings/year2024/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2024/row_parser.rb @@ -607,14 +607,22 @@ private def validate_uprn_exists_if_any_key_address_fields_are_blank if field_16.blank? && !key_address_fields_provided? - errors.add(:field_16, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "UPRN.")) + %i[field_17 field_19 field_21 field_22].each do |field| + errors.add(field, I18n.t("#{ERROR_BASE_KEY}.address.not_answered")) if send(field).blank? + end + errors.add(:field_16, I18n.t("#{ERROR_BASE_KEY}.address.not_answered", question: "UPRN.")) end end def validate_address_option_found if log.uprn.nil? && field_16.blank? && key_address_fields_provided? + error_message = if log.address_options_present? + I18n.t("#{ERROR_BASE_KEY}.address.not_determined") + else + I18n.t("#{ERROR_BASE_KEY}.address.not_found") + end %i[field_17 field_18 field_19 field_20 field_21 field_22].each do |field| - errors.add(field, I18n.t("#{ERROR_BASE_KEY}.address.not_found")) + errors.add(field, error_message) if errors[field].blank? end end end @@ -625,19 +633,19 @@ private def validate_address_fields if field_16.blank? || log.errors.attribute_names.include?(:uprn) - if field_17.blank? + if field_17.blank? && errors[:field_17].blank? errors.add(:field_17, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "address line 1.")) end - if field_19.blank? + if field_19.blank? && errors[:field_19].blank? errors.add(:field_19, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "town or city.")) end - if field_21.blank? + if field_21.blank? && errors[:field_21].blank? errors.add(:field_21, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "part 1 of postcode.")) end - if field_22.blank? + if field_22.blank? && errors[:field_22].blank? errors.add(:field_22, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "part 2 of postcode.")) end end diff --git a/app/services/bulk_upload/sales/year2024/row_parser.rb b/app/services/bulk_upload/sales/year2024/row_parser.rb index fdef65aec..0e7b72d6f 100644 --- a/app/services/bulk_upload/sales/year2024/row_parser.rb +++ b/app/services/bulk_upload/sales/year2024/row_parser.rb @@ -605,14 +605,22 @@ private def validate_uprn_exists_if_any_key_address_fields_are_blank if field_22.blank? && !key_address_fields_provided? - errors.add(:field_22, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "UPRN.")) + %i[field_23 field_25 field_27 field_28].each do |field| + errors.add(field, I18n.t("#{ERROR_BASE_KEY}.address.not_answered")) if send(field).blank? + end + errors.add(:field_22, I18n.t("#{ERROR_BASE_KEY}.address.not_answered", question: "UPRN.")) end end def validate_address_option_found if log.uprn.nil? && field_22.blank? && key_address_fields_provided? + error_message = if log.address_options_present? + I18n.t("#{ERROR_BASE_KEY}.address.not_determined") + else + I18n.t("#{ERROR_BASE_KEY}.address.not_found") + end %i[field_23 field_24 field_25 field_26 field_27 field_28].each do |field| - errors.add(field, I18n.t("#{ERROR_BASE_KEY}.address.not_found")) + errors.add(field, error_message) if errors[field].blank? end end end @@ -623,19 +631,19 @@ private def validate_address_fields if field_22.blank? || log.errors.attribute_names.include?(:uprn) - if field_23.blank? + if field_23.blank? && errors[:field_23].blank? errors.add(:field_23, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "address line 1.")) end - if field_25.blank? + if field_25.blank? && errors[:field_25].blank? errors.add(:field_25, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "town or city.")) end - if field_27.blank? + if field_27.blank? && errors[:field_27].blank? errors.add(:field_27, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "part 1 of postcode.")) end - if field_28.blank? + if field_28.blank? && errors[:field_28].blank? errors.add(:field_28, I18n.t("#{ERROR_BASE_KEY}.not_answered", question: "part 2 of postcode.")) end end diff --git a/config/locales/validations/lettings/2024/bulk_upload.en.yml b/config/locales/validations/lettings/2024/bulk_upload.en.yml index 76985ee32..a16090543 100644 --- a/config/locales/validations/lettings/2024/bulk_upload.en.yml +++ b/config/locales/validations/lettings/2024/bulk_upload.en.yml @@ -50,6 +50,8 @@ en: invalid: "Age of person %{person_num} must be a number or the letter R" address: not_found: "We could not find this address. Check the address data in your CSV file is correct and complete, or select the correct address using the CORE site." + not_determined: "There are multiple matches for this address. Either select the correct address manually or correct the UPRN in the CSV file." + not_answered: "Enter either the UPRN or the full address." nationality: invalid: "Select a valid nationality." charges: diff --git a/config/locales/validations/sales/2024/bulk_upload.en.yml b/config/locales/validations/sales/2024/bulk_upload.en.yml index fc68662d1..2621386c1 100644 --- a/config/locales/validations/sales/2024/bulk_upload.en.yml +++ b/config/locales/validations/sales/2024/bulk_upload.en.yml @@ -40,5 +40,7 @@ en: buyer_cannot_be_over_16_and_child: "Buyer 2's age cannot be 16 or over if their working situation is child under 16." address: not_found: "We could not find this address. Check the address data in your CSV file is correct and complete, or select the correct address using the CORE site." + not_determined: "There are multiple matches for this address. Either select the correct address manually or correct the UPRN in the CSV file." + not_answered: "Enter either the UPRN or the full address." nationality: invalid: "Select a valid nationality." diff --git a/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb index e9047b2ae..13ab91370 100644 --- a/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb @@ -1658,11 +1658,11 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "adds appropriate errors to UPRN and key address fields" do parser.valid? - expect(parser.errors[:field_16]).to eql([I18n.t("validations.lettings.2024.bulk_upload.not_answered", question: "UPRN.")]) - expect(parser.errors[:field_17]).to eql([I18n.t("validations.lettings.2024.bulk_upload.not_answered", question: "address line 1.")]) - expect(parser.errors[:field_19]).to eql([I18n.t("validations.lettings.2024.bulk_upload.not_answered", question: "town or city.")]) - expect(parser.errors[:field_21]).to eql([I18n.t("validations.lettings.2024.bulk_upload.not_answered", question: "part 1 of postcode.")]) - expect(parser.errors[:field_22]).to eql([I18n.t("validations.lettings.2024.bulk_upload.not_answered", question: "part 2 of postcode.")]) + expect(parser.errors[:field_16]).to eql([I18n.t("validations.lettings.2024.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_17]).to eql([I18n.t("validations.lettings.2024.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_19]).to eql([I18n.t("validations.lettings.2024.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_21]).to eql([I18n.t("validations.lettings.2024.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_22]).to eql([I18n.t("validations.lettings.2024.bulk_upload.address.not_answered")]) end end @@ -1671,8 +1671,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "adds errors to UPRN and the missing key address field" do parser.valid? - expect(parser.errors[:field_16]).to eql([I18n.t("validations.lettings.2024.bulk_upload.not_answered", question: "UPRN.")]) - expect(parser.errors[:field_17]).to eql([I18n.t("validations.lettings.2024.bulk_upload.not_answered", question: "address line 1.")]) + expect(parser.errors[:field_16]).to eql([I18n.t("validations.lettings.2024.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_17]).to eql([I18n.t("validations.lettings.2024.bulk_upload.address.not_answered")]) expect(parser.errors[:field_19]).to be_empty expect(parser.errors[:field_21]).to be_empty expect(parser.errors[:field_22]).to be_empty @@ -1721,7 +1721,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do parser.valid? expect(parser.errors[:field_16]).to be_empty %i[field_17 field_18 field_19 field_20 field_21 field_22].each do |field| - expect(parser.errors[field]).to eql([I18n.t("validations.lettings.2024.bulk_upload.address.not_found")]) + expect(parser.errors[field]).to eql([I18n.t("validations.lettings.2024.bulk_upload.address.not_determined")]) end end end diff --git a/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb b/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb index e4e2eb6f5..7e969c10e 100644 --- a/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb +++ b/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb @@ -310,7 +310,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do it "only has one error added to the field" do parser.valid? - expect(parser.errors[:field_23]).to eql([I18n.t("validations.sales.2024.bulk_upload.not_answered", question: "address line 1.")]) + expect(parser.errors[:field_23]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_answered")]) end end @@ -1042,11 +1042,11 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do it "adds appropriate errors to UPRN and key address fields" do parser.valid? - expect(parser.errors[:field_22]).to eql([I18n.t("validations.sales.2024.bulk_upload.not_answered", question: "UPRN.")]) - expect(parser.errors[:field_23]).to eql([I18n.t("validations.sales.2024.bulk_upload.not_answered", question: "address line 1.")]) - expect(parser.errors[:field_25]).to eql([I18n.t("validations.sales.2024.bulk_upload.not_answered", question: "town or city.")]) - expect(parser.errors[:field_27]).to eql([I18n.t("validations.sales.2024.bulk_upload.not_answered", question: "part 1 of postcode.")]) - expect(parser.errors[:field_28]).to eql([I18n.t("validations.sales.2024.bulk_upload.not_answered", question: "part 2 of postcode.")]) + expect(parser.errors[:field_22]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_23]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_25]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_27]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_28]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_answered")]) end end @@ -1055,8 +1055,8 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do it "adds errors to UPRN and the missing key address field" do parser.valid? - expect(parser.errors[:field_22]).to eql([I18n.t("validations.sales.2024.bulk_upload.not_answered", question: "UPRN.")]) - expect(parser.errors[:field_23]).to eql([I18n.t("validations.sales.2024.bulk_upload.not_answered", question: "address line 1.")]) + expect(parser.errors[:field_22]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_answered")]) + expect(parser.errors[:field_23]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_answered")]) expect(parser.errors[:field_25]).to be_empty expect(parser.errors[:field_27]).to be_empty expect(parser.errors[:field_28]).to be_empty @@ -1105,7 +1105,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do parser.valid? expect(parser.errors[:field_22]).to be_empty %i[field_23 field_24 field_25 field_26 field_27 field_28].each do |field| - expect(parser.errors[field]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_found")]) + expect(parser.errors[field]).to eql([I18n.t("validations.sales.2024.bulk_upload.address.not_determined")]) end end end From 918cf0f08f832aadf15cad9a51c541b7eb8554d7 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Wed, 4 Dec 2024 15:54:53 +0000 Subject: [PATCH 16/36] CLDC-2434 Fix test warnings and update some flaky tests (#2850) * Remove test warnings * Fix some flaky tests --- spec/requests/lettings_logs_controller_spec.rb | 2 +- .../organisation_relationships_controller_spec.rb | 8 ++++---- spec/requests/organisations_controller_spec.rb | 6 +++--- spec/requests/sales_logs_controller_spec.rb | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/spec/requests/lettings_logs_controller_spec.rb b/spec/requests/lettings_logs_controller_spec.rb index 3d2c27400..e795b76ea 100644 --- a/spec/requests/lettings_logs_controller_spec.rb +++ b/spec/requests/lettings_logs_controller_spec.rb @@ -1483,7 +1483,7 @@ RSpec.describe LettingsLogsController, type: :request do end context "when viewing a collection of logs affected by deactivated location" do - let!(:affected_lettings_logs) { FactoryBot.create_list(:lettings_log, 3, unresolved: true, assigned_to: user) } + let!(:affected_lettings_logs) { FactoryBot.create_list(:lettings_log, 3, unresolved: true, assigned_to: user, tenancycode: "affected tenancycode", propcode: "affected propcode") } let!(:other_user_affected_lettings_log) { FactoryBot.create(:lettings_log, unresolved: true) } let!(:non_affected_lettings_logs) { FactoryBot.create_list(:lettings_log, 4, assigned_to: user) } let(:other_user) { FactoryBot.create(:user, organisation: user.organisation) } diff --git a/spec/requests/organisation_relationships_controller_spec.rb b/spec/requests/organisation_relationships_controller_spec.rb index feb687e0c..79706fff9 100644 --- a/spec/requests/organisation_relationships_controller_spec.rb +++ b/spec/requests/organisation_relationships_controller_spec.rb @@ -40,7 +40,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do it "shows a table of stock owners" do expected_html = " Date: Wed, 4 Dec 2024 16:05:05 +0000 Subject: [PATCH 17/36] CLDC-3314: Skip BU error for ownershipsch in validate_discounted_ownership_value (#2837) --- .../validations/sales/sale_information_validations.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/models/validations/sales/sale_information_validations.rb b/app/models/validations/sales/sale_information_validations.rb index 3825271c5..4b8948053 100644 --- a/app/models/validations/sales/sale_information_validations.rb +++ b/app/models/validations/sales/sale_information_validations.rb @@ -64,7 +64,7 @@ module Validations::Sales::SaleInformationValidations if over_tolerance?(record.mortgage_deposit_and_grant_total, record.value_with_discount, tolerance, strict: !record.discount.nil?) && record.discounted_ownership_sale? deposit_and_grant_sentence = record.grant.present? ? ", cash deposit (#{record.field_formatted_as_currency('deposit')}), and grant (#{record.field_formatted_as_currency('grant')})" : " and cash deposit (#{record.field_formatted_as_currency('deposit')})" discount_sentence = record.discount.present? ? " (#{record.field_formatted_as_currency('value')}) subtracted by the sum of the full purchase price (#{record.field_formatted_as_currency('value')}) multiplied by the percentage discount (#{record.discount}%)" : "" - %i[mortgageused mortgage value deposit ownershipsch discount grant].each do |field| + %i[mortgageused mortgage value deposit discount grant].each do |field| record.errors.add field, I18n.t("validations.sales.sale_information.#{field}.discounted_ownership_value", mortgage: record.mortgage&.positive? ? " (#{record.field_formatted_as_currency('mortgage')})" : "", deposit_and_grant_sentence:, @@ -72,6 +72,12 @@ module Validations::Sales::SaleInformationValidations discount_sentence:, value_with_discount: record.field_formatted_as_currency("value_with_discount")).html_safe end + record.errors.add :ownershipsch, :skip_bu_error, message: I18n.t("validations.sales.sale_information.ownershipsch.discounted_ownership_value", + mortgage: record.mortgage&.positive? ? " (#{record.field_formatted_as_currency('mortgage')})" : "", + deposit_and_grant_sentence:, + mortgage_deposit_and_grant_total: record.field_formatted_as_currency("mortgage_deposit_and_grant_total"), + discount_sentence:, + value_with_discount: record.field_formatted_as_currency("value_with_discount")).html_safe end end From 1b2100bc8fc3729ce409ed5aba7a9a7c38925c9f Mon Sep 17 00:00:00 2001 From: Manny Dinssa <44172848+Dinssa@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:06:25 +0000 Subject: [PATCH 18/36] CLDC-3093: Update content inconsistencies and typos (#2846) * Update copy * Update sale_information.en.yml * Update sale_information.en.yml * Fixes * Update test * Update test * Update test --- app/models/form/sales/questions/equity.rb | 2 +- app/models/form/sales/questions/value.rb | 2 +- .../lettings/household_characteristics.en.yml | 38 ++++++------ .../2023/lettings/income_and_benefits.en.yml | 2 +- .../sales/household_characteristics.en.yml | 58 +++++++++---------- .../sales/income_benefits_and_savings.en.yml | 12 ++-- .../forms/2023/sales/sale_information.en.yml | 24 ++++---- .../lettings/household_characteristics.en.yml | 8 +-- .../2024/lettings/income_and_benefits.en.yml | 2 +- .../sales/household_characteristics.en.yml | 58 +++++++++---------- .../sales/income_benefits_and_savings.en.yml | 8 +-- .../forms/2024/sales/sale_information.en.yml | 24 ++++---- .../lettings/household_characteristics.en.yml | 2 +- .../2025/lettings/income_and_benefits.en.yml | 2 +- .../sales/household_characteristics.en.yml | 20 +++---- .../sales/income_benefits_and_savings.en.yml | 2 +- .../forms/2025/sales/sale_information.en.yml | 8 +-- config/locales/forms/2025/sales/setup.en.yml | 2 +- spec/models/form/sales/pages/equity_spec.rb | 4 ++ .../pages/value_shared_ownership_spec.rb | 4 ++ .../form/sales/questions/equity_spec.rb | 4 ++ 21 files changed, 149 insertions(+), 137 deletions(-) diff --git a/app/models/form/sales/questions/equity.rb b/app/models/form/sales/questions/equity.rb index e39e77ebb..74f5e9970 100644 --- a/app/models/form/sales/questions/equity.rb +++ b/app/models/form/sales/questions/equity.rb @@ -2,7 +2,7 @@ class Form::Sales::Questions::Equity < ::Form::Question def initialize(id, hsh, page) super @id = "equity" - @copy_key = "sales.sale_information.equity.#{page.id}" + @copy_key = form.start_year_2025_or_later? ? "sales.sale_information.equity.#{page.id}" : "sales.sale_information.equity" @type = "numeric" @min = 0 @max = 100 diff --git a/app/models/form/sales/questions/value.rb b/app/models/form/sales/questions/value.rb index ad021e920..ce8d07e28 100644 --- a/app/models/form/sales/questions/value.rb +++ b/app/models/form/sales/questions/value.rb @@ -2,7 +2,7 @@ class Form::Sales::Questions::Value < ::Form::Question def initialize(id, hsh, page) super @id = "value" - @copy_key = "sales.sale_information.value.#{page.id}" + @copy_key = form.start_year_2025_or_later? ? "sales.sale_information.value.#{page.id}" : "sales.sale_information.value" @type = "numeric" @min = 0 @step = 1 diff --git a/config/locales/forms/2023/lettings/household_characteristics.en.yml b/config/locales/forms/2023/lettings/household_characteristics.en.yml index 1f175adbc..3de0745b1 100644 --- a/config/locales/forms/2023/lettings/household_characteristics.en.yml +++ b/config/locales/forms/2023/lettings/household_characteristics.en.yml @@ -19,23 +19,23 @@ en: page_header: "" age1_known: check_answer_label: "" - hint_text: "The ’lead’ or ’main’ tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest." + hint_text: "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest." question_text: "Do you know the lead tenant’s age?" age1: check_answer_label: "Lead tenant’s age" hint_text: "" question_text: "Age" - + sex1: page_header: "" check_answer_label: "Lead tenant’s gender identity" hint_text: "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest." question_text: "Which of these best describes the lead tenant’s gender identity?" - + ethnic_group: page_header: "" check_answer_label: "Lead tenant’s ethnic group" - hint_text: "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest." + hint_text: "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest." question_text: "What is the lead tenant’s ethnic group?" ethnic: @@ -97,13 +97,13 @@ en: question_text: "Do you know person 2’s age?" age2: check_answer_label: "Person 2’s age" - hint_text: "" + hint_text: "" question_text: "Age" sex2: page_header: "" check_answer_label: "Person 2’s gender identity" - hint_text: "" + hint_text: "" question_text: "Which of these best describes person 2’s gender identity?" ecstat2: @@ -132,13 +132,13 @@ en: question_text: "Do you know person 3’s age?" age3: check_answer_label: "Person 3’s age" - hint_text: "" + hint_text: "" question_text: "Age" sex3: page_header: "" check_answer_label: "Person 3’s gender identity" - hint_text: "" + hint_text: "" question_text: "Which of these best describes person 3’s gender identity?" ecstat3: @@ -167,13 +167,13 @@ en: question_text: "Do you know person 4’s age?" age4: check_answer_label: "Person 4’s age" - hint_text: "" + hint_text: "" question_text: "Age" sex4: page_header: "" check_answer_label: "Person 4’s gender identity" - hint_text: "" + hint_text: "" question_text: "Which of these best describes person 4’s gender identity?" ecstat4: @@ -202,13 +202,13 @@ en: question_text: "Do you know person 5’s age?" age5: check_answer_label: "Person 5’s age" - hint_text: "" + hint_text: "" question_text: "Age" sex5: page_header: "" check_answer_label: "Person 5’s gender identity" - hint_text: "" + hint_text: "" question_text: "Which of these best describes person 5’s gender identity?" ecstat5: @@ -237,13 +237,13 @@ en: question_text: "Do you know person 6’s age?" age6: check_answer_label: "Person 6’s age" - hint_text: "" + hint_text: "" question_text: "Age" sex6: page_header: "" check_answer_label: "Person 6’s gender identity" - hint_text: "" + hint_text: "" question_text: "Which of these best describes person 6’s gender identity?" ecstat6: @@ -272,13 +272,13 @@ en: question_text: "Do you know person 7’s age?" age7: check_answer_label: "Person 7’s age" - hint_text: "" + hint_text: "" question_text: "Age" sex7: page_header: "" check_answer_label: "Person 7’s gender identity" - hint_text: "" + hint_text: "" question_text: "Which of these best describes person 7’s gender identity?" ecstat7: @@ -307,17 +307,17 @@ en: question_text: "Do you know person 8’s age?" age8: check_answer_label: "Person 8’s age" - hint_text: "" + hint_text: "" question_text: "Age" sex8: page_header: "" check_answer_label: "Person 8’s gender identity" - hint_text: "" + hint_text: "" question_text: "Which of these best describes person 8’s gender identity?" ecstat8: page_header: "" check_answer_label: "Person 8’s working situation" hint_text: "" - question_text: "Which of these best describes person 8’s working situation?" \ No newline at end of file + question_text: "Which of these best describes person 8’s working situation?" diff --git a/config/locales/forms/2023/lettings/income_and_benefits.en.yml b/config/locales/forms/2023/lettings/income_and_benefits.en.yml index bc19c7954..dc5ee5a53 100644 --- a/config/locales/forms/2023/lettings/income_and_benefits.en.yml +++ b/config/locales/forms/2023/lettings/income_and_benefits.en.yml @@ -103,6 +103,6 @@ en: hint_text: "You only need to give an approximate figure." question_text: "Can you estimate the outstanding amount?" tshortfall: - check_answer_label: "Estimated outstanding amountt" + check_answer_label: "Estimated outstanding amount" hint_text: "Also known as the ‘outstanding amount’." question_text: "Estimated outstanding amount" diff --git a/config/locales/forms/2023/sales/household_characteristics.en.yml b/config/locales/forms/2023/sales/household_characteristics.en.yml index a49a817dc..ed03bc698 100644 --- a/config/locales/forms/2023/sales/household_characteristics.en.yml +++ b/config/locales/forms/2023/sales/household_characteristics.en.yml @@ -37,7 +37,7 @@ en: check_answer_label: "Buyer 1’s age" hint_text: "" question_text: "Age" - + sex1: page_header: "" check_answer_label: "Buyer 1’s gender identity" @@ -49,7 +49,7 @@ en: check_answer_label: "Buyer 1’s ethnic group" 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." question_text: "What is buyer 1’s ethnic group?" - + ethnic: ethnic_background_black: page_header: "" @@ -103,9 +103,9 @@ en: question_text: "What is buyer 2's relationship to buyer 1?" person: page_header: "" - check_answer_label: "Person 2’s relationship to Buyer 1" + check_answer_label: "Person 2’s relationship to buyer 1" hint_text: "" - question_text: "What is Person 2’s relationship to Buyer 1?" + question_text: "What is person 2’s relationship to buyer 1?" age2: buyer: @@ -139,14 +139,14 @@ en: page_header: "" check_answer_label: "Person 2’s gender identity" hint_text: "" - question_text: "Which of these best describes Person 2’s gender identity?" + question_text: "Which of these best describes person 2’s gender identity?" ethnic_group2: page_header: "" check_answer_label: "Buyer 2’s ethnic group" hint_text: "" question_text: "What is buyer 2’s ethnic group?" - + ethnicbuy2: ethnic_background_black: page_header: "" @@ -179,7 +179,7 @@ en: check_answer_label: "Buyer 2’s nationality" hint_text: "" question_text: "What is buyer 2’s nationality?" - + ecstat2: buyer: page_header: "" @@ -190,14 +190,14 @@ en: page_header: "" check_answer_label: "Person 2’s working situation" hint_text: "" - question_text: "Which of these best describes Person 2’s working situation?" + question_text: "Which of these best describes person 2’s working situation?" buy2livein: page_header: "" check_answer_label: "Will buyer 2 live in the property?" hint_text: "" question_text: "Will buyer 2 live in the property?" - + hholdcount: joint_purchase: page_header: "" @@ -224,9 +224,9 @@ en: relat3: page_header: "" - check_answer_label: "Person 3’s relationship to Buyer 1" + check_answer_label: "Person 3’s relationship to buyer 1" hint_text: "" - question_text: "What is Person 3’s relationship to Buyer 1?" + question_text: "What is person 3’s relationship to buyer 1?" age3: page_header: "" @@ -238,18 +238,18 @@ en: check_answer_label: "Person 3’s age" hint_text: "" question_text: "Age" - + sex3: page_header: "" check_answer_label: "Person 3’s gender identity" hint_text: "" - question_text: "Which of these best describes Person 3’s gender identity?" + question_text: "Which of these best describes person 3’s gender identity?" ecstat3: page_header: "" check_answer_label: "Person 3’s working situation" hint_text: "" - question_text: "Which of these best describes Person 3’s working situation?" + question_text: "Which of these best describes person 3’s working situation?" details_known_4: page_header: "" @@ -259,9 +259,9 @@ en: relat4: page_header: "" - check_answer_label: "Person 4’s relationship to Buyer 1" + check_answer_label: "Person 4’s relationship to buyer 1" hint_text: "" - question_text: "What is Person 4’s relationship to Buyer 1?" + question_text: "What is person 4’s relationship to buyer 1?" age4: page_header: "" @@ -273,18 +273,18 @@ en: check_answer_label: "Person 4’s age" hint_text: "" question_text: "Age" - + sex4: page_header: "" check_answer_label: "Person 4’s gender identity" hint_text: "" - question_text: "Which of these best describes Person 4’s gender identity?" + question_text: "Which of these best describes person 4’s gender identity?" ecstat4: page_header: "" check_answer_label: "Person 4’s working situation" hint_text: "" - question_text: "Which of these best describes Person 4’s working situation?" + question_text: "Which of these best describes person 4’s working situation?" details_known_5: page_header: "" @@ -294,9 +294,9 @@ en: relat5: page_header: "" - check_answer_label: "Person 5’s relationship to Buyer 1" + check_answer_label: "Person 5’s relationship to buyer 1" hint_text: "" - question_text: "What is Person 5’s relationship to Buyer 1?" + question_text: "What is person 5’s relationship to buyer 1?" age5: page_header: "" @@ -308,18 +308,18 @@ en: check_answer_label: "Person 5’s age" hint_text: "" question_text: "Age" - + sex5: page_header: "" check_answer_label: "Person 5’s gender identity" hint_text: "" - question_text: "Which of these best describes Person 5’s gender identity?" + question_text: "Which of these best describes person 5’s gender identity?" ecstat5: page_header: "" check_answer_label: "Person 5’s working situation" hint_text: "" - question_text: "Which of these best describes Person 5’s working situation?" + question_text: "Which of these best describes person 5’s working situation?" details_known_6: page_header: "" @@ -329,9 +329,9 @@ en: relat6: page_header: "" - check_answer_label: "Person 6’s relationship to Buyer 1" + check_answer_label: "Person 6’s relationship to buyer 1" hint_text: "" - question_text: "What is Person 6’s relationship to Buyer 1?" + question_text: "What is person 6’s relationship to buyer 1?" age6: page_header: "" @@ -343,15 +343,15 @@ en: check_answer_label: "Person 6’s age" hint_text: "" question_text: "Age" - + sex6: page_header: "" check_answer_label: "Person 6’s gender identity" hint_text: "" - question_text: "Which of these best describes Person 6’s gender identity?" + question_text: "Which of these best describes person 6’s gender identity?" ecstat6: page_header: "" check_answer_label: "Person 6’s working situation" hint_text: "" - question_text: "Which of these best describes Person 6’s working situation?" + question_text: "Which of these best describes person 6’s working situation?" diff --git a/config/locales/forms/2023/sales/income_benefits_and_savings.en.yml b/config/locales/forms/2023/sales/income_benefits_and_savings.en.yml index 49262d7a1..689c611cb 100644 --- a/config/locales/forms/2023/sales/income_benefits_and_savings.en.yml +++ b/config/locales/forms/2023/sales/income_benefits_and_savings.en.yml @@ -13,7 +13,7 @@ en: check_answer_label: "Buyer 1’s gross annual income" hint_text: "Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments." question_text: "Buyer 1’s gross annual income" - + inc1mort: page_header: "" check_answer_label: "Buyer 1’s income used for mortgage application" @@ -33,7 +33,7 @@ en: inc2mort: page_header: "" - check_answer_label: "Buyer 2’s income used for mortgage application" + check_answer_label: "Buyer 2’s income used for mortgage application" hint_text: "" question_text: "Was buyer 2’s income used for a mortgage application?" @@ -48,14 +48,14 @@ en: check_answer_label: "Housing-related benefits buyer received before buying this property" hint_text: "" question_text: "Was the buyer receiving any of these housing-related benefits immediately before buying this property?" - + savings: joint_purchase: page_header: "" savingsnk: - check_answer_label: "Buyers’ total savings known?" + check_answer_label: "Buyers’ total savings known?" hint_text: "" - question_text: "Do you know how much the 'buyers' had in savings before they paid any deposit for the property?" + question_text: "Do you know how much the buyers had in savings before they paid any deposit for the property?" savings: check_answer_label: "Buyers’ total savings before any deposit paid" hint_text: "Include any savings, investments, ISAs, premium bonds, shares, or money held in a bank or building society account." @@ -87,4 +87,4 @@ en: page_header: "" check_answer_label: "Previous property shared ownership?" hint_text: "For any buyer" - question_text: "Was the previous property under shared ownership?" \ No newline at end of file + question_text: "Was the previous property under shared ownership?" diff --git a/config/locales/forms/2023/sales/sale_information.en.yml b/config/locales/forms/2023/sales/sale_information.en.yml index 318d7c7db..24f767026 100644 --- a/config/locales/forms/2023/sales/sale_information.en.yml +++ b/config/locales/forms/2023/sales/sale_information.en.yml @@ -28,7 +28,7 @@ en: staircasing: page_header: "" check_answer_label: "Staircasing transaction" - hint_text: "A staircasing transaction is when the household purchases more shares in their property, increasing the proportion they own and decreasing the proportion the housing association owns. Once the household purchases 100% of the shares, they own the property" + hint_text: "A staircasing transaction is when the household purchases more shares in their property, increasing the proportion they own and decreasing the proportion the housing association owns. Once the household purchases 100% of the shares, they own the property." question_text: "Is this a staircasing transaction?" about_staircasing: page_header: "About the staircasing transaction" @@ -69,7 +69,7 @@ en: check_answer_label: "Household rehoused under a local authority nominations agreement?" hint_text: "A local authority nominations agreement is a written agreement between a local authority and private registered provider (PRP) that some or all of its sales vacancies are offered to local authorities for rehousing" question_text: "Was the household rehoused under a 'local authority nominations agreement'?" - + soctenant: joint_purchase: page_header: "" @@ -81,11 +81,11 @@ en: check_answer_label: "Buyer was a registered provider, housing association or local authority tenant immediately before this sale?" hint_text: "" question_text: "Was the buyer a private registered provider, housing association or local authority tenant immediately before this sale?" - + frombeds: - page_header: "About the buyers’ previous property" + page_header: "" check_answer_label: "Number of bedrooms in previous property" - hint_text: "For bedsits enter 1" + hint_text: "A bedsit has 1 bedroom." question_text: "How many bedrooms did the property have?" fromprop: @@ -113,13 +113,13 @@ en: question_text: "What was the initial percentage equity stake purchased?" mortgageused: - page_header: "Mortgage Amount" + page_header: "" check_answer_label: "Mortgage used" hint_text: "" question_text: "Was a mortgage used for the purchase of this property?" mortgage: - page_header: "Mortgage Amount" + page_header: "" check_answer_label: "Mortgage amount" hint_text: "Enter the amount of mortgage agreed with the mortgage lender. Exclude any deposits or cash payments. Numeric in pounds. Rounded to the nearest pound." question_text: "What is the mortgage amount?" @@ -135,7 +135,7 @@ en: check_answer_label: "Other Mortgage Lender" hint_text: "" question_text: "What is the other mortgage lender?" - + mortlen: page_header: "" check_answer_label: "Length of mortgage" @@ -147,7 +147,7 @@ en: check_answer_label: "Any other borrowing?" hint_text: "" question_text: "Does this include any extra borrowing?" - + deposit: page_header: "About the deposit" check_answer_label: "Deposit amount" @@ -165,7 +165,7 @@ en: check_answer_label: "Monthly rent" hint_text: "Amount paid before any charges" question_text: "What is the basic monthly rent?" - + leaseholdcharges: page_header: "" has_mscharge: @@ -199,10 +199,10 @@ en: check_answer_label: "Percentage discount" hint_text: "For Right to Buy (RTB), Preserved Right to Buy (PRTB), and Voluntary Right to Buy (VRTB)

If discount capped, enter capped %

If the property is being sold to an existing tenant under the RTB, PRTB, or VRTB schemes, enter the % discount from the full market value that is being given." question_text: "What was the percentage discount?" - + grant: page_header: "About the price of the property" check_answer_label: "Amount of any loan, grant or subsidy" hint_text: "For all schemes except Right to Buy (RTB), Preserved Right to Buy (PRTB), Voluntary Right to Buy (VRTB) and Rent to Buy" question_text: "What was the amount of any loan, grant, discount or subsidy given?" - \ No newline at end of file + diff --git a/config/locales/forms/2024/lettings/household_characteristics.en.yml b/config/locales/forms/2024/lettings/household_characteristics.en.yml index 04a311f06..0537a82bd 100644 --- a/config/locales/forms/2024/lettings/household_characteristics.en.yml +++ b/config/locales/forms/2024/lettings/household_characteristics.en.yml @@ -13,19 +13,19 @@ en: page_header: "" age1_known: check_answer_label: "" - hint_text: "The ’lead’ or ’main’ tenant is the person in the household who does the most paid work. If several people do the same amount of paid work, the lead tenant is whoever is the oldest." + hint_text: "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest." question_text: "Do you know the lead tenant’s age?" age1: check_answer_label: "Lead tenant’s age" hint_text: "" question_text: "Age" - + sex1: page_header: "" check_answer_label: "Lead tenant’s gender identity" hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth." question_text: "Which of these best describes the lead tenant’s gender identity?" - + ethnic_group: page_header: "" check_answer_label: "Lead tenant’s ethnic group" @@ -319,4 +319,4 @@ en: page_header: "" check_answer_label: "Person 8’s working situation" hint_text: "" - question_text: "Which of these best describes person 8’s working situation?" \ No newline at end of file + question_text: "Which of these best describes person 8’s working situation?" diff --git a/config/locales/forms/2024/lettings/income_and_benefits.en.yml b/config/locales/forms/2024/lettings/income_and_benefits.en.yml index bb3cc320e..8e364f611 100644 --- a/config/locales/forms/2024/lettings/income_and_benefits.en.yml +++ b/config/locales/forms/2024/lettings/income_and_benefits.en.yml @@ -103,6 +103,6 @@ en: hint_text: "You only need to give an approximate figure." question_text: "Can you estimate the outstanding amount?" tshortfall: - check_answer_label: "Estimated outstanding amountt" + check_answer_label: "Estimated outstanding amount" hint_text: "Also known as the ‘outstanding amount’." question_text: "Estimated outstanding amount" diff --git a/config/locales/forms/2024/sales/household_characteristics.en.yml b/config/locales/forms/2024/sales/household_characteristics.en.yml index 22f9427e8..5b06639fe 100644 --- a/config/locales/forms/2024/sales/household_characteristics.en.yml +++ b/config/locales/forms/2024/sales/household_characteristics.en.yml @@ -13,7 +13,7 @@ en: check_answer_label: "Buyer 1’s age" hint_text: "" question_text: "Age" - + sex1: page_header: "" check_answer_label: "Buyer 1’s gender identity" @@ -25,7 +25,7 @@ en: check_answer_label: "Buyer 1’s ethnic group" hint_text: "" question_text: "What is buyer 1’s ethnic group?" - + ethnic: ethnic_background_black: page_header: "" @@ -85,9 +85,9 @@ en: question_text: "What is buyer 2's relationship to buyer 1?" person: page_header: "" - check_answer_label: "Person 2’s relationship to Buyer 1" + check_answer_label: "Person 2’s relationship to buyer 1" hint_text: "" - question_text: "What is Person 2’s relationship to Buyer 1?" + question_text: "What is person 2’s relationship to buyer 1?" age2: buyer: @@ -121,14 +121,14 @@ en: page_header: "" check_answer_label: "Person 2’s gender identity" hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth." - question_text: "Which of these best describes Person 2’s gender identity?" + question_text: "Which of these best describes person 2’s gender identity?" ethnic_group2: page_header: "" check_answer_label: "Buyer 2’s ethnic group" hint_text: "" question_text: "What is buyer 2’s ethnic group?" - + ethnicbuy2: ethnic_background_black: page_header: "" @@ -167,7 +167,7 @@ en: check_answer_label: "Buyer 2’s nationality" hint_text: "" question_text: "Enter a nationality" - + ecstat2: buyer: page_header: "" @@ -178,14 +178,14 @@ en: page_header: "" check_answer_label: "Person 2’s working situation" hint_text: "" - question_text: "Which of these best describes Person 2’s working situation?" + question_text: "Which of these best describes person 2’s working situation?" buy2livein: page_header: "" check_answer_label: "Will buyer 2 live in the property?" hint_text: "" question_text: "Will buyer 2 live in the property?" - + hholdcount: joint_purchase: page_header: "" @@ -212,9 +212,9 @@ en: relat3: page_header: "" - check_answer_label: "Person 3’s relationship to Buyer 1" + check_answer_label: "Person 3’s relationship to buyer 1" hint_text: "" - question_text: "What is Person 3’s relationship to Buyer 1?" + question_text: "What is person 3’s relationship to buyer 1?" age3: page_header: "" @@ -226,18 +226,18 @@ en: check_answer_label: "Person 3’s age" hint_text: "" question_text: "Age" - + sex3: page_header: "" check_answer_label: "Person 3’s gender identity" hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth." - question_text: "Which of these best describes Person 3’s gender identity?" + question_text: "Which of these best describes person 3’s gender identity?" ecstat3: page_header: "" check_answer_label: "Person 3’s working situation" hint_text: "" - question_text: "Which of these best describes Person 3’s working situation?" + question_text: "Which of these best describes person 3’s working situation?" details_known_4: page_header: "" @@ -247,9 +247,9 @@ en: relat4: page_header: "" - check_answer_label: "Person 4’s relationship to Buyer 1" + check_answer_label: "Person 4’s relationship to buyer 1" hint_text: "" - question_text: "What is Person 4’s relationship to Buyer 1?" + question_text: "What is person 4’s relationship to buyer 1?" age4: page_header: "" @@ -261,18 +261,18 @@ en: check_answer_label: "Person 4’s age" hint_text: "" question_text: "Age" - + sex4: page_header: "" check_answer_label: "Person 4’s gender identity" hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth." - question_text: "Which of these best describes Person 4’s gender identity?" + question_text: "Which of these best describes person 4’s gender identity?" ecstat4: page_header: "" check_answer_label: "Person 4’s working situation" hint_text: "" - question_text: "Which of these best describes Person 4’s working situation?" + question_text: "Which of these best describes person 4’s working situation?" details_known_5: page_header: "" @@ -282,9 +282,9 @@ en: relat5: page_header: "" - check_answer_label: "Person 5’s relationship to Buyer 1" + check_answer_label: "Person 5’s relationship to buyer 1" hint_text: "" - question_text: "What is Person 5’s relationship to Buyer 1?" + question_text: "What is person 5’s relationship to buyer 1?" age5: page_header: "" @@ -296,18 +296,18 @@ en: check_answer_label: "Person 5’s age" hint_text: "" question_text: "Age" - + sex5: page_header: "" check_answer_label: "Person 5’s gender identity" hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth." - question_text: "Which of these best describes Person 5’s gender identity?" + question_text: "Which of these best describes person 5’s gender identity?" ecstat5: page_header: "" check_answer_label: "Person 5’s working situation" hint_text: "" - question_text: "Which of these best describes Person 5’s working situation?" + question_text: "Which of these best describes person 5’s working situation?" details_known_6: page_header: "" @@ -317,9 +317,9 @@ en: relat6: page_header: "" - check_answer_label: "Person 6’s relationship to Buyer 1" + check_answer_label: "Person 6’s relationship to buyer 1" hint_text: "" - question_text: "What is Person 6’s relationship to Buyer 1?" + question_text: "What is person 6’s relationship to buyer 1?" age6: page_header: "" @@ -331,15 +331,15 @@ en: check_answer_label: "Person 6’s age" hint_text: "" question_text: "Age" - + sex6: page_header: "" check_answer_label: "Person 6’s gender identity" hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth." - question_text: "Which of these best describes Person 6’s gender identity?" + question_text: "Which of these best describes person 6’s gender identity?" ecstat6: page_header: "" check_answer_label: "Person 6’s working situation" hint_text: "" - question_text: "Which of these best describes Person 6’s working situation?" + question_text: "Which of these best describes person 6’s working situation?" diff --git a/config/locales/forms/2024/sales/income_benefits_and_savings.en.yml b/config/locales/forms/2024/sales/income_benefits_and_savings.en.yml index 0654c9e1f..8d5ec0772 100644 --- a/config/locales/forms/2024/sales/income_benefits_and_savings.en.yml +++ b/config/locales/forms/2024/sales/income_benefits_and_savings.en.yml @@ -13,7 +13,7 @@ en: check_answer_label: "Buyer 1’s gross annual income" hint_text: "Provide the gross annual income (i.e. salary before tax) plus the annual amount of benefits, Universal Credit or pensions, and income from investments." question_text: "Buyer 1’s gross annual income" - + inc1mort: page_header: "" check_answer_label: "Buyer 1’s income used for mortgage application" @@ -48,14 +48,14 @@ en: check_answer_label: "Housing-related benefits buyer received before buying this property" hint_text: "" question_text: "Was the buyer receiving any of these housing-related benefits immediately before buying this property?" - + savings: joint_purchase: page_header: "" savingsnk: check_answer_label: "Buyers’ total savings known?" hint_text: "" - question_text: "Do you know how much the 'buyers' had in savings before they paid any deposit for the property?" + question_text: "Do you know how much the buyers had in savings before they paid any deposit for the property?" savings: check_answer_label: "Buyers’ total savings before any deposit paid" hint_text: "Include any savings, investments, ISAs, premium bonds, shares, or money held in a bank or building society account." @@ -87,4 +87,4 @@ en: page_header: "" check_answer_label: "Previous property shared ownership?" hint_text: "For any buyer" - question_text: "Was the previous property under shared ownership?" \ No newline at end of file + question_text: "Was the previous property under shared ownership?" diff --git a/config/locales/forms/2024/sales/sale_information.en.yml b/config/locales/forms/2024/sales/sale_information.en.yml index d0031416e..92acca9cd 100644 --- a/config/locales/forms/2024/sales/sale_information.en.yml +++ b/config/locales/forms/2024/sales/sale_information.en.yml @@ -28,7 +28,7 @@ en: staircasing: page_header: "" check_answer_label: "Staircasing transaction" - hint_text: "A staircasing transaction is when the household purchases more shares in their property, increasing the proportion they own and decreasing the proportion the housing association owns. Once the household purchases 100% of the shares, they own the property" + hint_text: "A staircasing transaction is when the household purchases more shares in their property, increasing the proportion they own and decreasing the proportion the housing association owns. Once the household purchases 100% of the shares, they own the property." question_text: "Is this a staircasing transaction?" about_staircasing: page_header: "About the staircasing transaction" @@ -73,7 +73,7 @@ en: check_answer_label: "Household rehoused under a local authority nominations agreement?" hint_text: "A local authority nominations agreement is a written agreement between a local authority and private registered provider (PRP) that some or all of its sales vacancies are offered to local authorities for rehousing" question_text: "Was the household rehoused under a 'local authority nominations agreement'?" - + soctenant: joint_purchase: page_header: "" @@ -85,11 +85,11 @@ en: check_answer_label: "Buyer was a registered provider, housing association or local authority tenant immediately before this sale?" hint_text: "" question_text: "Was the buyer a private registered provider, housing association or local authority tenant immediately before this sale?" - + frombeds: - page_header: "About the buyers’ previous property" + page_header: "" check_answer_label: "Number of bedrooms in previous property" - hint_text: "For bedsits enter 1" + hint_text: "A bedsit has 1 bedroom." question_text: "How many bedrooms did the property have?" fromprop: @@ -117,13 +117,13 @@ en: question_text: "What was the initial percentage equity stake purchased?" mortgageused: - page_header: "Mortgage Amount" + page_header: "" check_answer_label: "Mortgage used" hint_text: "" question_text: "Was a mortgage used for the purchase of this property?" mortgage: - page_header: "Mortgage Amount" + page_header: "" check_answer_label: "Mortgage amount" hint_text: "Enter the amount of mortgage agreed with the mortgage lender. Exclude any deposits or cash payments. Numeric in pounds. Rounded to the nearest pound." question_text: "What is the mortgage amount?" @@ -139,7 +139,7 @@ en: check_answer_label: "Other Mortgage Lender" hint_text: "" question_text: "What is the other mortgage lender?" - + mortlen: page_header: "" check_answer_label: "Length of mortgage" @@ -151,7 +151,7 @@ en: check_answer_label: "Any other borrowing?" hint_text: "" question_text: "Does this include any extra borrowing?" - + deposit: page_header: "About the deposit" check_answer_label: "Deposit amount" @@ -169,7 +169,7 @@ en: check_answer_label: "Monthly rent" hint_text: "Amount paid before any charges" question_text: "What is the basic monthly rent?" - + leaseholdcharges: page_header: "" has_mscharge: @@ -198,10 +198,10 @@ en: check_answer_label: "Percentage discount" hint_text: "For Right to Buy (RTB), Preserved Right to Buy (PRTB), and Voluntary Right to Buy (VRTB)

If discount capped, enter capped %

If the property is being sold to an existing tenant under the RTB, PRTB, or VRTB schemes, enter the % discount from the full market value that is being given." question_text: "What was the percentage discount?" - + grant: page_header: "About the price of the property" check_answer_label: "Amount of any loan, grant or subsidy" hint_text: "For all schemes except Right to Buy (RTB), Preserved Right to Buy (PRTB), Voluntary Right to Buy (VRTB) and Rent to Buy" question_text: "What was the amount of any loan, grant, discount or subsidy given?" - \ No newline at end of file + diff --git a/config/locales/forms/2025/lettings/household_characteristics.en.yml b/config/locales/forms/2025/lettings/household_characteristics.en.yml index 1aef9297a..b6d7ad7e9 100644 --- a/config/locales/forms/2025/lettings/household_characteristics.en.yml +++ b/config/locales/forms/2025/lettings/household_characteristics.en.yml @@ -13,7 +13,7 @@ en: page_header: "" age1_known: check_answer_label: "" - hint_text: "The ’lead’ or ’main’ tenant is the person in the household who does the most paid work. If several people do the same amount of paid work, the lead tenant is whoever is the oldest." + hint_text: "The lead tenant is the person in the household who does the most paid work. If several people do the same paid work, the lead tenant is whoever is the oldest." question_text: "Do you know the lead tenant’s age?" age1: check_answer_label: "Lead tenant’s age" diff --git a/config/locales/forms/2025/lettings/income_and_benefits.en.yml b/config/locales/forms/2025/lettings/income_and_benefits.en.yml index 55e193ff7..5b8ed26c0 100644 --- a/config/locales/forms/2025/lettings/income_and_benefits.en.yml +++ b/config/locales/forms/2025/lettings/income_and_benefits.en.yml @@ -103,6 +103,6 @@ en: hint_text: "You only need to give an approximate figure." question_text: "Can you estimate the outstanding amount?" tshortfall: - check_answer_label: "Estimated outstanding amountt" + check_answer_label: "Estimated outstanding amount" hint_text: "Also known as the ‘outstanding amount’." question_text: "Estimated outstanding amount" diff --git a/config/locales/forms/2025/sales/household_characteristics.en.yml b/config/locales/forms/2025/sales/household_characteristics.en.yml index 3f9f503be..a217c578c 100644 --- a/config/locales/forms/2025/sales/household_characteristics.en.yml +++ b/config/locales/forms/2025/sales/household_characteristics.en.yml @@ -121,7 +121,7 @@ en: page_header: "" check_answer_label: "Person 2’s gender identity" hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth." - question_text: "Which of these best describes Person 2’s gender identity?" + question_text: "Which of these best describes person 2’s gender identity?" ethnic_group2: page_header: "" @@ -178,7 +178,7 @@ en: page_header: "" check_answer_label: "Person 2’s working situation" hint_text: "" - question_text: "Which of these best describes Person 2’s working situation?" + question_text: "Which of these best describes person 2’s working situation?" buy2livein: page_header: "" @@ -231,13 +231,13 @@ en: page_header: "" check_answer_label: "Person 3’s gender identity" hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth." - question_text: "Which of these best describes Person 3’s gender identity?" + question_text: "Which of these best describes person 3’s gender identity?" ecstat3: page_header: "" check_answer_label: "Person 3’s working situation" hint_text: "" - question_text: "Which of these best describes Person 3’s working situation?" + question_text: "Which of these best describes person 3’s working situation?" details_known_4: page_header: "" @@ -266,13 +266,13 @@ en: page_header: "" check_answer_label: "Person 4’s gender identity" hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth." - question_text: "Which of these best describes Person 4’s gender identity?" + question_text: "Which of these best describes person 4’s gender identity?" ecstat4: page_header: "" check_answer_label: "Person 4’s working situation" hint_text: "" - question_text: "Which of these best describes Person 4’s working situation?" + question_text: "Which of these best describes person 4’s working situation?" details_known_5: page_header: "" @@ -301,13 +301,13 @@ en: page_header: "" check_answer_label: "Person 5’s gender identity" hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth." - question_text: "Which of these best describes Person 5’s gender identity?" + question_text: "Which of these best describes person 5’s gender identity?" ecstat5: page_header: "" check_answer_label: "Person 5’s working situation" hint_text: "" - question_text: "Which of these best describes Person 5’s working situation?" + question_text: "Which of these best describes person 5’s working situation?" details_known_6: page_header: "" @@ -336,10 +336,10 @@ en: page_header: "" check_answer_label: "Person 6’s gender identity" hint_text: "This should be however they personally choose to identify from the options below. This may or may not be the same as their biological sex or the sex they were assigned at birth." - question_text: "Which of these best describes Person 6’s gender identity?" + question_text: "Which of these best describes person 6’s gender identity?" ecstat6: page_header: "" check_answer_label: "Person 6’s working situation" hint_text: "" - question_text: "Which of these best describes Person 6’s working situation?" + question_text: "Which of these best describes person 6’s working situation?" diff --git a/config/locales/forms/2025/sales/income_benefits_and_savings.en.yml b/config/locales/forms/2025/sales/income_benefits_and_savings.en.yml index 20beb0b85..b38c203f7 100644 --- a/config/locales/forms/2025/sales/income_benefits_and_savings.en.yml +++ b/config/locales/forms/2025/sales/income_benefits_and_savings.en.yml @@ -55,7 +55,7 @@ en: savingsnk: check_answer_label: "Buyers’ total savings known?" hint_text: "" - question_text: "Do you know how much the 'buyers' had in savings before they paid any deposit for the property?" + question_text: "Do you know how much the buyers had in savings before they paid any deposit for the property?" savings: check_answer_label: "Buyers’ total savings before any deposit paid" hint_text: "Include any savings, investments, ISAs, premium bonds, shares, or money held in a bank or building society account." diff --git a/config/locales/forms/2025/sales/sale_information.en.yml b/config/locales/forms/2025/sales/sale_information.en.yml index 0535caca1..278c8a235 100644 --- a/config/locales/forms/2025/sales/sale_information.en.yml +++ b/config/locales/forms/2025/sales/sale_information.en.yml @@ -105,9 +105,9 @@ en: question_text: "Was the buyer a private registered provider, housing association or local authority tenant immediately before this sale?" frombeds: - page_header: "About the buyers’ previous property" + page_header: "" check_answer_label: "Number of bedrooms in previous property" - hint_text: "For bedsits enter 1" + hint_text: "A bedsit has 1 bedroom." question_text: "How many bedrooms did the property have?" fromprop: @@ -145,13 +145,13 @@ en: question_text: "What was the percentage shared purchased in the initial transaction?" mortgageused: - page_header: "Mortgage Amount" + page_header: "" check_answer_label: "Mortgage used?" hint_text: "" question_text: "Was a mortgage used for the purchase of this property?" mortgage: - page_header: "Mortgage Amount" + page_header: "" check_answer_label: "Mortgage amount" hint_text: "Enter the amount of mortgage agreed with the mortgage lender. Exclude any deposits or cash payments. Numeric in pounds. Rounded to the nearest pound." question_text: "What is the mortgage amount?" diff --git a/config/locales/forms/2025/sales/setup.en.yml b/config/locales/forms/2025/sales/setup.en.yml index 6f7c5da98..c4cc9a42c 100644 --- a/config/locales/forms/2025/sales/setup.en.yml +++ b/config/locales/forms/2025/sales/setup.en.yml @@ -42,7 +42,7 @@ en: staircasing: page_header: "" check_answer_label: "Staircasing transaction" - hint_text: "A staircasing transaction is when the household purchases more shares in their property, increasing the proportion they own and decreasing the proportion the housing association owns. Once the household purchases 100% of the shares, they own the property" + hint_text: "A staircasing transaction is when the household purchases more shares in their property, increasing the proportion they own and decreasing the proportion the housing association owns. Once the household purchases 100% of the shares, they own the property." question_text: "Is this a staircasing transaction?" type: diff --git a/spec/models/form/sales/pages/equity_spec.rb b/spec/models/form/sales/pages/equity_spec.rb index 83a5dfaa3..e27538263 100644 --- a/spec/models/form/sales/pages/equity_spec.rb +++ b/spec/models/form/sales/pages/equity_spec.rb @@ -7,6 +7,10 @@ RSpec.describe Form::Sales::Pages::Equity, type: :model do let(:page_definition) { nil } let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1))) } + before do + allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(false) + end + it "has correct subsection" do expect(page.subsection).to eq(subsection) end diff --git a/spec/models/form/sales/pages/value_shared_ownership_spec.rb b/spec/models/form/sales/pages/value_shared_ownership_spec.rb index eb1b1099f..82e6c1055 100644 --- a/spec/models/form/sales/pages/value_shared_ownership_spec.rb +++ b/spec/models/form/sales/pages/value_shared_ownership_spec.rb @@ -7,6 +7,10 @@ RSpec.describe Form::Sales::Pages::ValueSharedOwnership, type: :model do let(:page_definition) { nil } let(:subsection) { instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1))) } + before do + allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(false) + end + it "has correct subsection" do expect(page.subsection).to eq(subsection) end diff --git a/spec/models/form/sales/questions/equity_spec.rb b/spec/models/form/sales/questions/equity_spec.rb index 5083af9e8..3e6b9d85a 100644 --- a/spec/models/form/sales/questions/equity_spec.rb +++ b/spec/models/form/sales/questions/equity_spec.rb @@ -7,6 +7,10 @@ RSpec.describe Form::Sales::Questions::Equity, type: :model do let(:question_definition) { nil } let(:page) { instance_double(Form::Page, id: "initial_equity", subsection: instance_double(Form::Subsection, form: instance_double(Form, start_date: Time.zone.local(2023, 4, 1)))) } + before do + allow(page.subsection.form).to receive(:start_year_2025_or_later?).and_return(false) + end + it "has correct page" do expect(question.page).to eq(page) end From ef49307d9bbda4d44366bb5feefd4dd4869ad237 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:16:55 +0000 Subject: [PATCH 19/36] CLDC-3361 Update scheme back link paths (#2802) * Update scheme back link paths * Correclty route if has other client group changed from no to yes --- app/controllers/schemes_controller.rb | 4 +- app/helpers/schemes_helper.rb | 18 +++++ app/views/schemes/confirm_secondary.html.erb | 2 +- app/views/schemes/details.html.erb | 2 +- .../schemes/primary_client_group.html.erb | 8 +- .../schemes/secondary_client_group.html.erb | 2 +- app/views/schemes/support.html.erb | 2 +- spec/requests/schemes_controller_spec.rb | 74 ++++++++++++++++++- 8 files changed, 98 insertions(+), 14 deletions(-) diff --git a/app/controllers/schemes_controller.rb b/app/controllers/schemes_controller.rb index 3dc642345..a76c9db52 100644 --- a/app/controllers/schemes_controller.rb +++ b/app/controllers/schemes_controller.rb @@ -150,7 +150,7 @@ class SchemesController < ApplicationController @scheme.update!(secondary_client_group: nil) if @scheme.has_other_client_group == "No" if scheme_params[:confirmed] == "true" || @scheme.confirmed? if check_answers && should_direct_via_secondary_client_group_page?(page) - redirect_to scheme_secondary_client_group_path(@scheme, referrer: "check-answers") + redirect_to scheme_secondary_client_group_path(@scheme, referrer: "has-other-client-group") else @scheme.locations.update!(confirmed: true) flash[:notice] = if scheme_previously_confirmed @@ -162,7 +162,7 @@ class SchemesController < ApplicationController end elsif check_answers if should_direct_via_secondary_client_group_page?(page) - redirect_to scheme_secondary_client_group_path(@scheme, referrer: "check-answers") + redirect_to scheme_secondary_client_group_path(@scheme, referrer: "has-other-client-group") else redirect_to scheme_check_answers_path(@scheme) end diff --git a/app/helpers/schemes_helper.rb b/app/helpers/schemes_helper.rb index 12d86aba8..f262c08ff 100644 --- a/app/helpers/schemes_helper.rb +++ b/app/helpers/schemes_helper.rb @@ -101,6 +101,24 @@ module SchemesHelper organisation.owned_schemes.duplicate_sets.any? || organisation.owned_schemes.any? { |scheme| scheme.locations.duplicate_sets.any? } end + def scheme_back_button_path(scheme, current_page) + return scheme_check_answers_path(scheme) if request.params[:referrer] == "check-answers" + return scheme_confirm_secondary_client_group_path(scheme, referrer: "check-answers") if request.params[:referrer] == "has-other-client-group" + + case current_page + when "details" + schemes_path + when "primary_client_group" + scheme_details_path(scheme) + when "confirm_secondary_client_group" + scheme_primary_client_group_path(scheme) + when "secondary_client_group" + scheme_confirm_secondary_client_group_path(scheme) + when "support" + scheme.has_other_client_group == "Yes" ? scheme_secondary_client_group_path(scheme) : scheme_confirm_secondary_client_group_path(scheme) + end + end + private ActivePeriod = Struct.new(:from, :to) diff --git a/app/views/schemes/confirm_secondary.html.erb b/app/views/schemes/confirm_secondary.html.erb index 9b715c264..9feca87bc 100644 --- a/app/views/schemes/confirm_secondary.html.erb +++ b/app/views/schemes/confirm_secondary.html.erb @@ -1,7 +1,7 @@ <% content_for :title, "Does this scheme provide for another client group?" %> <% content_for :before_content do %> - <%= govuk_back_link(href: :back) %> + <%= govuk_back_link(href: scheme_back_button_path(@scheme, "confirm_secondary_client_group")) %> <% end %> <%= render partial: "organisations/headings", locals: { main: "Does this scheme provide for another client group?", sub: @scheme.service_name } %> diff --git a/app/views/schemes/details.html.erb b/app/views/schemes/details.html.erb index 4b23ab016..d1943cabe 100644 --- a/app/views/schemes/details.html.erb +++ b/app/views/schemes/details.html.erb @@ -1,7 +1,7 @@ <% content_for :title, "Create a new supported housing scheme" %> <% content_for :before_content do %> - <%= govuk_back_link(href: :back) %> + <%= govuk_back_link(href: scheme_back_button_path(@scheme, "details")) %> <% end %> <%= form_for(@scheme, method: :patch) do |f| %> diff --git a/app/views/schemes/primary_client_group.html.erb b/app/views/schemes/primary_client_group.html.erb index 55dc504b4..68da2f35c 100644 --- a/app/views/schemes/primary_client_group.html.erb +++ b/app/views/schemes/primary_client_group.html.erb @@ -1,13 +1,7 @@ <% content_for :title, "What client group is this scheme intended for?" %> -<% if request.referer&.include?("new") %> - <% back_button_path = scheme_details_path(@scheme) %> -<% else %> - <% back_button_path = :back %> -<% end %> - <% content_for :before_content do %> - <%= govuk_back_link(href: back_button_path) %> + <%= govuk_back_link(href: scheme_back_button_path(@scheme, "primary_client_group")) %> <% end %> <%= form_for(@scheme, method: :patch) do |f| %> diff --git a/app/views/schemes/secondary_client_group.html.erb b/app/views/schemes/secondary_client_group.html.erb index cd50283e3..8200702d2 100644 --- a/app/views/schemes/secondary_client_group.html.erb +++ b/app/views/schemes/secondary_client_group.html.erb @@ -1,7 +1,7 @@ <% content_for :title, "What is the other client group?" %> <% content_for :before_content do %> - <%= govuk_back_link(href: :back) %> + <%= govuk_back_link(href: scheme_back_button_path(@scheme, "secondary_client_group")) %> <% end %> <%= form_for(@scheme, method: :patch) do |f| %> diff --git a/app/views/schemes/support.html.erb b/app/views/schemes/support.html.erb index a5d30ed7f..eca72014f 100644 --- a/app/views/schemes/support.html.erb +++ b/app/views/schemes/support.html.erb @@ -1,7 +1,7 @@ <% content_for :title, "What support does this scheme provide?" %> <% content_for :before_content do %> - <%= govuk_back_link(href: :back) %> + <%= govuk_back_link(href: scheme_back_button_path(@scheme, "support")) %> <% end %> <%= form_for(@scheme, method: :patch) do |f| %> diff --git a/spec/requests/schemes_controller_spec.rb b/spec/requests/schemes_controller_spec.rb index 83ba11fd9..2eb2330c8 100644 --- a/spec/requests/schemes_controller_spec.rb +++ b/spec/requests/schemes_controller_spec.rb @@ -2035,6 +2035,17 @@ RSpec.describe SchemesController, type: :request do expect(page).to have_content("What client group is this scheme intended for?") end + it "has correct back link" do + expect(page).to have_link("Back", href: "/schemes/#{scheme.id}/details") + end + + context "and accessed from check answers" do + it "has correct back link" do + get "/schemes/#{scheme.id}/primary-client-group?referrer=check-answers" + expect(page).to have_link("Back", href: "/schemes/#{scheme.id}/check-answers") + end + end + context "when attempting to access primary-client-group scheme page for another organisation" do before do get "/schemes/#{another_scheme.id}/primary-client-group" @@ -2112,6 +2123,17 @@ RSpec.describe SchemesController, type: :request do expect(page).to have_content("Does this scheme provide for another client group?") end + it "has correct back link" do + expect(page).to have_link("Back", href: "/schemes/#{scheme.id}/primary-client-group") + end + + context "and accessed from check answers" do + it "has correct back link" do + get "/schemes/#{scheme.id}/confirm-secondary-client-group?referrer=check-answers" + expect(page).to have_link("Back", href: "/schemes/#{scheme.id}/check-answers") + end + end + context "when attempting to access confirm-secondary-client-group scheme page for another organisation" do before do get "/schemes/#{another_scheme.id}/confirm-secondary-client-group" @@ -2189,6 +2211,24 @@ RSpec.describe SchemesController, type: :request do expect(page).to have_content("What is the other client group?") end + it "has correct back link" do + expect(page).to have_link("Back", href: "/schemes/#{scheme.id}/confirm-secondary-client-group") + end + + context "and accessed from check answers" do + it "has correct back link" do + get "/schemes/#{scheme.id}/secondary-client-group?referrer=check-answers" + expect(page).to have_link("Back", href: "/schemes/#{scheme.id}/check-answers") + end + end + + context "and accessed from has other client group" do + it "has correct back link" do + get "/schemes/#{scheme.id}/secondary-client-group?referrer=has-other-client-group" + expect(page).to have_link("Back", href: "/schemes/#{scheme.id}/confirm-secondary-client-group?referrer=check-answers") + end + end + context "when attempting to access secondary-client-group scheme page for another organisation" do before do get "/schemes/#{another_scheme.id}/secondary-client-group" @@ -2258,7 +2298,7 @@ RSpec.describe SchemesController, type: :request do context "when signed in as a data coordinator" do let(:user) { create(:user, :data_coordinator) } - let(:scheme) { create(:scheme, owning_organisation: user.organisation, confirmed: nil) } + let(:scheme) { create(:scheme, owning_organisation: user.organisation, confirmed: nil, has_other_client_group: "Yes") } let(:another_scheme) { create(:scheme, confirmed: nil) } before do @@ -2271,6 +2311,27 @@ RSpec.describe SchemesController, type: :request do expect(page).to have_content("What support does this scheme provide?") end + context "when scheme has secondary client group" do + it "has correct back link" do + expect(page).to have_link("Back", href: "/schemes/#{scheme.id}/secondary-client-group") + end + end + + context "when scheme has no secondary client group" do + let(:scheme) { create(:scheme, owning_organisation: user.organisation, confirmed: nil, has_other_client_group: "No") } + + it "has correct back link" do + expect(page).to have_link("Back", href: "/schemes/#{scheme.id}/confirm-secondary-client-group") + end + end + + context "and accessed from check answers" do + it "has correct back link" do + get "/schemes/#{scheme.id}/support?referrer=check-answers" + expect(page).to have_link("Back", href: "/schemes/#{scheme.id}/check-answers") + end + end + context "when attempting to access secondary-client-group scheme page for another organisation" do before do get "/schemes/#{another_scheme.id}/support" @@ -2433,6 +2494,17 @@ RSpec.describe SchemesController, type: :request do expect(page).to have_content("Create a new supported housing scheme") end + it "has correct back link" do + expect(page).to have_link("Back", href: "/schemes") + end + + context "and accessed from check answers" do + it "has correct back link" do + get "/schemes/#{scheme.id}/details?referrer=check-answers" + expect(page).to have_link("Back", href: "/schemes/#{scheme.id}/check-answers") + end + end + context "when attempting to access check-answers scheme page for another organisation" do before do get "/schemes/#{another_scheme.id}/details" From 46331b92adcb0854a12aa0eff76dc8bd227553e6 Mon Sep 17 00:00:00 2001 From: Rachael Booth Date: Thu, 5 Dec 2024 09:29:01 +0000 Subject: [PATCH 20/36] CLDC-3268: Update github action dependencies (#2852) --- .github/workflows/aws_deploy.yml | 22 ++++++-------- .../workflows/review_teardown_pipeline.yml | 6 ++-- .github/workflows/staging_pipeline.yml | 30 +++++++++---------- 3 files changed, 27 insertions(+), 31 deletions(-) diff --git a/.github/workflows/aws_deploy.yml b/.github/workflows/aws_deploy.yml index c72c9d874..5af3c2d08 100644 --- a/.github/workflows/aws_deploy.yml +++ b/.github/workflows/aws_deploy.yml @@ -41,19 +41,17 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v3 + uses: aws-actions/configure-aws-credentials@v4 with: aws-region: ${{ env.aws_region }} role-to-assume: ${{ env.app_repo_role }} - name: Login to Amazon ECR id: ecr-login - uses: aws-actions/amazon-ecr-login@v1 - with: - mask-password: "true" + uses: aws-actions/amazon-ecr-login@v2 - name: Check if image with tag already exists run: | @@ -81,16 +79,14 @@ jobs: steps: - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v3 + uses: aws-actions/configure-aws-credentials@v4 with: aws-region: ${{ env.aws_region }} role-to-assume: ${{ env.app_repo_role }} - name: Login to Amazon ECR id: ecr-login - uses: aws-actions/amazon-ecr-login@v1 - with: - mask-password: "true" + uses: aws-actions/amazon-ecr-login@v2 - name: Get timestamp id: timestamp @@ -112,7 +108,7 @@ jobs: echo "image=$registry/$repository:$readable_tag" >> $GITHUB_ENV - name: Configure AWS credentials for environment - uses: aws-actions/configure-aws-credentials@v3 + uses: aws-actions/configure-aws-credentials@v4 with: aws-region: ${{ env.aws_region }} role-to-assume: arn:aws:iam::${{ inputs.aws_account_id }}:role/${{ inputs.aws_role_prefix }}-deployment @@ -133,7 +129,7 @@ jobs: image: ${{ env.image }} - name: Update ad hoc task definition - uses: aws-actions/amazon-ecs-deploy-task-definition@v1 + uses: aws-actions/amazon-ecs-deploy-task-definition@v2 with: task-definition: ${{ steps.ad-hoc-task-def.outputs.task-definition }} @@ -185,7 +181,7 @@ jobs: image: ${{ env.image }} - name: Deploy updated application - uses: aws-actions/amazon-ecs-deploy-task-definition@v1 + uses: aws-actions/amazon-ecs-deploy-task-definition@v2 with: cluster: ${{ inputs.aws_task_prefix }}-app service: ${{ inputs.aws_task_prefix }}-app @@ -207,7 +203,7 @@ jobs: image: ${{ env.image }} - name: Deploy updated sidekiq - uses: aws-actions/amazon-ecs-deploy-task-definition@v1 + uses: aws-actions/amazon-ecs-deploy-task-definition@v2 with: cluster: ${{ inputs.aws_task_prefix }}-app service: ${{ inputs.aws_task_prefix }}-sidekiq diff --git a/.github/workflows/review_teardown_pipeline.yml b/.github/workflows/review_teardown_pipeline.yml index 4303e4063..8925b3340 100644 --- a/.github/workflows/review_teardown_pipeline.yml +++ b/.github/workflows/review_teardown_pipeline.yml @@ -25,13 +25,13 @@ jobs: steps: - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v3 + uses: aws-actions/configure-aws-credentials@v4 with: aws-region: ${{ env.aws_region }} role-to-assume: ${{ env.app_repo_role }} - name: Configure AWS credentials for review environment - uses: aws-actions/configure-aws-credentials@v3 + uses: aws-actions/configure-aws-credentials@v4 with: aws-region: ${{ env.aws_region }} role-to-assume: arn:aws:iam::${{ env.aws_account_id }}:role/${{ env.aws_role_prefix }}-deployment @@ -46,7 +46,7 @@ jobs: network=$(aws ecs describe-services --cluster $cluster --services $service --query services[0].networkConfiguration) overrides='{ "containerOverrides" : [{ "name" : "app", "command" : ["bundle", "exec", "rake", "db:drop"]}]}' arn=$(aws ecs run-task --cluster $cluster --task-definition $ad_hoc_task_definition --network-configuration "$network" --overrides "$overrides" --group migrations --launch-type FARGATE --query tasks[0].taskArn) - echo "Waiting for db prepare task to complete" + echo "Waiting for db drop task to complete" temp=${arn##*/} id=${temp%*\"} aws ecs wait tasks-stopped --cluster $cluster --tasks $id diff --git a/.github/workflows/staging_pipeline.yml b/.github/workflows/staging_pipeline.yml index e5447f4ef..aff0fe5f0 100644 --- a/.github/workflows/staging_pipeline.yml +++ b/.github/workflows/staging_pipeline.yml @@ -55,7 +55,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 @@ -63,7 +63,7 @@ jobs: bundler-cache: true - name: Set up Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: cache: yarn node-version: 20 @@ -113,7 +113,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 @@ -121,7 +121,7 @@ jobs: bundler-cache: true - name: Set up Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: cache: yarn node-version: 20 @@ -171,7 +171,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 @@ -179,7 +179,7 @@ jobs: bundler-cache: true - name: Set up Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: cache: yarn node-version: 20 @@ -230,7 +230,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 @@ -238,7 +238,7 @@ jobs: bundler-cache: true - name: Set up Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: cache: yarn node-version: 20 @@ -289,7 +289,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 @@ -297,7 +297,7 @@ jobs: bundler-cache: true - name: Set up Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: cache: yarn node-version: 20 @@ -320,7 +320,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 @@ -328,7 +328,7 @@ jobs: bundler-cache: true - name: Set up Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: cache: yarn node-version: 20 @@ -347,7 +347,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 @@ -379,13 +379,13 @@ jobs: steps: - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v3 + uses: aws-actions/configure-aws-credentials@v4 with: aws-region: ${{ env.aws_region }} role-to-assume: ${{ env.app_repo_role }} - name: Configure AWS credentials for the environment - uses: aws-actions/configure-aws-credentials@v3 + uses: aws-actions/configure-aws-credentials@v4 with: aws-region: eu-west-2 role-to-assume: arn:aws:iam::107155005276:role/core-staging-deployment From 5fa69ce0b62c844ad122de5adae813ec71deac74 Mon Sep 17 00:00:00 2001 From: Rachael Booth Date: Thu, 5 Dec 2024 11:38:14 +0000 Subject: [PATCH 21/36] CLDC-2128: Validate supplied year in bulk upload pages (#2839) * CLDC-2128: Validate supplied year in bulk upload pages * Fix lint * Refactor --- .../bulk_upload_lettings_logs_controller.rb | 23 +++- .../bulk_upload_sales_logs_controller.rb | 19 ++- .../forms/bulk_upload_lettings/needstype.rb | 39 ------ .../forms/needstype.erb | 23 ---- ...lk_upload_lettings_logs_controller_spec.rb | 111 ++++++++++++++++++ .../bulk_upload_sales_logs_controller_spec.rb | 111 ++++++++++++++++++ 6 files changed, 257 insertions(+), 69 deletions(-) delete mode 100644 app/models/forms/bulk_upload_lettings/needstype.rb delete mode 100644 app/views/bulk_upload_lettings_logs/forms/needstype.erb diff --git a/app/controllers/bulk_upload_lettings_logs_controller.rb b/app/controllers/bulk_upload_lettings_logs_controller.rb index a8ad14f9e..39bc05f7e 100644 --- a/app/controllers/bulk_upload_lettings_logs_controller.rb +++ b/app/controllers/bulk_upload_lettings_logs_controller.rb @@ -1,6 +1,7 @@ class BulkUploadLettingsLogsController < ApplicationController before_action :authenticate_user! - before_action :validate_data_protection_agrement_signed! + before_action :validate_data_protection_agreement_signed! + before_action :validate_year!, except: %w[start] def start if have_choice_of_year? @@ -24,12 +25,26 @@ class BulkUploadLettingsLogsController < ApplicationController private - def validate_data_protection_agrement_signed! + def validate_data_protection_agreement_signed! return if @current_user.organisation.data_protection_confirmed? redirect_to lettings_logs_path end + def validate_year! + return if params[:id] == "year" + return if params[:id] == "guidance" && params.dig(:form, :year).nil? + + allowed_years = [current_year] + allowed_years.push(current_year - 1) if FormHandler.instance.lettings_in_crossover_period? + allowed_years.push(current_year + 1) if FeatureToggle.allow_future_form_use? + + provided_year = params.dig(:form, :year)&.to_i + return if allowed_years.include?(provided_year) + + render_not_found + end + def current_year FormHandler.instance.current_collection_start_year end @@ -48,8 +63,6 @@ private Forms::BulkUploadLettings::PrepareYourFile.new(form_params) when "guidance" Forms::BulkUploadLettings::Guidance.new(form_params.merge(referrer: params[:referrer])) - when "needstype" - Forms::BulkUploadLettings::Needstype.new(form_params) when "upload-your-file" Forms::BulkUploadLettings::UploadYourFile.new(form_params.merge(current_user:)) when "checking-file" @@ -60,6 +73,6 @@ private end def form_params - params.fetch(:form, {}).permit(:year, :needstype, :file, :organisation_id) + params.fetch(:form, {}).permit(:year, :file, :organisation_id) end end diff --git a/app/controllers/bulk_upload_sales_logs_controller.rb b/app/controllers/bulk_upload_sales_logs_controller.rb index 2fd312d10..cb04cea95 100644 --- a/app/controllers/bulk_upload_sales_logs_controller.rb +++ b/app/controllers/bulk_upload_sales_logs_controller.rb @@ -1,6 +1,7 @@ class BulkUploadSalesLogsController < ApplicationController before_action :authenticate_user! - before_action :validate_data_protection_agrement_signed! + before_action :validate_data_protection_agreement_signed! + before_action :validate_year!, except: %w[start] def start if have_choice_of_year? @@ -24,12 +25,26 @@ class BulkUploadSalesLogsController < ApplicationController private - def validate_data_protection_agrement_signed! + def validate_data_protection_agreement_signed! return if @current_user.organisation.data_protection_confirmed? redirect_to sales_logs_path end + def validate_year! + return if params[:id] == "year" + return if params[:id] == "guidance" && params.dig(:form, :year).nil? + + allowed_years = [current_year] + allowed_years.push(current_year - 1) if FormHandler.instance.sales_in_crossover_period? + allowed_years.push(current_year + 1) if FeatureToggle.allow_future_form_use? + + provided_year = params.dig(:form, :year)&.to_i + return if allowed_years.include?(provided_year) + + render_not_found + end + def current_year FormHandler.instance.current_collection_start_year end diff --git a/app/models/forms/bulk_upload_lettings/needstype.rb b/app/models/forms/bulk_upload_lettings/needstype.rb deleted file mode 100644 index b15c05b52..000000000 --- a/app/models/forms/bulk_upload_lettings/needstype.rb +++ /dev/null @@ -1,39 +0,0 @@ -module Forms - module BulkUploadLettings - class Needstype - include ActiveModel::Model - include ActiveModel::Attributes - include Rails.application.routes.url_helpers - - attribute :needstype, :integer - attribute :year, :integer - attribute :organisation_id, :integer - - validates :needstype, presence: true - - def view_path - "bulk_upload_lettings_logs/forms/needstype" - end - - def options - [OpenStruct.new(id: 1, name: "General needs"), OpenStruct.new(id: 2, name: "Supported housing")] - end - - def back_path - bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year:, needstype:, organisation_id: }.compact) - end - - def next_path - bulk_upload_lettings_log_path(id: "upload-your-file", form: { year:, needstype:, organisation_id: }.compact) - end - - def year_combo - "#{year} to #{year + 1}" - end - - def save! - true - end - end - end -end diff --git a/app/views/bulk_upload_lettings_logs/forms/needstype.erb b/app/views/bulk_upload_lettings_logs/forms/needstype.erb deleted file mode 100644 index 644dd9f5f..000000000 --- a/app/views/bulk_upload_lettings_logs/forms/needstype.erb +++ /dev/null @@ -1,23 +0,0 @@ -<% content_for :before_content do %> - <%= govuk_back_link href: @form.back_path %> -<% end %> - -
-
- <%= form_with model: @form, scope: :form, url: bulk_upload_lettings_log_path(id: "needstype"), method: :patch do |f| %> - <%= f.govuk_error_summary %> - <%= f.hidden_field :year %> - <%= f.hidden_field :organisation_id %> - - <%= f.govuk_collection_radio_buttons :needstype, - @form.options, - :id, - :name, - hint: { text: I18n.t("hints.bulk_upload.needstype") }, - legend: { text: "What is the needs type?", size: "l" }, - caption: { text: "Upload lettings logs in bulk (#{@form.year_combo})", size: "l" } %> - - <%= f.govuk_submit %> - <% end %> -
-
diff --git a/spec/requests/bulk_upload_lettings_logs_controller_spec.rb b/spec/requests/bulk_upload_lettings_logs_controller_spec.rb index 18b208e74..c9a22768d 100644 --- a/spec/requests/bulk_upload_lettings_logs_controller_spec.rb +++ b/spec/requests/bulk_upload_lettings_logs_controller_spec.rb @@ -73,5 +73,116 @@ RSpec.describe BulkUploadLettingsLogsController, type: :request do expect(response.body).to include("How to upload logs in bulk") end end + + context "when no year is specified" do + it "shows guidance page with links defaulting to the current year" do + get "/lettings-logs/bulk-upload-logs/guidance" + + expect(response.body).to include("Download the lettings bulk upload template (#{current_collection_start_year} to #{current_collection_start_year + 1})") + end + end + + context "when an invalid year is specified" do + it "shows not found" do + get "/lettings-logs/bulk-upload-logs/guidance?form%5Byear%5D=10000" + + expect(response).to be_not_found + end + end + end + + describe "GET /lettings-logs/bulk-upload-logs/year" do + it "does not require a year to be specified" do + get "/lettings-logs/bulk-upload-logs/year" + + expect(response).to be_ok + end + end + + pages_requiring_year_specification = %w[prepare-your-file upload-your-file checking-file] + pages_requiring_year_specification.each do |page_id| + describe "GET /lettings-logs/bulk-upload-logs/#{page_id}" do + context "when no year is provided" do + it "returns not found" do + get "/lettings-logs/bulk-upload-logs/#{page_id}" + + expect(response).to be_not_found + end + end + + context "when requesting the previous year in a crossover period" do + before do + allow(FormHandler.instance).to receive(:lettings_in_crossover_period?).and_return(true) + end + + it "succeeds" do + get "/lettings-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year - 1}" + + expect(response).to be_ok + end + end + + context "when requesting the previous year outside a crossover period" do + before do + allow(FormHandler.instance).to receive(:lettings_in_crossover_period?).and_return(false) + end + + it "returns not found" do + get "/lettings-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year - 1}" + + expect(response).to be_not_found + end + end + + context "when requesting the current year" do + it "succeeds" do + get "/lettings-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year}" + + expect(response).to be_ok + end + end + + if page_id != "prepare-your-file" + context "when requesting the next year with future form use toggled on" do + before do + allow(FeatureToggle).to receive(:allow_future_form_use?).and_return(true) + end + + it "succeeds" do + get "/lettings-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year + 1}" + + expect(response).to be_ok + end + end + end + + context "when requesting the next year with future form use toggled off" do + before do + allow(FeatureToggle).to receive(:allow_future_form_use?).and_return(false) + end + + it "returns not found" do + get "/lettings-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year + 1}" + + expect(response).to be_not_found + end + end + + context "when requesting a far future year" do + it "returns not found" do + get "/lettings-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=9990" + + expect(response).to be_not_found + end + end + + context "when requesting a nonsense value for year" do + it "returns not found" do + get "/lettings-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=thisisnotayear" + + expect(response).to be_not_found + end + end + end end end diff --git a/spec/requests/bulk_upload_sales_logs_controller_spec.rb b/spec/requests/bulk_upload_sales_logs_controller_spec.rb index 7cd6d4be8..4c20482be 100644 --- a/spec/requests/bulk_upload_sales_logs_controller_spec.rb +++ b/spec/requests/bulk_upload_sales_logs_controller_spec.rb @@ -73,5 +73,116 @@ RSpec.describe BulkUploadSalesLogsController, type: :request do expect(response.body).to include("How to upload logs in bulk") end end + + context "when no year is specified" do + it "shows guidance page with links defaulting to the current year" do + get "/sales-logs/bulk-upload-logs/guidance" + + expect(response.body).to include("Download the sales bulk upload template (#{current_collection_start_year} to #{current_collection_start_year + 1})") + end + end + + context "when an invalid year is specified" do + it "shows not found" do + get "/sales-logs/bulk-upload-logs/guidance?form%5Byear%5D=10000" + + expect(response).to be_not_found + end + end + end + + describe "GET /sales-logs/bulk-upload-logs/year" do + it "does not require a year to be specified" do + get "/sales-logs/bulk-upload-logs/year" + + expect(response).to be_ok + end + end + + pages_requiring_year_specification = %w[prepare-your-file upload-your-file checking-file] + pages_requiring_year_specification.each do |page_id| + describe "GET /sales-logs/bulk-upload-logs/#{page_id}" do + context "when no year is provided" do + it "returns not found" do + get "/sales-logs/bulk-upload-logs/#{page_id}" + + expect(response).to be_not_found + end + end + + context "when requesting the previous year in a crossover period" do + before do + allow(FormHandler.instance).to receive(:sales_in_crossover_period?).and_return(true) + end + + it "succeeds" do + get "/sales-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year - 1}" + + expect(response).to be_ok + end + end + + context "when requesting the previous year outside a crossover period" do + before do + allow(FormHandler.instance).to receive(:sales_in_crossover_period?).and_return(false) + end + + it "returns not found" do + get "/sales-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year - 1}" + + expect(response).to be_not_found + end + end + + context "when requesting the current year" do + it "succeeds" do + get "/sales-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year}" + + expect(response).to be_ok + end + end + + if page_id != "prepare-your-file" + context "when requesting the next year with future form use toggled on" do + before do + allow(FeatureToggle).to receive(:allow_future_form_use?).and_return(true) + end + + it "succeeds" do + get "/sales-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year + 1}" + + expect(response).to be_ok + end + end + end + + context "when requesting the next year with future form use toggled off" do + before do + allow(FeatureToggle).to receive(:allow_future_form_use?).and_return(false) + end + + it "returns not found" do + get "/sales-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=#{current_collection_start_year + 1}" + + expect(response).to be_not_found + end + end + + context "when requesting a far future year" do + it "returns not found" do + get "/sales-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=9990" + + expect(response).to be_not_found + end + end + + context "when requesting a nonsense value for year" do + it "returns not found" do + get "/sales-logs/bulk-upload-logs/#{page_id}?form%5Byear%5D=thisisnotayear" + + expect(response).to be_not_found + end + end + end end end From 3b8858f59455e46022254314f199e3e7d78d7b7a Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:30:53 +0000 Subject: [PATCH 22/36] If scheme is incomplete and deactivated treat is as deactivated (#2842) --- app/models/scheme.rb | 2 +- spec/models/scheme_spec.rb | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/models/scheme.rb b/app/models/scheme.rb index 1cd56ac7d..a9b479342 100644 --- a/app/models/scheme.rb +++ b/app/models/scheme.rb @@ -329,9 +329,9 @@ class Scheme < ApplicationRecord def status_at(date) return :deleted if discarded_at.present? - return :incomplete unless confirmed && locations.confirmed.any? return :deactivated if owning_organisation.status_at(date) == :deactivated || owning_organisation.status_at(date) == :merged || (open_deactivation&.deactivation_date.present? && date >= open_deactivation.deactivation_date) + return :incomplete unless confirmed && locations.confirmed.any? return :deactivating_soon if open_deactivation&.deactivation_date.present? && date < open_deactivation.deactivation_date return :reactivating_soon if last_deactivation_before(date)&.reactivation_date.present? && date < last_deactivation_before(date).reactivation_date return :activating_soon if startdate.present? && date < startdate diff --git a/spec/models/scheme_spec.rb b/spec/models/scheme_spec.rb index 65174388d..21b60cd52 100644 --- a/spec/models/scheme_spec.rb +++ b/spec/models/scheme_spec.rb @@ -390,6 +390,13 @@ RSpec.describe Scheme, type: :model do scheme.startdate = Time.zone.today + 2.weeks expect(scheme.status).to eq(:activating_soon) end + + it "returns deactivated if scheme is deactivated and incomplete" do + scheme.update!(support_type: nil, confirmed: nil) + FactoryBot.create(:scheme_deactivation_period, deactivation_date: Time.zone.yesterday, scheme:) + scheme.reload + expect(scheme.status).to eq(:deactivated) + end end context "when there have been previous deactivations" do From b8a576d87f0f39caa27068bf490c9ae9601ab27e Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Fri, 6 Dec 2024 13:18:12 +0000 Subject: [PATCH 23/36] CLDC-3048 Fix pluralisation for 1 result (#2841) * Fix pluralisation for 1 result * Fix world * Update tests --- .../search_result_caption_component.html.erb | 2 +- app/views/bulk_upload_shared/uploads.html.erb | 2 +- app/views/locations/index.html.erb | 2 +- app/views/logs/_log_list.html.erb | 2 +- app/views/logs/index.html.erb | 2 +- app/views/logs/update_logs.html.erb | 2 +- .../managing_agents.html.erb | 4 +-- .../stock_owners.html.erb | 4 +-- .../organisations/_organisation_list.html.erb | 2 +- app/views/organisations/index.html.erb | 2 +- app/views/organisations/logs.html.erb | 2 +- app/views/schemes/_scheme_list.html.erb | 2 +- app/views/users/_user_list.html.erb | 2 +- .../search_result_caption_component_spec.rb | 26 ++++++++++++++++++- .../requests/lettings_logs_controller_spec.rb | 4 +-- ...anisation_relationships_controller_spec.rb | 12 ++++----- .../requests/organisations_controller_spec.rb | 8 +++--- spec/requests/sales_logs_controller_spec.rb | 4 +-- 18 files changed, 54 insertions(+), 30 deletions(-) diff --git a/app/components/search_result_caption_component.html.erb b/app/components/search_result_caption_component.html.erb index b8a9382b7..b2a28a505 100644 --- a/app/components/search_result_caption_component.html.erb +++ b/app/components/search_result_caption_component.html.erb @@ -7,7 +7,7 @@ <%= count %> <%= item_label.pluralize(count) %> matching filters
<% else %> - <%= count %> total <%= item %> + <%= count %> total <%= item.pluralize(count) %> <% end %> diff --git a/app/views/bulk_upload_shared/uploads.html.erb b/app/views/bulk_upload_shared/uploads.html.erb index 958887453..a9d134c60 100644 --- a/app/views/bulk_upload_shared/uploads.html.erb +++ b/app/views/bulk_upload_shared/uploads.html.erb @@ -1,4 +1,4 @@ -<% item_label = format_label(@pagy.count, "uploads") %> +<% item_label = format_label(@pagy.count, "upload") %> <% title = format_title(@searched, bulk_upload_title(controller.controller_name), current_user, item_label, @pagy.count, nil) %> <% content_for :title, title %> diff --git a/app/views/locations/index.html.erb b/app/views/locations/index.html.erb index 23550f894..8ef5bcb56 100644 --- a/app/views/locations/index.html.erb +++ b/app/views/locations/index.html.erb @@ -32,7 +32,7 @@ count: @pagy.count, item_label:, total_count: @total_count, - item: "locations", + item: "location", filters_count: applied_filters_count(@filter_type), )) %> <% end %> diff --git a/app/views/logs/_log_list.html.erb b/app/views/logs/_log_list.html.erb index b5290c117..24714f247 100644 --- a/app/views/logs/_log_list.html.erb +++ b/app/views/logs/_log_list.html.erb @@ -1,7 +1,7 @@

- <%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "logs", filters_count: applied_filters_count(@filter_type))) %> + <%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "log", filters_count: applied_filters_count(@filter_type))) %> <% if logs&.any? %> <%= govuk_link_to "Download (CSV)", csv_download_url, type: "text/csv", class: "govuk-!-margin-right-4", style: "white-space: nowrap" %> <% if @current_user.support? %> diff --git a/app/views/logs/index.html.erb b/app/views/logs/index.html.erb index f142a2580..c51466097 100644 --- a/app/views/logs/index.html.erb +++ b/app/views/logs/index.html.erb @@ -1,4 +1,4 @@ -<% item_label = format_label(@pagy.count, "logs") %> +<% item_label = format_label(@pagy.count, "log") %> <% title = format_title(@searched, "#{log_type_for_controller(controller).capitalize} logs", current_user, item_label, @pagy.count, nil) %> <% content_for :title, title %> diff --git a/app/views/logs/update_logs.html.erb b/app/views/logs/update_logs.html.erb index 1ab1fa31c..985adecf3 100644 --- a/app/views/logs/update_logs.html.erb +++ b/app/views/logs/update_logs.html.erb @@ -1,4 +1,4 @@ -<% item_label = format_label(@pagy.count, "logs") %> +<% item_label = format_label(@pagy.count, "log") %> <% title = "Update logs" %> <% content_for :title, title %> diff --git a/app/views/organisation_relationships/managing_agents.html.erb b/app/views/organisation_relationships/managing_agents.html.erb index 726533e53..d09056d86 100644 --- a/app/views/organisation_relationships/managing_agents.html.erb +++ b/app/views/organisation_relationships/managing_agents.html.erb @@ -1,4 +1,4 @@ -<% item_label = format_label(@pagy.count, "managing agents") %> +<% item_label = format_label(@pagy.count, "managing agent") %> <% if current_user.support? %> <%= render partial: "organisations/headings", locals: { main: @organisation.name, sub: nil } %> @@ -44,7 +44,7 @@ pagy: @pagy, searched: @searched, item_label:, - search_item: "managing agents", + search_item: "managing agent", total_count: @total_count, remove_path: ->(org_id) { managing_agents_remove_organisation_path(target_organisation_id: org_id) }, } %> diff --git a/app/views/organisation_relationships/stock_owners.html.erb b/app/views/organisation_relationships/stock_owners.html.erb index 41b7af06d..3bbba6bf8 100644 --- a/app/views/organisation_relationships/stock_owners.html.erb +++ b/app/views/organisation_relationships/stock_owners.html.erb @@ -1,4 +1,4 @@ -<% item_label = format_label(@pagy.count, "stock owners") %> +<% item_label = format_label(@pagy.count, "stock owner") %> <% if current_user.support? %> <%= render partial: "organisations/headings", locals: { main: @organisation.name, sub: nil } %> <%= render SubNavigationComponent.new(items: secondary_items(request.path, @organisation.id)) %> @@ -41,7 +41,7 @@ pagy: @pagy, searched: @searched, item_label:, - search_item: "stock owners", + search_item: "stock owner", total_count: @total_count, remove_path: ->(org_id) { stock_owners_remove_organisation_path(target_organisation_id: org_id) }, } %> diff --git a/app/views/organisations/_organisation_list.html.erb b/app/views/organisations/_organisation_list.html.erb index 67cc9c7a3..16309a5eb 100644 --- a/app/views/organisations/_organisation_list.html.erb +++ b/app/views/organisations/_organisation_list.html.erb @@ -1,7 +1,7 @@
<%= govuk_table do |table| %> <%= table.with_caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %> - <%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "organisations", filters_count: applied_filters_count(@filter_type))) %> + <%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "organisation", filters_count: applied_filters_count(@filter_type))) %> <% end %> <%= table.with_head do |head| %> <%= head.with_row do |row| %> diff --git a/app/views/organisations/index.html.erb b/app/views/organisations/index.html.erb index 411d792c1..1de12ab77 100644 --- a/app/views/organisations/index.html.erb +++ b/app/views/organisations/index.html.erb @@ -1,4 +1,4 @@ -<% item_label = format_label(@pagy.count, "organisations") %> +<% item_label = format_label(@pagy.count, "organisation") %> <% title = format_title(@searched, "Organisations", current_user, item_label, @pagy.count, nil) %> <% content_for :title, title %> diff --git a/app/views/organisations/logs.html.erb b/app/views/organisations/logs.html.erb index e172a76a9..e318ff6ee 100644 --- a/app/views/organisations/logs.html.erb +++ b/app/views/organisations/logs.html.erb @@ -1,4 +1,4 @@ -<% item_label = format_label(@pagy.count, "logs") %> +<% item_label = format_label(@pagy.count, "log") %> <% title = format_title(@searched, action_name.humanize, current_user, item_label, @pagy.count, @organisation.name) %> <% content_for :title, title %> diff --git a/app/views/schemes/_scheme_list.html.erb b/app/views/schemes/_scheme_list.html.erb index 967295236..1c11e86d1 100644 --- a/app/views/schemes/_scheme_list.html.erb +++ b/app/views/schemes/_scheme_list.html.erb @@ -2,7 +2,7 @@ <%= govuk_table do |table| %> <%= table.with_caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %> - <%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "schemes", filters_count: applied_filters_count(@filter_type))) %> + <%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "scheme", filters_count: applied_filters_count(@filter_type))) %> <% if @schemes&.any? %> <%= govuk_link_to "Download schemes (CSV)", schemes_csv_download_url, type: "text/csv", class: "govuk-!-margin-right-4", style: "white-space: nowrap" %> <%= govuk_link_to "Download locations (CSV)", locations_csv_download_url, type: "text/csv", class: "govuk-!-margin-right-4", style: "white-space: nowrap" %> diff --git a/app/views/users/_user_list.html.erb b/app/views/users/_user_list.html.erb index 436c0def2..82a82b33b 100644 --- a/app/views/users/_user_list.html.erb +++ b/app/views/users/_user_list.html.erb @@ -1,7 +1,7 @@
<%= govuk_table do |table| %> <%= table.with_caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %> - <%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "users", filters_count: applied_filters_count(@filter_type))) %> + <%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "user", filters_count: applied_filters_count(@filter_type))) %> <% if current_user.support? %> <% query = searched.present? ? "?search=#{searched}" : nil %> <%= govuk_link_to "Download (CSV)", "#{request.path}.csv#{query}", type: "text/csv", style: "white-space: nowrap" %> diff --git a/spec/components/search_result_caption_component_spec.rb b/spec/components/search_result_caption_component_spec.rb index 25cbc1bdd..05ac09630 100644 --- a/spec/components/search_result_caption_component_spec.rb +++ b/spec/components/search_result_caption_component_spec.rb @@ -5,7 +5,7 @@ RSpec.describe SearchResultCaptionComponent, type: :component do let(:count) { 2 } let(:item_label) { "user" } let(:total_count) { 3 } - let(:item) { "schemes" } + let(:item) { "scheme" } let(:filters_count) { 1 } let(:result) { render_inline(described_class.new(searched:, count:, item_label:, total_count:, item:, filters_count:)) } @@ -21,6 +21,14 @@ RSpec.describe SearchResultCaptionComponent, type: :component do it "renders table caption including the search results and total" do expect(result.to_html).to eq("\n 2 users matching search
\n
\n") end + + context "with 1 result" do + let(:count) { 1 } + + it "renders table caption including the search results and total" do + expect(result.to_html).to eq("\n 1 user matching search
\n
\n") + end + end end context "when filter results are found" do @@ -29,6 +37,14 @@ RSpec.describe SearchResultCaptionComponent, type: :component do it "renders table caption including the search results and total" do expect(result.to_html).to eq("\n 2 users matching filters
\n
\n") end + + context "with 1 result" do + let(:count) { 1 } + + it "renders table caption including the search results and total" do + expect(result.to_html).to eq("\n 1 user matching filters
\n
\n") + end + end end context "when no search/filter is applied" do @@ -38,6 +54,14 @@ RSpec.describe SearchResultCaptionComponent, type: :component do it "renders table caption with total count only" do expect(result.to_html).to eq("\n \n 2 total schemes\n \n\n") end + + context "with 1 result" do + let(:count) { 1 } + + it "renders table caption with total count only" do + expect(result.to_html).to eq("\n \n 1 total scheme\n \n\n") + end + end end context "when nothing is found" do diff --git a/spec/requests/lettings_logs_controller_spec.rb b/spec/requests/lettings_logs_controller_spec.rb index e795b76ea..d84a6d714 100644 --- a/spec/requests/lettings_logs_controller_spec.rb +++ b/spec/requests/lettings_logs_controller_spec.rb @@ -759,7 +759,7 @@ RSpec.describe LettingsLogsController, type: :request do it "has search results in the title" do get "/lettings-logs?search=#{log_to_search.id}", headers:, params: {} - expect(page).to have_title("Lettings logs (1 logs matching ‘#{log_to_search.id}’) - Submit social housing lettings and sales data (CORE) - GOV.UK") + expect(page).to have_title("Lettings logs (1 log matching ‘#{log_to_search.id}’) - Submit social housing lettings and sales data (CORE) - GOV.UK") end it "shows lettings logs matching the id" do @@ -895,7 +895,7 @@ RSpec.describe LettingsLogsController, type: :request do end it "shows the total log count" do - expect(CGI.unescape_html(response.body)).to match("1 total logs") + expect(CGI.unescape_html(response.body)).to match("1 total log") end it "does not show the pagination links" do diff --git a/spec/requests/organisation_relationships_controller_spec.rb b/spec/requests/organisation_relationships_controller_spec.rb index 79706fff9..8e8dc4f2d 100644 --- a/spec/requests/organisation_relationships_controller_spec.rb +++ b/spec/requests/organisation_relationships_controller_spec.rb @@ -53,7 +53,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do end it "shows the pagination count" do - expect(page).to have_content("1 total stock owners") + expect(page).to have_content("1 total stock owner") end context "when adding a stock owner" do @@ -149,7 +149,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do end it "shows the pagination count" do - expect(page).to have_content("1 total managing agents") + expect(page).to have_content("1 total managing agent") end context "and current organisation is deactivated" do @@ -345,7 +345,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do end it "shows the pagination count" do - expect(page).to have_content("1 total stock owners") + expect(page).to have_content("1 total stock owner") end end @@ -481,7 +481,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do end it "shows the pagination count" do - expect(page).to have_content("1 total managing agents") + expect(page).to have_content("1 total managing agent") end end @@ -647,7 +647,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do end it "shows the pagination count" do - expect(page).to have_content("1 total stock owners") + expect(page).to have_content("1 total stock owner") end context "when adding a stock owner" do @@ -697,7 +697,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do end it "shows the pagination count" do - expect(page).to have_content("1 total managing agents") + expect(page).to have_content("1 total managing agent") end it "shows remove link(s)" do diff --git a/spec/requests/organisations_controller_spec.rb b/spec/requests/organisations_controller_spec.rb index ae3297d59..26723a563 100644 --- a/spec/requests/organisations_controller_spec.rb +++ b/spec/requests/organisations_controller_spec.rb @@ -1203,7 +1203,7 @@ RSpec.describe OrganisationsController, type: :request do it "has search results in the title" do get "/organisations/#{organisation.id}/lettings-logs?search=#{log_to_search.id}", headers: headers, params: {} - expect(page).to have_title("#{organisation.name} (1 logs matching ‘#{log_to_search.id}’) - Submit social housing lettings and sales data (CORE) - GOV.UK") + expect(page).to have_title("#{organisation.name} (1 log matching ‘#{log_to_search.id}’) - Submit social housing lettings and sales data (CORE) - GOV.UK") end it "has search term in the search box" do @@ -1352,7 +1352,7 @@ RSpec.describe OrganisationsController, type: :request do it "has search results in the title" do get "/organisations/#{organisation.id}/sales-logs?search=#{log_to_search.id}", headers: headers, params: {} - expect(page).to have_title("#{organisation.name} (1 logs matching ‘#{log_to_search.id}’) - Submit social housing lettings and sales data (CORE) - GOV.UK") + expect(page).to have_title("#{organisation.name} (1 log matching ‘#{log_to_search.id}’) - Submit social housing lettings and sales data (CORE) - GOV.UK") end it "shows sales logs matching the id" do @@ -1616,11 +1616,11 @@ RSpec.describe OrganisationsController, type: :request do end it "updates the table caption" do - expect(page).to have_content("1 organisations matching search") + expect(page).to have_content("1 organisation matching search") end it "has search in the title" do - expect(page).to have_title("Organisations (1 organisations matching ‘#{search_param}’) - Submit social housing lettings and sales data (CORE) - GOV.UK") + expect(page).to have_title("Organisations (1 organisation matching ‘#{search_param}’) - Submit social housing lettings and sales data (CORE) - GOV.UK") end context "when the search term matches more than 1 result" do diff --git a/spec/requests/sales_logs_controller_spec.rb b/spec/requests/sales_logs_controller_spec.rb index dc056c1ed..6c882cc87 100644 --- a/spec/requests/sales_logs_controller_spec.rb +++ b/spec/requests/sales_logs_controller_spec.rb @@ -610,7 +610,7 @@ RSpec.describe SalesLogsController, type: :request do it "has search results in the title" do get "/sales-logs?search=#{log_to_search.id}", headers: headers, params: {} - expect(page).to have_title("Sales logs (1 logs matching ‘#{log_to_search.id}’) - Submit social housing lettings and sales data (CORE) - GOV.UK") + expect(page).to have_title("Sales logs (1 log matching ‘#{log_to_search.id}’) - Submit social housing lettings and sales data (CORE) - GOV.UK") end it "shows sales logs matching the id" do @@ -692,7 +692,7 @@ RSpec.describe SalesLogsController, type: :request do end it "shows the total log count" do - expect(CGI.unescape_html(response.body)).to match("1 total logs") + expect(CGI.unescape_html(response.body)).to match("1 total log") end it "does not show the pagination links" do From af79b58741dc9e18098ccade83adedf9d1936e75 Mon Sep 17 00:00:00 2001 From: Rachael Booth Date: Fri, 6 Dec 2024 16:51:49 +0000 Subject: [PATCH 24/36] Separate services tests to improve pipeline times (#2853) * Display slowest test * Attempt to improve test times on github * Run services tests in parallel * Try keeping services in Tests * Revert "Try keeping services in Tests" This reverts commit ef2e1bf4d255ed9a4bf87abda2b4d2b33787d406. * Fix parallel setup for services tests * Remove profiling --- .github/workflows/run_tests.yml | 411 +++++++++++++++++++++++++ .github/workflows/staging_pipeline.yml | 347 +-------------------- spec/db/seeds_spec.rb | 7 +- 3 files changed, 417 insertions(+), 348 deletions(-) create mode 100644 .github/workflows/run_tests.yml diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml new file mode 100644 index 000000000..1965a4034 --- /dev/null +++ b/.github/workflows/run_tests.yml @@ -0,0 +1,411 @@ +name: Run Tests + +on: + workflow_call: + pull_request: + types: + - opened + - synchronize + merge_group: + workflow_dispatch: + +defaults: + run: + shell: bash + +jobs: + test: + name: Tests + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:13.5 + env: + POSTGRES_PASSWORD: password + POSTGRES_USER: postgres + POSTGRES_DB: data_collector + ports: + - 5432:5432 + # Needed because the Postgres container does not provide a health check + # tmpfs makes database faster by using RAM + options: >- + --mount type=tmpfs,destination=/var/lib/postgresql/data + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + env: + RAILS_ENV: test + GEMFILE_RUBY_VERSION: 3.1.1 + DB_HOST: localhost + DB_DATABASE: data_collector + DB_USERNAME: postgres + DB_PASSWORD: password + RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} + PARALLEL_TEST_PROCESSORS: 4 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + cache: yarn + node-version: 20 + + - name: Create database + run: | + bundle exec rake parallel:setup + + - name: Compile assets + run: | + bundle exec rake assets:precompile + + - name: Run tests + run: | + bundle exec rake parallel:spec['spec\/(?!features|models|requests|services)'] + + feature_test: + name: Feature Tests + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:13.5 + env: + POSTGRES_PASSWORD: password + POSTGRES_USER: postgres + POSTGRES_DB: data_collector + ports: + - 5432:5432 + # Needed because the Postgres container does not provide a health check + # tmpfs makes database faster by using RAM + options: >- + --mount type=tmpfs,destination=/var/lib/postgresql/data + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + env: + RAILS_ENV: test + GEMFILE_RUBY_VERSION: 3.1.1 + DB_HOST: localhost + DB_DATABASE: data_collector + DB_USERNAME: postgres + DB_PASSWORD: password + RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + cache: yarn + node-version: 20 + + - name: Create database + run: | + bundle exec rake db:prepare + + - name: Compile assets + run: | + bundle exec rake assets:precompile + + - name: Run tests + run: | + bundle exec rspec spec/features --fail-fast --exclude-pattern "spec/features/accessibility_spec.rb" + + model_test: + name: Model tests + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:13.5 + env: + POSTGRES_PASSWORD: password + POSTGRES_USER: postgres + POSTGRES_DB: data_collector + ports: + - 5432:5432 + # Needed because the Postgres container does not provide a health check + # tmpfs makes database faster by using RAM + options: >- + --mount type=tmpfs,destination=/var/lib/postgresql/data + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + env: + RAILS_ENV: test + GEMFILE_RUBY_VERSION: 3.1.1 + DB_HOST: localhost + DB_DATABASE: data_collector + DB_USERNAME: postgres + DB_PASSWORD: password + RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + cache: yarn + node-version: 20 + + - name: Create database + run: | + bundle exec rake db:prepare + + - name: Compile assets + run: | + bundle exec rake assets:precompile + + - name: Run tests + run: | + bundle exec rspec spec/models --fail-fast + + requests_test: + name: Requests tests + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:13.5 + env: + POSTGRES_PASSWORD: password + POSTGRES_USER: postgres + POSTGRES_DB: data_collector + ports: + - 5432:5432 + # Needed because the Postgres container does not provide a health check + # tmpfs makes database faster by using RAM + options: >- + --mount type=tmpfs,destination=/var/lib/postgresql/data + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + env: + RAILS_ENV: test + GEMFILE_RUBY_VERSION: 3.1.1 + DB_HOST: localhost + DB_DATABASE: data_collector + DB_USERNAME: postgres + DB_PASSWORD: password + RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} + PARALLEL_TEST_PROCESSORS: 4 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + cache: yarn + node-version: 20 + + - name: Create database + run: | + bundle exec rake parallel:setup + + - name: Compile assets + run: | + bundle exec rake assets:precompile + + - name: Run tests + run: | + bundle exec rake parallel:spec['spec/requests'] + + services_test: + name: Services Tests + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:13.5 + env: + POSTGRES_PASSWORD: password + POSTGRES_USER: postgres + POSTGRES_DB: data_collector + ports: + - 5432:5432 + # Needed because the Postgres container does not provide a health check + # tmpfs makes database faster by using RAM + options: >- + --mount type=tmpfs,destination=/var/lib/postgresql/data + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + env: + RAILS_ENV: test + GEMFILE_RUBY_VERSION: 3.1.1 + DB_HOST: localhost + DB_DATABASE: data_collector + DB_USERNAME: postgres + DB_PASSWORD: password + RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} + PARALLEL_TEST_PROCESSORS: 4 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + cache: yarn + node-version: 20 + + - name: Create database + run: | + bundle exec rake parallel:setup + + - name: Compile assets + run: | + bundle exec rake assets:precompile + + - name: Run tests + run: | + bundle exec rake parallel:spec['spec\/services'] + + accessibility_test: + name: Accessibility tests + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:13.5 + env: + POSTGRES_PASSWORD: password + POSTGRES_USER: postgres + POSTGRES_DB: data_collector + ports: + - 5432:5432 + # Needed because the Postgres container does not provide a health check + # tmpfs makes database faster by using RAM + options: >- + --mount type=tmpfs,destination=/var/lib/postgresql/data + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + env: + RAILS_ENV: test + GEMFILE_RUBY_VERSION: 3.1.1 + DB_HOST: localhost + DB_DATABASE: data_collector + DB_USERNAME: postgres + DB_PASSWORD: password + RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} + PARALLEL_TEST_PROCESSORS: 4 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + cache: yarn + node-version: 20 + + - name: Create database + run: | + bundle exec rake parallel:setup + + - name: Compile assets + run: | + bundle exec rake assets:precompile + + - name: Run tests + run: | + bundle exec rspec spec/features/accessibility_spec.rb --fail-fast + + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + cache: yarn + node-version: 20 + + - name: Install packages and symlink local dependencies + run: | + yarn install --immutable --immutable-cache --check-cache + + - name: Lint + run: | + bundle exec rake lint + + audit: + name: Audit dependencies + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + bundler-cache: true + + - name: Audit + run: | + bundle exec bundler-audit diff --git a/.github/workflows/staging_pipeline.yml b/.github/workflows/staging_pipeline.yml index aff0fe5f0..a2e777db0 100644 --- a/.github/workflows/staging_pipeline.yml +++ b/.github/workflows/staging_pipeline.yml @@ -4,11 +4,6 @@ on: push: branches: - main - pull_request: - types: - - opened - - synchronize - merge_group: workflow_dispatch: defaults: @@ -21,347 +16,13 @@ env: repository: core jobs: - test: - name: Tests - runs-on: ubuntu-latest - - services: - postgres: - image: postgres:13.5 - env: - POSTGRES_PASSWORD: password - POSTGRES_USER: postgres - POSTGRES_DB: data_collector - ports: - - 5432:5432 - # Needed because the Postgres container does not provide a health check - # tmpfs makes database faster by using RAM - options: >- - --mount type=tmpfs,destination=/var/lib/postgresql/data - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - env: - RAILS_ENV: test - GEMFILE_RUBY_VERSION: 3.1.1 - DB_HOST: localhost - DB_DATABASE: data_collector - DB_USERNAME: postgres - DB_PASSWORD: password - RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} - PARALLEL_TEST_PROCESSORS: 4 - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - bundler-cache: true - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - cache: yarn - node-version: 20 - - - name: Create database - run: | - bundle exec rake parallel:setup - - - name: Compile assets - run: | - bundle exec rake assets:precompile - - - name: Run tests - run: | - bundle exec rake parallel:spec['spec\/(?!features|models|requests)'] - - feature_test: - name: Feature Tests - runs-on: ubuntu-latest - - services: - postgres: - image: postgres:13.5 - env: - POSTGRES_PASSWORD: password - POSTGRES_USER: postgres - POSTGRES_DB: data_collector - ports: - - 5432:5432 - # Needed because the Postgres container does not provide a health check - # tmpfs makes database faster by using RAM - options: >- - --mount type=tmpfs,destination=/var/lib/postgresql/data - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - env: - RAILS_ENV: test - GEMFILE_RUBY_VERSION: 3.1.1 - DB_HOST: localhost - DB_DATABASE: data_collector - DB_USERNAME: postgres - DB_PASSWORD: password - RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - bundler-cache: true - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - cache: yarn - node-version: 20 - - - name: Create database - run: | - bundle exec rake db:prepare - - - name: Compile assets - run: | - bundle exec rake assets:precompile - - - name: Run tests - run: | - bundle exec rspec spec/features --fail-fast --exclude-pattern "spec/features/accessibility_spec.rb" - - model_test: - name: Model tests - runs-on: ubuntu-latest - - services: - postgres: - image: postgres:13.5 - env: - POSTGRES_PASSWORD: password - POSTGRES_USER: postgres - POSTGRES_DB: data_collector - ports: - - 5432:5432 - # Needed because the Postgres container does not provide a health check - # tmpfs makes database faster by using RAM - options: >- - --mount type=tmpfs,destination=/var/lib/postgresql/data - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - env: - RAILS_ENV: test - GEMFILE_RUBY_VERSION: 3.1.1 - DB_HOST: localhost - DB_DATABASE: data_collector - DB_USERNAME: postgres - DB_PASSWORD: password - RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - bundler-cache: true - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - cache: yarn - node-version: 20 - - - name: Create database - run: | - bundle exec rake db:prepare - - - name: Compile assets - run: | - bundle exec rake assets:precompile - - - name: Run tests - run: | - bundle exec rspec spec/models --fail-fast - - requests_test: - name: Requests tests - runs-on: ubuntu-latest - - services: - postgres: - image: postgres:13.5 - env: - POSTGRES_PASSWORD: password - POSTGRES_USER: postgres - POSTGRES_DB: data_collector - ports: - - 5432:5432 - # Needed because the Postgres container does not provide a health check - # tmpfs makes database faster by using RAM - options: >- - --mount type=tmpfs,destination=/var/lib/postgresql/data - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - env: - RAILS_ENV: test - GEMFILE_RUBY_VERSION: 3.1.1 - DB_HOST: localhost - DB_DATABASE: data_collector - DB_USERNAME: postgres - DB_PASSWORD: password - RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} - PARALLEL_TEST_PROCESSORS: 4 - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - bundler-cache: true - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - cache: yarn - node-version: 20 - - - name: Create database - run: | - bundle exec rake parallel:setup - - - name: Compile assets - run: | - bundle exec rake assets:precompile - - - name: Run tests - run: | - bundle exec rake parallel:spec['spec/requests'] - - accessibility_test: - name: Accessibility tests - runs-on: ubuntu-latest - - services: - postgres: - image: postgres:13.5 - env: - POSTGRES_PASSWORD: password - POSTGRES_USER: postgres - POSTGRES_DB: data_collector - ports: - - 5432:5432 - # Needed because the Postgres container does not provide a health check - # tmpfs makes database faster by using RAM - options: >- - --mount type=tmpfs,destination=/var/lib/postgresql/data - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - env: - RAILS_ENV: test - GEMFILE_RUBY_VERSION: 3.1.1 - DB_HOST: localhost - DB_DATABASE: data_collector - DB_USERNAME: postgres - DB_PASSWORD: password - RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} - PARALLEL_TEST_PROCESSORS: 4 - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - bundler-cache: true - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - cache: yarn - node-version: 20 - - - name: Create database - run: | - bundle exec rake parallel:setup - - - name: Compile assets - run: | - bundle exec rake assets:precompile - - - name: Run tests - run: | - bundle exec rspec spec/features/accessibility_spec.rb --fail-fast - - lint: - name: Lint - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - bundler-cache: true - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - cache: yarn - node-version: 20 - - - name: Install packages and symlink local dependencies - run: | - yarn install --immutable --immutable-cache --check-cache - - - name: Lint - run: | - bundle exec rake lint - - audit: - name: Audit dependencies - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - bundler-cache: true - - - name: Audit - run: | - bundle exec bundler-audit + tests: + name: Run Tests + uses: ./.github/workflows/run_tests.yml aws_deploy: name: AWS Deploy - if: github.ref == 'refs/heads/main' - needs: [lint, test, feature_test, requests_test, model_test, audit] + needs: [tests] uses: ./.github/workflows/aws_deploy.yml with: aws_account_id: 107155005276 diff --git a/spec/db/seeds_spec.rb b/spec/db/seeds_spec.rb index 316f04ba6..6ae07ddb0 100644 --- a/spec/db/seeds_spec.rb +++ b/spec/db/seeds_spec.rb @@ -21,7 +21,8 @@ RSpec.describe "seeding process", type: task do allow(Rails.env).to receive(:review?).and_return(true) end - it "sets up correct data" do + # Doing this in one test should save ~2 minutes + it "sets up correct data idempotently" do expect { Rails.application.load_seed }.to change(User, :count) @@ -30,10 +31,6 @@ RSpec.describe "seeding process", type: task do .and change(Scheme, :count) .and change(Location, :count) .and change(LaRentRange, :count) - end - - it "is idempotent" do - Rails.application.load_seed expect { Rails.application.load_seed From 474ed4f348bfe85bb39ebbf0a0ae985b2e04ad81 Mon Sep 17 00:00:00 2001 From: Manny Dinssa <44172848+Dinssa@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:47:57 +0000 Subject: [PATCH 25/36] CLDC-3344: Sales CSV download ecstat2 child label bug fix (#2801) * Add missing answer option * Update tests * Allow user to change the age of a person if they're not actually under 16 without being blocked * Update displayed answer options * Add tests * Fix lint * Clear only when age is not known * Lint * Use existing method * Update tests * Revert changes in derived variables * Update sales_log_spec.rb --- .../questions/buyer2_working_situation.rb | 6 ++++ .../buyer2_working_situation_spec.rb | 28 +++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/app/models/form/sales/questions/buyer2_working_situation.rb b/app/models/form/sales/questions/buyer2_working_situation.rb index 38ab320b3..9105b8597 100644 --- a/app/models/form/sales/questions/buyer2_working_situation.rb +++ b/app/models/form/sales/questions/buyer2_working_situation.rb @@ -15,6 +15,10 @@ class Form::Sales::Questions::Buyer2WorkingSituation < ::Form::Question @question_number = QUESTION_NUMBER_FROM_YEAR[form.start_date.year] || QUESTION_NUMBER_FROM_YEAR[QUESTION_NUMBER_FROM_YEAR.keys.max] end + def displayed_answer_options(_log, _user = nil) + answer_options.reject { |key, _| key == "9" } + end + def answer_options if form.start_year_2025_or_later? { @@ -26,6 +30,7 @@ class Form::Sales::Questions::Buyer2WorkingSituation < ::Form::Question "6" => { "value" => "Not seeking work" }, "7" => { "value" => "Full-time student" }, "8" => { "value" => "Unable to work due to long term sick or disability" }, + "9" => { "value" => "Child under 16" }, "0" => { "value" => "Other" }, "10" => { "value" => "Buyer prefers not to say" }, }.freeze @@ -41,6 +46,7 @@ class Form::Sales::Questions::Buyer2WorkingSituation < ::Form::Question "0" => { "value" => "Other" }, "10" => { "value" => "Buyer prefers not to say" }, "7" => { "value" => "Full-time student" }, + "9" => { "value" => "Child under 16" }, }.freeze end end diff --git a/spec/models/form/sales/questions/buyer2_working_situation_spec.rb b/spec/models/form/sales/questions/buyer2_working_situation_spec.rb index 7b825185c..c8ee349b6 100644 --- a/spec/models/form/sales/questions/buyer2_working_situation_spec.rb +++ b/spec/models/form/sales/questions/buyer2_working_situation_spec.rb @@ -36,6 +36,22 @@ RSpec.describe Form::Sales::Questions::Buyer2WorkingSituation, type: :model do "0" => { "value" => "Other" }, "10" => { "value" => "Buyer prefers not to say" }, "7" => { "value" => "Full-time student" }, + "9" => { "value" => "Child under 16" }, + }) + end + + it "has the correct displayed_answer_options" do + expect(question.displayed_answer_options(nil)).to eq({ + "1" => { "value" => "Full-time - 30 hours or more" }, + "2" => { "value" => "Part-time - Less than 30 hours" }, + "3" => { "value" => "In government training into work" }, + "4" => { "value" => "Jobseeker" }, + "6" => { "value" => "Not seeking work" }, + "8" => { "value" => "Unable to work due to long term sick or disability" }, + "5" => { "value" => "Retired" }, + "0" => { "value" => "Other" }, + "10" => { "value" => "Buyer prefers not to say" }, + "7" => { "value" => "Full-time student" }, }) end @@ -43,7 +59,11 @@ RSpec.describe Form::Sales::Questions::Buyer2WorkingSituation, type: :model do let(:form) { instance_double(Form, start_date: Time.zone.local(2024, 4, 1), start_year_2025_or_later?: false) } it "uses the old ordering for answer options" do - expect(question.answer_options.keys).to eq(%w[1 2 3 4 6 8 5 0 10 7]) + expect(question.answer_options.keys).to eq(%w[1 2 3 4 6 8 5 0 10 7 9]) + end + + it "uses the old ordering for displayed answer options" do + expect(question.displayed_answer_options(nil).keys).to eq(%w[1 2 3 4 6 8 5 0 10 7]) end end @@ -51,7 +71,11 @@ RSpec.describe Form::Sales::Questions::Buyer2WorkingSituation, type: :model do let(:form) { instance_double(Form, start_date: Time.zone.local(2025, 4, 1), start_year_2025_or_later?: true) } it "uses the new ordering for answer options" do - expect(question.answer_options.keys).to eq(%w[1 2 3 4 5 6 7 8 0 10]) + expect(question.answer_options.keys).to eq(%w[1 2 3 4 5 6 7 8 9 0 10]) + end + + it "uses the new ordering for displayed answer options" do + expect(question.displayed_answer_options(nil).keys).to eq(%w[1 2 3 4 5 6 7 8 0 10]) end end From 8594561a18eac0f351290662d370a390e29a9388 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Tue, 10 Dec 2024 12:27:46 +0000 Subject: [PATCH 26/36] Stub some credentials (#2857) --- spec/mailers/devise_notify_mailer_spec.rb | 11 +++++++---- spec/models/user_spec.rb | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/spec/mailers/devise_notify_mailer_spec.rb b/spec/mailers/devise_notify_mailer_spec.rb index 4ed209b24..7a7123be0 100644 --- a/spec/mailers/devise_notify_mailer_spec.rb +++ b/spec/mailers/devise_notify_mailer_spec.rb @@ -36,8 +36,11 @@ RSpec.describe DeviseNotifyMailer do end context "when the email domain is in the allowlist" do - let(:domain) { Rails.application.credentials[:email_allowlist].first } - let(:email) { "test@#{domain}" } + before do + allow(Rails.application.credentials).to receive(:[]).with(:email_allowlist).and_return(["example.com"]) + end + + let(:email) { "test@example.com" } it "does send emails" do expect(notify_client).to receive(:send_email).once @@ -48,10 +51,10 @@ RSpec.describe DeviseNotifyMailer do context "when notify mailer raises BadRequestError" do before do allow(notify_client).to receive(:send_email).and_raise(bad_request_error) + allow(Rails.application.credentials).to receive(:[]).with(:email_allowlist).and_return(["example.com"]) end - let(:domain) { Rails.application.credentials[:email_allowlist].first } - let(:email) { "test@#{domain}" } + let(:email) { "test@example.com" } it "does not raise an error" do expect { diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 51cfc00bd..53561f3e9 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -300,6 +300,7 @@ RSpec.describe User, type: :model do context "when the user is in staging environment" do before do allow(Rails.env).to receive(:staging?).and_return(true) + allow(Rails.application.credentials).to receive(:[]).with(:staging_role_update_email_allowlist).and_return(["not_one_of_the_examples.com"]) end context "and the user is not in the staging role update email allowlist" do From e7490f1381dbad8180179cbd814bb90684f8fb8d Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:45:53 +0000 Subject: [PATCH 27/36] CLDC-3779 Add duplicate logs email (#2843) * Find block log creation reason * Update mailer * Remove create_logs? method --- app/mailers/bulk_upload_mailer.rb | 21 ++++++++++ .../bulk_upload/lettings/validator.rb | 14 +++---- app/services/bulk_upload/processor.rb | 40 +++++++++++++------ app/services/bulk_upload/sales/validator.rb | 12 +++--- spec/mailers/bulk_upload_mailer_spec.rb | 25 ++++++++++++ .../bulk_upload/lettings/validator_spec.rb | 10 ++--- spec/services/bulk_upload/processor_spec.rb | 32 +++++++++++++-- .../bulk_upload/sales/validator_spec.rb | 16 ++++---- 8 files changed, 124 insertions(+), 46 deletions(-) diff --git a/app/mailers/bulk_upload_mailer.rb b/app/mailers/bulk_upload_mailer.rb index e0115abb0..7c62f71a1 100644 --- a/app/mailers/bulk_upload_mailer.rb +++ b/app/mailers/bulk_upload_mailer.rb @@ -3,6 +3,7 @@ class BulkUploadMailer < NotifyMailer COMPLETE_TEMPLATE_ID = "83279578-c890-4168-838b-33c9cf0dc9f0".freeze FAILED_CSV_ERRORS_TEMPLATE_ID = "e27abcd4-5295-48c2-b127-e9ee4b781b75".freeze + FAILED_CSV_DUPLICATE_ERRORS_TEMPLATE_ID = "931d5bda-a08f-4de6-a455-38a63bff1956".freeze FAILED_FILE_SETUP_ERROR_TEMPLATE_ID = "24c9f4c7-96ad-470a-ba31-eb51b7cbafd9".freeze FAILED_SERVICE_ERROR_TEMPLATE_ID = "c3f6288c-7a74-4e77-99ee-6c4a0f6e125a".freeze HOW_TO_FIX_UPLOAD_TEMPLATE_ID = "21a07b26-f625-4846-9f4d-39e30937aa24".freeze @@ -95,6 +96,26 @@ class BulkUploadMailer < NotifyMailer ) end + def send_correct_duplicates_and_upload_again_mail(bulk_upload:) + summary_report_link = if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors? + bulk_upload.sales? ? summary_bulk_upload_sales_result_url(bulk_upload) : summary_bulk_upload_lettings_result_url(bulk_upload) + else + bulk_upload.sales? ? bulk_upload_sales_result_url(bulk_upload) : bulk_upload_lettings_result_url(bulk_upload) + end + + send_email( + bulk_upload.user.email, + FAILED_CSV_DUPLICATE_ERRORS_TEMPLATE_ID, + { + filename: bulk_upload.filename, + upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time), + year_combo: bulk_upload.year_combo, + lettings_or_sales: bulk_upload.log_type, + summary_report_link:, + }, + ) + end + def send_bulk_upload_failed_file_setup_error_mail(bulk_upload:) bulk_upload_link = if BulkUploadErrorSummaryTableComponent.new(bulk_upload:).errors? bulk_upload.sales? ? summary_bulk_upload_sales_result_url(bulk_upload) : summary_bulk_upload_lettings_result_url(bulk_upload) diff --git a/app/services/bulk_upload/lettings/validator.rb b/app/services/bulk_upload/lettings/validator.rb index 116c3b745..291bf45e7 100644 --- a/app/services/bulk_upload/lettings/validator.rb +++ b/app/services/bulk_upload/lettings/validator.rb @@ -40,29 +40,25 @@ class BulkUpload::Lettings::Validator end end - def create_logs? - return false if any_setup_errors? + def block_log_creation_reason + return "setup_errors" if any_setup_errors? if row_parsers.any?(&:block_log_creation?) Sentry.capture_message("Bulk upload log creation blocked: #{bulk_upload.id}.") - return false + return "row_parser_block_log_creation" end if any_logs_already_exist? && FeatureToggle.bulk_upload_duplicate_log_check_enabled? - Sentry.capture_message("Bulk upload log creation blocked due to duplicate logs: #{bulk_upload.id}.") - return false + return "duplicate_logs" end row_parsers.each do |row_parser| row_parser.log.blank_invalid_non_setup_fields! end - if any_logs_invalid? Sentry.capture_message("Bulk upload log creation blocked due to invalid logs after blanking non setup fields: #{bulk_upload.id}.") - return false + "logs_invalid" end - - true end def self.question_for_field(field) diff --git a/app/services/bulk_upload/processor.rb b/app/services/bulk_upload/processor.rb index 38f67ede4..c54032fda 100644 --- a/app/services/bulk_upload/processor.rb +++ b/app/services/bulk_upload/processor.rb @@ -36,20 +36,28 @@ class BulkUpload::Processor if validator.any_setup_errors? send_setup_errors_mail - - elsif validator.create_logs? - create_logs - - if validator.soft_validation_errors_only? - send_check_soft_validations_mail - elsif created_logs_but_incompleted? - send_how_to_fix_upload_mail - elsif created_logs_and_all_completed? - bulk_upload.unpend - send_success_mail - end else - send_correct_and_upload_again_mail # summary/full report + block_creation_reason = validator.block_log_creation_reason + + if block_creation_reason.present? + case block_creation_reason + when "duplicate_logs" + send_correct_duplicates_and_upload_again_mail + else + send_correct_and_upload_again_mail # summary/full report + end + else + create_logs + + if validator.soft_validation_errors_only? + send_check_soft_validations_mail + elsif created_logs_but_incompleted? + send_how_to_fix_upload_mail + elsif created_logs_and_all_completed? + bulk_upload.unpend + send_success_mail + end + end end rescue StandardError => e Sentry.capture_exception(e) @@ -97,6 +105,12 @@ private .deliver_later end + def send_correct_duplicates_and_upload_again_mail + BulkUploadMailer + .send_correct_duplicates_and_upload_again_mail(bulk_upload:) + .deliver_later + end + def send_success_mail BulkUploadMailer .send_bulk_upload_complete_mail(user:, bulk_upload:) diff --git a/app/services/bulk_upload/sales/validator.rb b/app/services/bulk_upload/sales/validator.rb index 76fb6f1ae..7ad9638d7 100644 --- a/app/services/bulk_upload/sales/validator.rb +++ b/app/services/bulk_upload/sales/validator.rb @@ -39,17 +39,17 @@ class BulkUpload::Sales::Validator end end - def create_logs? - return false if any_setup_errors? + def block_log_creation_reason + return "setup_errors" if any_setup_errors? if row_parsers.any?(&:block_log_creation?) Sentry.capture_message("Bulk upload log creation blocked: #{bulk_upload.id}.") - return false + return "row_parser_block_log_creation" end if any_logs_already_exist? && FeatureToggle.bulk_upload_duplicate_log_check_enabled? Sentry.capture_message("Bulk upload log creation blocked due to duplicate logs: #{bulk_upload.id}.") - return false + return "duplicate_logs" end row_parsers.each do |row_parser| @@ -58,10 +58,8 @@ class BulkUpload::Sales::Validator if any_logs_invalid? Sentry.capture_message("Bulk upload log creation blocked due to invalid logs after blanking non setup fields: #{bulk_upload.id}.") - return false + "logs_invalid" end - - true end def any_setup_errors? diff --git a/spec/mailers/bulk_upload_mailer_spec.rb b/spec/mailers/bulk_upload_mailer_spec.rb index c3225c937..910bca4a9 100644 --- a/spec/mailers/bulk_upload_mailer_spec.rb +++ b/spec/mailers/bulk_upload_mailer_spec.rb @@ -121,4 +121,29 @@ RSpec.describe BulkUploadMailer do mailer.send_check_soft_validations_mail(bulk_upload:) end end + + describe "#send_correct_duplicates_and_upload_again_mail" do + context "when 2 columns with errors" do + before do + create(:bulk_upload_error, bulk_upload:, col: "A") + create(:bulk_upload_error, bulk_upload:, col: "B") + end + + it "sends correctly formed email" do + expect(notify_client).to receive(:send_email).with( + email_address: user.email, + template_id: described_class::FAILED_CSV_DUPLICATE_ERRORS_TEMPLATE_ID, + personalisation: { + filename: bulk_upload.filename, + upload_timestamp: bulk_upload.created_at.to_fs(:govuk_date_and_time), + year_combo: bulk_upload.year_combo, + lettings_or_sales: bulk_upload.log_type, + summary_report_link: "http://localhost:3000/lettings-logs/bulk-upload-results/#{bulk_upload.id}", + }, + ) + + mailer.send_correct_duplicates_and_upload_again_mail(bulk_upload:) + end + end + end end diff --git a/spec/services/bulk_upload/lettings/validator_spec.rb b/spec/services/bulk_upload/lettings/validator_spec.rb index 897010de6..60eb8a955 100644 --- a/spec/services/bulk_upload/lettings/validator_spec.rb +++ b/spec/services/bulk_upload/lettings/validator_spec.rb @@ -232,7 +232,7 @@ RSpec.describe BulkUpload::Lettings::Validator do end end - describe "#create_logs?" do + describe "#block_log_creation_reason" do context "when a log has a clearable, non-setup error" do let(:log_1) { build(:lettings_log, :completed, period: 2, assigned_to: user) } let(:log_2) { build(:lettings_log, :completed, period: 2, assigned_to: user, age1: 5) } @@ -245,7 +245,7 @@ RSpec.describe BulkUpload::Lettings::Validator do it "returns false" do validator.call - expect(validator).to be_create_logs + expect(validator.block_log_creation_reason).to be_nil end end @@ -261,7 +261,7 @@ RSpec.describe BulkUpload::Lettings::Validator do it "returns true" do validator.call - expect(validator).to be_create_logs + expect(validator.block_log_creation_reason).to be_nil end end @@ -277,7 +277,7 @@ RSpec.describe BulkUpload::Lettings::Validator do it "will not create logs" do validator.call - expect(validator).not_to be_create_logs + expect(validator.block_log_creation_reason).to eq("setup_errors") end end @@ -291,7 +291,7 @@ RSpec.describe BulkUpload::Lettings::Validator do it "returns false" do validator.call - expect(validator).not_to be_create_logs + expect(validator.block_log_creation_reason).to eq("setup_errors") end end end diff --git a/spec/services/bulk_upload/processor_spec.rb b/spec/services/bulk_upload/processor_spec.rb index de0ed2dba..0368635e7 100644 --- a/spec/services/bulk_upload/processor_spec.rb +++ b/spec/services/bulk_upload/processor_spec.rb @@ -14,7 +14,7 @@ RSpec.describe BulkUpload::Processor do call: nil, total_logs_count: 1, any_setup_errors?: false, - create_logs?: true, + block_log_creation_reason: nil, soft_validation_errors_only?: false, ) end @@ -165,7 +165,7 @@ RSpec.describe BulkUpload::Processor do let(:log) { build(:lettings_log, :setup_completed, assigned_to: user) } before do - allow(mock_validator).to receive(:create_logs?).and_return(true) + allow(mock_validator).to receive(:block_log_creation_reason).and_return(nil) allow(mock_validator).to receive(:soft_validation_errors_only?).and_return(false) allow(FeatureToggle).to receive(:bulk_upload_duplicate_log_check_enabled?).and_return(true) end @@ -198,7 +198,7 @@ RSpec.describe BulkUpload::Processor do context "when a bulk upload has logs with only soft validations triggered" do before do - allow(mock_validator).to receive(:create_logs?).and_return(true) + allow(mock_validator).to receive(:block_log_creation_reason).and_return(nil) allow(mock_validator).to receive(:soft_validation_errors_only?).and_return(true) allow(FeatureToggle).to receive(:bulk_upload_duplicate_log_check_enabled?).and_return(true) end @@ -239,7 +239,7 @@ RSpec.describe BulkUpload::Processor do call: nil, total_logs_count: 1, any_setup_errors?: false, - create_logs?: false, + block_log_creation_reason: "row_parser_block_log_creation", ) end @@ -254,6 +254,30 @@ RSpec.describe BulkUpload::Processor do expect(mail_double).to have_received(:deliver_later) end end + + context "when upload has duplicate logs blocking log creation" do + let(:mock_validator) do + instance_double( + BulkUpload::Lettings::Validator, + invalid?: false, + call: nil, + total_logs_count: 1, + any_setup_errors?: false, + block_log_creation_reason: "duplicate_logs", + ) + end + + it "sends correct_and_upload_again_mail" do + mail_double = instance_double("ActionMailer::MessageDelivery", deliver_later: nil) + + allow(BulkUploadMailer).to receive(:send_correct_duplicates_and_upload_again_mail).and_return(mail_double) + + processor.call + + expect(BulkUploadMailer).to have_received(:send_correct_duplicates_and_upload_again_mail) + expect(mail_double).to have_received(:deliver_later) + end + end end describe "#approve" do diff --git a/spec/services/bulk_upload/sales/validator_spec.rb b/spec/services/bulk_upload/sales/validator_spec.rb index c275ce681..968014e7c 100644 --- a/spec/services/bulk_upload/sales/validator_spec.rb +++ b/spec/services/bulk_upload/sales/validator_spec.rb @@ -204,7 +204,7 @@ RSpec.describe BulkUpload::Sales::Validator do end end - describe "#create_logs?" do + describe "#block_log_creation_reason" do context "when all logs are valid" do let(:log_1) { build(:sales_log, :completed, assigned_to: user) } let(:log_2) { build(:sales_log, :completed, assigned_to: user) } @@ -214,9 +214,9 @@ RSpec.describe BulkUpload::Sales::Validator do file.write(BulkUpload::SalesLogToCsv.new(log: log_2).to_csv_row) end - it "returns truthy" do + it "returns nil" do validator.call - expect(validator).to be_create_logs + expect(validator.block_log_creation_reason).to be_nil end end @@ -229,9 +229,9 @@ RSpec.describe BulkUpload::Sales::Validator do file.write(BulkUpload::SalesLogToCsv.new(log: log_2).to_csv_row) end - it "returns truthy" do + it "returns nil" do validator.call - expect(validator).to be_create_logs + expect(validator.block_log_creation_reason).to be_nil end end @@ -245,9 +245,9 @@ RSpec.describe BulkUpload::Sales::Validator do file.close end - it "returns false" do + it "returns the reason" do validator.call - expect(validator).not_to be_create_logs + expect(validator.block_log_creation_reason).to eq("setup_errors") end end @@ -262,7 +262,7 @@ RSpec.describe BulkUpload::Sales::Validator do it "will not create logs" do validator.call - expect(validator).not_to be_create_logs + expect(validator.block_log_creation_reason).to eq("setup_errors") end end end From 43ae7add3cd02ac2f6be10033c808d1d95b77f80 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Wed, 11 Dec 2024 13:21:47 +0000 Subject: [PATCH 28/36] Patch rails (#2861) --- Gemfile | 2 +- Gemfile.lock | 124 +++++++++++++++++++++++++-------------------------- 2 files changed, 63 insertions(+), 63 deletions(-) diff --git a/Gemfile b/Gemfile index e9af29d55..027ed10d0 100644 --- a/Gemfile +++ b/Gemfile @@ -6,7 +6,7 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby "3.1.4" # Bundle edge Rails instead: gem 'rails', github: 'rails/rails', branch: 'main' -gem "rails", "~> 7.0.8.5" +gem "rails", "~> 7.0.8.7" # Use postgresql as the database for Active Record gem "pg", "~> 1.1" # Use Puma as the app server diff --git a/Gemfile.lock b/Gemfile.lock index d2a5de05d..329c92051 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,71 +1,71 @@ GEM remote: https://rubygems.org/ specs: - actioncable (7.0.8.5) - actionpack (= 7.0.8.5) - activesupport (= 7.0.8.5) + actioncable (7.0.8.7) + actionpack (= 7.0.8.7) + activesupport (= 7.0.8.7) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (7.0.8.5) - actionpack (= 7.0.8.5) - activejob (= 7.0.8.5) - activerecord (= 7.0.8.5) - activestorage (= 7.0.8.5) - activesupport (= 7.0.8.5) + actionmailbox (7.0.8.7) + actionpack (= 7.0.8.7) + activejob (= 7.0.8.7) + activerecord (= 7.0.8.7) + activestorage (= 7.0.8.7) + activesupport (= 7.0.8.7) mail (>= 2.7.1) net-imap net-pop net-smtp - actionmailer (7.0.8.5) - actionpack (= 7.0.8.5) - actionview (= 7.0.8.5) - activejob (= 7.0.8.5) - activesupport (= 7.0.8.5) + actionmailer (7.0.8.7) + actionpack (= 7.0.8.7) + actionview (= 7.0.8.7) + activejob (= 7.0.8.7) + activesupport (= 7.0.8.7) mail (~> 2.5, >= 2.5.4) net-imap net-pop net-smtp rails-dom-testing (~> 2.0) - actionpack (7.0.8.5) - actionview (= 7.0.8.5) - activesupport (= 7.0.8.5) + actionpack (7.0.8.7) + actionview (= 7.0.8.7) + activesupport (= 7.0.8.7) rack (~> 2.0, >= 2.2.4) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (7.0.8.5) - actionpack (= 7.0.8.5) - activerecord (= 7.0.8.5) - activestorage (= 7.0.8.5) - activesupport (= 7.0.8.5) + actiontext (7.0.8.7) + actionpack (= 7.0.8.7) + activerecord (= 7.0.8.7) + activestorage (= 7.0.8.7) + activesupport (= 7.0.8.7) globalid (>= 0.6.0) nokogiri (>= 1.8.5) - actionview (7.0.8.5) - activesupport (= 7.0.8.5) + actionview (7.0.8.7) + activesupport (= 7.0.8.7) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (7.0.8.5) - activesupport (= 7.0.8.5) + activejob (7.0.8.7) + activesupport (= 7.0.8.7) globalid (>= 0.3.6) - activemodel (7.0.8.5) - activesupport (= 7.0.8.5) + activemodel (7.0.8.7) + activesupport (= 7.0.8.7) activemodel-serializers-xml (1.0.2) activemodel (> 5.x) activesupport (> 5.x) builder (~> 3.1) - activerecord (7.0.8.5) - activemodel (= 7.0.8.5) - activesupport (= 7.0.8.5) - activestorage (7.0.8.5) - actionpack (= 7.0.8.5) - activejob (= 7.0.8.5) - activerecord (= 7.0.8.5) - activesupport (= 7.0.8.5) + activerecord (7.0.8.7) + activemodel (= 7.0.8.7) + activesupport (= 7.0.8.7) + activestorage (7.0.8.7) + actionpack (= 7.0.8.7) + activejob (= 7.0.8.7) + activerecord (= 7.0.8.7) + activesupport (= 7.0.8.7) marcel (~> 1.0) mini_mime (>= 1.1.0) - activesupport (7.0.8.5) + activesupport (7.0.8.7) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -149,7 +149,7 @@ GEM crass (1.0.6) cssbundling-rails (1.4.0) railties (>= 6.0.0) - date (3.3.4) + date (3.4.1) descendants_tracker (0.0.4) thread_safe (~> 0.3, >= 0.3.1) devise (4.9.3) @@ -258,13 +258,13 @@ GEM matrix (0.4.2) method_source (1.1.0) mini_mime (1.1.5) - minitest (5.25.1) + minitest (5.25.4) msgpack (1.7.2) multipart-post (2.4.1) nested_form (0.3.2) net-http (0.4.1) uri - net-imap (0.4.17) + net-imap (0.5.1) date net-protocol net-pop (0.1.2) @@ -273,12 +273,12 @@ GEM timeout net-smtp (0.5.0) net-protocol - nio4r (2.7.3) - nokogiri (1.16.8-arm64-darwin) + nio4r (2.7.4) + nokogiri (1.17.1-arm64-darwin) racc (~> 1.4) - nokogiri (1.16.8-x86_64-darwin) + nokogiri (1.17.1-x86_64-darwin) racc (~> 1.4) - nokogiri (1.16.8-x86_64-linux) + nokogiri (1.17.1-x86_64-linux) racc (~> 1.4) notifications-ruby-client (6.0.0) jwt (>= 1.5, < 3) @@ -327,20 +327,20 @@ GEM rack (>= 1.2.0) rack-test (2.1.0) rack (>= 1.3) - rails (7.0.8.5) - actioncable (= 7.0.8.5) - actionmailbox (= 7.0.8.5) - actionmailer (= 7.0.8.5) - actionpack (= 7.0.8.5) - actiontext (= 7.0.8.5) - actionview (= 7.0.8.5) - activejob (= 7.0.8.5) - activemodel (= 7.0.8.5) - activerecord (= 7.0.8.5) - activestorage (= 7.0.8.5) - activesupport (= 7.0.8.5) + rails (7.0.8.7) + actioncable (= 7.0.8.7) + actionmailbox (= 7.0.8.7) + actionmailer (= 7.0.8.7) + actionpack (= 7.0.8.7) + actiontext (= 7.0.8.7) + actionview (= 7.0.8.7) + activejob (= 7.0.8.7) + activemodel (= 7.0.8.7) + activerecord (= 7.0.8.7) + activestorage (= 7.0.8.7) + activesupport (= 7.0.8.7) bundler (>= 1.15.0) - railties (= 7.0.8.5) + railties (= 7.0.8.7) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest @@ -354,9 +354,9 @@ GEM nested_form (~> 0.3) rails (>= 6.0, < 8) turbo-rails (~> 1.0) - railties (7.0.8.5) - actionpack (= 7.0.8.5) - activesupport (= 7.0.8.5) + railties (7.0.8.7) + actionpack (= 7.0.8.7) + activesupport (= 7.0.8.7) method_source rake (>= 12.2) thor (~> 1.0) @@ -465,7 +465,7 @@ GEM thor (1.3.2) thread_safe (0.3.6) timecop (0.9.8) - timeout (0.4.1) + timeout (0.4.2) turbo-rails (1.5.0) actionpack (>= 6.0.0) activejob (>= 6.0.0) @@ -553,7 +553,7 @@ DEPENDENCIES rack (>= 2.2.6.3) rack-attack rack-mini-profiler (~> 2.0) - rails (~> 7.0.8.5) + rails (~> 7.0.8.7) rails_admin (~> 3.1) redcarpet (~> 3.6) redis (~> 4.8) From a7964e64588941c8800c2c9814a4775b5eab32b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 15:51:59 +0000 Subject: [PATCH 29/36] Bump nanoid from 3.3.7 to 3.3.8 (#2863) Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.7 to 3.3.8. - [Release notes](https://github.com/ai/nanoid/releases) - [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md) - [Commits](https://github.com/ai/nanoid/compare/3.3.7...3.3.8) --- updated-dependencies: - dependency-name: nanoid dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3e9afa0d8..94b220fe1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4335,9 +4335,9 @@ mute-stream@0.0.8: integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + version "3.3.8" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" + integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== natural-compare@^1.4.0: version "1.4.0" From ca2d670116f1399fd48db3d07fd36c63ec963020 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Wed, 11 Dec 2024 15:52:13 +0000 Subject: [PATCH 30/36] CLDC-3748 Check org name uniqueness (#2818) * Check org name uniqueness * Some tests * Do not update org name * Update some request specs * Remove unnecessary updates * Update error message --- app/controllers/organisations_controller.rb | 1 + app/models/organisation.rb | 1 + config/locales/en.yml | 1 + .../20241125153349_add_unique_index_to_org_name.rb | 5 +++++ db/schema.rb | 1 + spec/fixtures/exports/general_needs_log.xml | 4 ++-- spec/fixtures/exports/general_needs_log_23_24.xml | 4 ++-- spec/fixtures/exports/general_needs_log_24_25.xml | 4 ++-- spec/fixtures/exports/organisation.xml | 2 +- spec/fixtures/exports/supported_housing_logs.xml | 4 ++-- spec/fixtures/exports/user.xml | 2 +- .../lettings/questions/managing_organisation_spec.rb | 2 +- spec/models/organisation_spec.rb | 6 ++++++ .../organisation_relationships_controller_spec.rb | 8 ++++---- spec/requests/organisations_controller_spec.rb | 10 ++++++++-- .../exports/lettings_log_export_service_spec.rb | 2 ++ .../exports/organisation_export_service_spec.rb | 1 + spec/services/exports/user_export_service_spec.rb | 1 + 18 files changed, 42 insertions(+), 17 deletions(-) create mode 100644 db/migrate/20241125153349_add_unique_index_to_org_name.rb diff --git a/app/controllers/organisations_controller.rb b/app/controllers/organisations_controller.rb index 8ffe426d7..93b667a99 100644 --- a/app/controllers/organisations_controller.rb +++ b/app/controllers/organisations_controller.rb @@ -155,6 +155,7 @@ class OrganisationsController < ApplicationController end redirect_to details_organisation_path(@organisation) else + @used_rent_periods = @organisation.lettings_logs.pluck(:period).uniq.compact.map(&:to_s) @rent_periods = helpers.rent_periods_with_checked_attr(checked_periods: selected_rent_periods) render :edit, status: :unprocessable_entity end diff --git a/app/models/organisation.rb b/app/models/organisation.rb index 23f91f1ad..69c80d198 100644 --- a/app/models/organisation.rb +++ b/app/models/organisation.rb @@ -58,6 +58,7 @@ class Organisation < ApplicationRecord alias_method :la?, :LA? validates :name, presence: { message: I18n.t("validations.organisation.name_missing") } + validates :name, uniqueness: { case_sensitive: false, message: I18n.t("validations.organisation.name_not_unique") } validates :provider_type, presence: { message: I18n.t("validations.organisation.provider_type_missing") } def self.find_by_id_on_multiple_fields(id) diff --git a/config/locales/en.yml b/config/locales/en.yml index 851a9ea2c..965c1f7a6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -235,6 +235,7 @@ en: organisation: data_sharing_agreement_not_signed: "Your organisation must accept the Data Sharing Agreement before you can create any logs." name_missing: "Enter the name of the organisation." + name_not_unique: "An organisation with this name already exists. Use the organisation that already exists or add a location or other identifier to the name. For example: Organisation name (City)." provider_type_missing: "Select the organisation type." stock_owner: blank: "You must choose a stock owner." diff --git a/db/migrate/20241125153349_add_unique_index_to_org_name.rb b/db/migrate/20241125153349_add_unique_index_to_org_name.rb new file mode 100644 index 000000000..a7a124183 --- /dev/null +++ b/db/migrate/20241125153349_add_unique_index_to_org_name.rb @@ -0,0 +1,5 @@ +class AddUniqueIndexToOrgName < ActiveRecord::Migration[7.0] + def change + add_index :organisations, :name, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index d31a54da2..1a1c127c6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -546,6 +546,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_12_04_100518) do t.datetime "discarded_at" t.datetime "schemes_deduplicated_at" t.index ["absorbing_organisation_id"], name: "index_organisations_on_absorbing_organisation_id" + t.index ["name"], name: "index_organisations_on_name", unique: true t.index ["old_visible_id"], name: "index_organisations_on_old_visible_id", unique: true end diff --git a/spec/fixtures/exports/general_needs_log.xml b/spec/fixtures/exports/general_needs_log.xml index bacc7e9f0..0341dd2d4 100644 --- a/spec/fixtures/exports/general_needs_log.xml +++ b/spec/fixtures/exports/general_needs_log.xml @@ -147,10 +147,10 @@ {id} {owning_org_id} - MHCLG + {owning_org_name} 1234 {managing_org_id} - MHCLG + {managing_org_name} 1234 2022-05-01T00:00:00+01:00 2022-05-01T00:00:00+01:00 diff --git a/spec/fixtures/exports/general_needs_log_23_24.xml b/spec/fixtures/exports/general_needs_log_23_24.xml index 9635cd0e4..ef0c4066c 100644 --- a/spec/fixtures/exports/general_needs_log_23_24.xml +++ b/spec/fixtures/exports/general_needs_log_23_24.xml @@ -148,10 +148,10 @@ {id} {owning_org_id} - MHCLG + {owning_org_name} 1234 {managing_org_id} - MHCLG + {managing_org_name} 1234 2023-04-03T00:00:00+01:00 2023-04-03T00:00:00+01:00 diff --git a/spec/fixtures/exports/general_needs_log_24_25.xml b/spec/fixtures/exports/general_needs_log_24_25.xml index a665a284e..00d8bb1a5 100644 --- a/spec/fixtures/exports/general_needs_log_24_25.xml +++ b/spec/fixtures/exports/general_needs_log_24_25.xml @@ -161,10 +161,10 @@ la as entered {id} {owning_org_id} - MHCLG + {owning_org_name} 1234 {managing_org_id} - MHCLG + {managing_org_name} 1234 2024-04-03T00:00:00+01:00 2024-04-03T00:00:00+01:00 diff --git a/spec/fixtures/exports/organisation.xml b/spec/fixtures/exports/organisation.xml index 8d87da16c..70c699915 100644 --- a/spec/fixtures/exports/organisation.xml +++ b/spec/fixtures/exports/organisation.xml @@ -2,7 +2,7 @@
{id} - MHCLG + {name} 1 2 Marsham Street diff --git a/spec/fixtures/exports/supported_housing_logs.xml b/spec/fixtures/exports/supported_housing_logs.xml index 50649241b..310d5ba2b 100644 --- a/spec/fixtures/exports/supported_housing_logs.xml +++ b/spec/fixtures/exports/supported_housing_logs.xml @@ -146,10 +146,10 @@ {id} {owning_org_id} - MHCLG + {owning_org_name} 1234 {managing_org_id} - MHCLG + {managing_org_name} 1234 2022-05-01T00:00:00+01:00 2022-05-01T00:00:00+01:00 diff --git a/spec/fixtures/exports/user.xml b/spec/fixtures/exports/user.xml index 5652ac9c6..d29a33225 100644 --- a/spec/fixtures/exports/user.xml +++ b/spec/fixtures/exports/user.xml @@ -12,6 +12,6 @@ false false true - MHCLG + {organisation_name}
diff --git a/spec/models/form/lettings/questions/managing_organisation_spec.rb b/spec/models/form/lettings/questions/managing_organisation_spec.rb index 776a873a6..00485e80f 100644 --- a/spec/models/form/lettings/questions/managing_organisation_spec.rb +++ b/spec/models/form/lettings/questions/managing_organisation_spec.rb @@ -185,7 +185,7 @@ RSpec.describe Form::Lettings::Questions::ManagingOrganisation, type: :model do context "when organisation has merged" do let(:absorbing_org) { create(:organisation, name: "Absorbing org", holds_own_stock: true) } let!(:merged_org) { create(:organisation, name: "Merged org", holds_own_stock: false) } - let!(:merged_deleted_org) { create(:organisation, name: "Merged org", holds_own_stock: false, discarded_at: Time.zone.yesterday) } + let!(:merged_deleted_org) { create(:organisation, name: "Merged org 2", holds_own_stock: false, discarded_at: Time.zone.yesterday) } let(:user) { create(:user, :data_coordinator, organisation: absorbing_org) } let(:log) do diff --git a/spec/models/organisation_spec.rb b/spec/models/organisation_spec.rb index 9b01845ae..b117feef7 100644 --- a/spec/models/organisation_spec.rb +++ b/spec/models/organisation_spec.rb @@ -19,6 +19,12 @@ RSpec.describe Organisation, type: :model do .to raise_error(ActiveRecord::RecordInvalid, "Validation failed: Provider type #{I18n.t('validations.organisation.provider_type_missing')}") end + it "validates uniqueness of name" do + org = build(:organisation, name: organisation.name.downcase) + org.valid? + expect(org.errors[:name]).to include(I18n.t("validations.organisation.name_not_unique")) + end + context "with parent/child associations", :aggregate_failures do let!(:child_organisation) { create(:organisation, name: "MHCLG Child") } let!(:grandchild_organisation) { create(:organisation, name: "MHCLG Grandchild") } diff --git a/spec/requests/organisation_relationships_controller_spec.rb b/spec/requests/organisation_relationships_controller_spec.rb index 8e8dc4f2d..8733fba4b 100644 --- a/spec/requests/organisation_relationships_controller_spec.rb +++ b/spec/requests/organisation_relationships_controller_spec.rb @@ -58,7 +58,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do context "when adding a stock owner" do let!(:active_organisation) { FactoryBot.create(:organisation, name: "Active Org", active: true) } - let!(:inactive_organisation) { FactoryBot.create(:organisation, name: "Inactive LTD", active: false) } + let!(:inactive_organisation) { FactoryBot.create(:organisation, name: "Inactive LTD 2", active: false) } before do get "/organisations/#{organisation.id}/stock-owners/add", headers:, params: {} @@ -115,7 +115,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do let!(:managing_agent) { FactoryBot.create(:organisation) } let!(:other_org_managing_agent) { FactoryBot.create(:organisation, name: "Foobar LTD") } let!(:inactive_managing_agent) { FactoryBot.create(:organisation, name: "Inactive LTD", active: false) } - let!(:other_organisation) { FactoryBot.create(:organisation, name: "Foobar LTD") } + let!(:other_organisation) { FactoryBot.create(:organisation, name: "Foobar LTD 3") } before do FactoryBot.create(:organisation_relationship, parent_organisation: organisation, child_organisation: managing_agent) @@ -316,7 +316,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do context "with an organisation that the user belongs to" do let!(:stock_owner) { FactoryBot.create(:organisation) } let!(:other_org_stock_owner) { FactoryBot.create(:organisation, name: "Foobar LTD") } - let!(:other_organisation) { FactoryBot.create(:organisation, name: "Foobar LTD") } + let!(:other_organisation) { FactoryBot.create(:organisation, name: "Foobar LTD 2") } before do FactoryBot.create(:organisation_relationship, child_organisation: organisation, parent_organisation: stock_owner) @@ -452,7 +452,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do context "with an organisation that the user belongs to" do let!(:managing_agent) { FactoryBot.create(:organisation) } let!(:other_org_managing_agent) { FactoryBot.create(:organisation, name: "Foobar LTD") } - let!(:other_organisation) { FactoryBot.create(:organisation, name: "Foobar LTD") } + let!(:other_organisation) { FactoryBot.create(:organisation, name: "Foobar LTD 5") } before do FactoryBot.create(:organisation_relationship, parent_organisation: organisation, child_organisation: managing_agent) diff --git a/spec/requests/organisations_controller_spec.rb b/spec/requests/organisations_controller_spec.rb index 26723a563..6c01e50bf 100644 --- a/spec/requests/organisations_controller_spec.rb +++ b/spec/requests/organisations_controller_spec.rb @@ -1555,7 +1555,10 @@ RSpec.describe OrganisationsController, type: :request do let(:total_organisations_count) { Organisation.all.count } before do - create_list(:organisation, 25) + build_list(:organisation, 25) do |organisation, index| + organisation.name = "Organisation #{index}" + organisation.save! + end get "/organisations" end @@ -1644,7 +1647,10 @@ RSpec.describe OrganisationsController, type: :request do let(:search_param) { "MHCLG" } before do - create_list(:organisation, 27, name: "MHCLG") + build_list(:organisation, 27) do |organisation, index| + organisation.name = "MHCLG #{index}" + organisation.save! + end get "/organisations?search=#{search_param}" end diff --git a/spec/services/exports/lettings_log_export_service_spec.rb b/spec/services/exports/lettings_log_export_service_spec.rb index 8c123b47e..c0dde5771 100644 --- a/spec/services/exports/lettings_log_export_service_spec.rb +++ b/spec/services/exports/lettings_log_export_service_spec.rb @@ -21,7 +21,9 @@ RSpec.describe Exports::LettingsLogExportService do def replace_entity_ids(lettings_log, export_template) export_template.sub!(/\{id\}/, (lettings_log["id"] + Exports::LettingsLogExportService::LOG_ID_OFFSET).to_s) export_template.sub!(/\{owning_org_id\}/, (lettings_log["owning_organisation_id"] + Exports::LettingsLogExportService::LOG_ID_OFFSET).to_s) + export_template.sub!(/\{owning_org_name\}/, lettings_log.owning_organisation.name) export_template.sub!(/\{managing_org_id\}/, (lettings_log["managing_organisation_id"] + Exports::LettingsLogExportService::LOG_ID_OFFSET).to_s) + export_template.sub!(/\{managing_org_name\}/, lettings_log.managing_organisation.name) export_template.sub!(/\{location_id\}/, (lettings_log["location_id"]).to_s) if lettings_log.needstype == 2 export_template.sub!(/\{scheme_id\}/, (lettings_log["scheme_id"]).to_s) if lettings_log.needstype == 2 export_template.sub!(/\{log_id\}/, lettings_log["id"].to_s) diff --git a/spec/services/exports/organisation_export_service_spec.rb b/spec/services/exports/organisation_export_service_spec.rb index 43ca19095..51c8fe8cf 100644 --- a/spec/services/exports/organisation_export_service_spec.rb +++ b/spec/services/exports/organisation_export_service_spec.rb @@ -16,6 +16,7 @@ RSpec.describe Exports::OrganisationExportService do def replace_entity_ids(organisation, export_template) export_template.sub!(/\{id\}/, organisation["id"].to_s) + export_template.sub!(/\{name\}/, organisation["name"]) export_template.sub!(/\{dsa_signed_at\}/, organisation.data_protection_confirmation&.signed_at.to_s) export_template.sub!(/\{dpo_email\}/, organisation.data_protection_confirmation&.data_protection_officer_email) end diff --git a/spec/services/exports/user_export_service_spec.rb b/spec/services/exports/user_export_service_spec.rb index 8a0e22267..854dd1ce7 100644 --- a/spec/services/exports/user_export_service_spec.rb +++ b/spec/services/exports/user_export_service_spec.rb @@ -17,6 +17,7 @@ RSpec.describe Exports::UserExportService do def replace_entity_ids(user, export_template) export_template.sub!(/\{id\}/, user["id"].to_s) export_template.sub!(/\{organisation_id\}/, user["organisation_id"].to_s) + export_template.sub!(/\{organisation_name\}/, user.organisation.name) export_template.sub!(/\{email\}/, user["email"].to_s) end From 1c03a70fa2268e293c59453ec7789c6c94349452 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Wed, 11 Dec 2024 17:20:28 +0000 Subject: [PATCH 31/36] CLDC-3769 Fix some check errors flows (#2826) * Fix some check errors flows * Only show check errors journey for question on different pages * Update restore_error_field_values --- app/controllers/form_controller.rb | 11 ++++++++--- app/helpers/form_page_error_helper.rb | 5 +++-- .../form/sales/pages/owning_organisation.rb | 2 ++ app/models/validations/property_validations.rb | 2 ++ .../validations/sales/property_validations.rb | 2 ++ app/views/form/page.html.erb | 4 ++-- spec/features/form/page_routing_spec.rb | 16 +++++++++++++++- spec/requests/check_errors_controller_spec.rb | 2 +- 8 files changed, 35 insertions(+), 9 deletions(-) diff --git a/app/controllers/form_controller.rb b/app/controllers/form_controller.rb index 7ce63e609..3e029371d 100644 --- a/app/controllers/form_controller.rb +++ b/app/controllers/form_controller.rb @@ -105,8 +105,13 @@ private def restore_error_field_values(previous_responses) return unless previous_responses - previous_responses_to_reset = previous_responses.reject do |key, _| - @log.form.get_question(key, @log)&.type == "date" + previous_responses_to_reset = previous_responses.reject do |key, value| + if @log.form.get_question(key, @log)&.type == "date" && value.present? + year = value.split("-").first.to_i + year&.zero? + else + false + end end @log.assign_attributes(previous_responses_to_reset) @@ -433,7 +438,7 @@ private @log.valid? @log.reload error_attributes = @log.errors.map(&:attribute) - @questions = @log.form.questions.select { |q| error_attributes.include?(q.id.to_sym) } + @questions = @log.form.questions.select { |q| error_attributes.include?(q.id.to_sym) && q.page.routed_to?(@log, current_user) } end render "form/check_errors" end diff --git a/app/helpers/form_page_error_helper.rb b/app/helpers/form_page_error_helper.rb index 2c90bfd2a..bf4e1db08 100644 --- a/app/helpers/form_page_error_helper.rb +++ b/app/helpers/form_page_error_helper.rb @@ -13,7 +13,8 @@ module FormPageErrorHelper end end - def all_questions_affected_by_errors(log) - (log.errors.map(&:attribute) - [:base]).uniq + def all_pages_affected_by_errors(log) + question_ids = (log.errors.map(&:attribute) - [:base]).uniq + question_ids.map { |id| log.form.get_question(id, log)&.page&.id }.compact.uniq end end diff --git a/app/models/form/sales/pages/owning_organisation.rb b/app/models/form/sales/pages/owning_organisation.rb index f0c9e4e68..f1875d52d 100644 --- a/app/models/form/sales/pages/owning_organisation.rb +++ b/app/models/form/sales/pages/owning_organisation.rb @@ -20,11 +20,13 @@ class Form::Sales::Pages::OwningOrganisation < ::Form::Page if current_user.organisation.holds_own_stock? return true if current_user.organisation.absorbed_organisations.any?(&:holds_own_stock?) return true if stock_owners.count >= 1 + return false if log.owning_organisation == current_user.organisation log.update!(owning_organisation: current_user.organisation) else return false if stock_owners.count.zero? return true if stock_owners.count > 1 + return false if log.owning_organisation == stock_owners.first log.update!(owning_organisation: stock_owners.first) end diff --git a/app/models/validations/property_validations.rb b/app/models/validations/property_validations.rb index e9eba8184..1cf710857 100644 --- a/app/models/validations/property_validations.rb +++ b/app/models/validations/property_validations.rb @@ -41,6 +41,8 @@ module Validations::PropertyValidations def validate_property_postcode(record) postcode = record.postcode_full + return unless postcode + if record.postcode_known? && (postcode.blank? || !postcode.match(POSTCODE_REGEXP)) error_message = I18n.t("validations.lettings.property.postcode_full.invalid") record.errors.add :postcode_full, :wrong_format, message: error_message diff --git a/app/models/validations/sales/property_validations.rb b/app/models/validations/sales/property_validations.rb index 2238a634a..7fd4d2440 100644 --- a/app/models/validations/sales/property_validations.rb +++ b/app/models/validations/sales/property_validations.rb @@ -31,6 +31,8 @@ module Validations::Sales::PropertyValidations def validate_property_postcode(record) postcode = record.postcode_full + return unless postcode + if record.postcode_known? && (postcode.blank? || !postcode.match(POSTCODE_REGEXP)) error_message = I18n.t("validations.sales.property_information.postcode_full.invalid") record.errors.add :postcode_full, :wrong_format, message: error_message diff --git a/app/views/form/page.html.erb b/app/views/form/page.html.erb index 95dc38ec6..7379de0eb 100644 --- a/app/views/form/page.html.erb +++ b/app/views/form/page.html.erb @@ -16,7 +16,7 @@ <%= form_with model: @log, url: request.original_url, method: "post", local: true do |f| %>
- <% all_questions_with_errors = all_questions_affected_by_errors(@log) %> + <% all_pages_with_errors = all_pages_affected_by_errors(@log) %> <% remove_other_page_errors(@log, @page) %> <%= f.govuk_error_summary %> @@ -76,7 +76,7 @@ <%= f.hidden_field :check_errors, value: @check_errors %> <% end %> - <% if all_questions_with_errors.count > 1 %> + <% if all_pages_with_errors.count > 1 %>
<%= f.submit "See all related answers", name: "check_errors", class: "govuk-body govuk-link submit-button-link" %>
diff --git a/spec/features/form/page_routing_spec.rb b/spec/features/form/page_routing_spec.rb index 42a2c25fb..118b52543 100644 --- a/spec/features/form/page_routing_spec.rb +++ b/spec/features/form/page_routing_spec.rb @@ -92,7 +92,7 @@ RSpec.describe "Form Page Routing" do expect(find("#lettings-log-postcode-full-field-error").value).to eq("FAKE_POSTCODE") end - it "does not reset the displayed date" do + it "does not reset the displayed date if it's an invalid date" do lettings_log.update!(startdate: "2021/10/13") visit("/lettings-logs/#{id}/tenancy-start-date") fill_in("lettings_log[startdate(1i)]", with: "202") @@ -106,6 +106,20 @@ RSpec.describe "Form Page Routing" do expect(find_field("lettings_log[startdate(1i)]").value).to eq("2021") end + it "displays the entered date if it's in a valid format" do + lettings_log.update!(startdate: "2021/10/13") + visit("/lettings-logs/#{id}/tenancy-start-date") + fill_in("lettings_log[startdate(1i)]", with: "202") + fill_in("lettings_log[startdate(2i)]", with: "12") + fill_in("lettings_log[startdate(3i)]", with: "1") + click_button("Save and continue") + + expect(page).to have_current_path("/lettings-logs/#{id}/tenancy-start-date") + expect(find_field("lettings_log[startdate(3i)]").value).to eq("1") + expect(find_field("lettings_log[startdate(2i)]").value).to eq("12") + expect(find_field("lettings_log[startdate(1i)]").value).to eq("202") + end + it "does not reset the displayed date if it's empty" do lettings_log.update!(startdate: nil) visit("/lettings-logs/#{id}/tenancy-start-date") diff --git a/spec/requests/check_errors_controller_spec.rb b/spec/requests/check_errors_controller_spec.rb index 29130f547..9c4d3f8c1 100644 --- a/spec/requests/check_errors_controller_spec.rb +++ b/spec/requests/check_errors_controller_spec.rb @@ -84,7 +84,7 @@ RSpec.describe CheckErrorsController, type: :request do end it "displays correct clear and change links" do - expect(page.all(:button, value: "Clear").count).to eq(2) + expect(page.all(:button, value: "Clear").count).to eq(1) expect(page).to have_link("Change", count: 1) expect(page).to have_button("Clear all") end From fde970ae430f97b0f99c12413b168f0f5304c3fc Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Thu, 12 Dec 2024 10:29:43 +0000 Subject: [PATCH 32/36] Make org test names unique (#2865) --- spec/factories/organisation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/factories/organisation.rb b/spec/factories/organisation.rb index ecab5bf81..f3184277e 100644 --- a/spec/factories/organisation.rb +++ b/spec/factories/organisation.rb @@ -1,6 +1,6 @@ FactoryBot.define do factory :organisation do - name { Faker::Company.name } + sequence(:name) { |n| "#{Faker::Company.name} #{n}" } address_line1 { Faker::Address.street_address } address_line2 { Faker::Address.city } provider_type { "LA" } From 6d22b847dfbcbda0e30c920cbf490288eae350f6 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Thu, 12 Dec 2024 12:07:29 +0000 Subject: [PATCH 33/36] CLDC-3801 Do not put error objects into cookies (#2864) * Do not put error objects into cookies * Update tests * typo --- app/controllers/form_controller.rb | 12 ++++++++++-- app/helpers/form_page_error_helper.rb | 5 ----- app/views/form/page.html.erb | 3 +-- spec/requests/form_controller_spec.rb | 20 ++++++++++++++++++++ spec/views/form/page_view_spec.rb | 1 + 5 files changed, 32 insertions(+), 9 deletions(-) diff --git a/app/controllers/form_controller.rb b/app/controllers/form_controller.rb index 3e029371d..f31662c4f 100644 --- a/app/controllers/form_controller.rb +++ b/app/controllers/form_controller.rb @@ -38,8 +38,14 @@ class FormController < ApplicationController error_attributes = @log.errors.map(&:attribute) Rails.logger.info "User triggered validation(s) on: #{error_attributes.join(', ')}" @subsection = form.subsection_for_page(@page) - flash[:errors] = @log.errors + flash[:errors] = @log.errors.each_with_object({}) do |error, result| + if @page.questions.map(&:id).include?(error.attribute.to_s) + result[error.attribute.to_s] = error.message + end + end flash[:log_data] = responses_for_page + question_ids = (@log.errors.map(&:attribute) - [:base]).uniq + flash[:pages_with_errors_count] = question_ids.map { |id| @log.form.get_question(id, @log)&.page&.id }.compact.uniq.count redirect_to send("#{@log.class.name.underscore}_#{@page.id}_path", @log, { referrer: request.params["referrer"], original_page_id: request.params["original_page_id"], related_question_ids: request.params["related_question_ids"] }) end else @@ -81,6 +87,7 @@ class FormController < ApplicationController page_id = request.path.split("/")[-1].underscore @page = form.get_page(page_id) @subsection = form.subsection_for_page(@page) + @pages_with_errors_count = 0 if @page.routed_to?(@log, current_user) || is_referrer_type?("interruption_screen") || adding_answer_from_check_errors_page? if updated_answer_from_check_errors_page? @questions = request.params["related_question_ids"].map { |id| @log.form.get_question(id, @log) } @@ -89,6 +96,7 @@ class FormController < ApplicationController if flash[:errors].present? restore_previous_errors(flash[:errors]) restore_error_field_values(flash[:log_data]) + @pages_with_errors_count = flash[:pages_with_errors_count] end render "form/page" end @@ -121,7 +129,7 @@ private return unless previous_errors previous_errors.each do |attribute, message| - @log.errors.add attribute, message.first + @log.errors.add attribute, message.html_safe end end diff --git a/app/helpers/form_page_error_helper.rb b/app/helpers/form_page_error_helper.rb index bf4e1db08..ded09d54c 100644 --- a/app/helpers/form_page_error_helper.rb +++ b/app/helpers/form_page_error_helper.rb @@ -12,9 +12,4 @@ module FormPageErrorHelper errors.each { |error| lettings_log.errors.delete(error.attribute) } end end - - def all_pages_affected_by_errors(log) - question_ids = (log.errors.map(&:attribute) - [:base]).uniq - question_ids.map { |id| log.form.get_question(id, log)&.page&.id }.compact.uniq - end end diff --git a/app/views/form/page.html.erb b/app/views/form/page.html.erb index 7379de0eb..1d4f0e7a1 100644 --- a/app/views/form/page.html.erb +++ b/app/views/form/page.html.erb @@ -16,7 +16,6 @@ <%= form_with model: @log, url: request.original_url, method: "post", local: true do |f| %>
- <% all_pages_with_errors = all_pages_affected_by_errors(@log) %> <% remove_other_page_errors(@log, @page) %> <%= f.govuk_error_summary %> @@ -76,7 +75,7 @@ <%= f.hidden_field :check_errors, value: @check_errors %> <% end %> - <% if all_pages_with_errors.count > 1 %> + <% if @pages_with_errors_count > 1 %>
<%= f.submit "See all related answers", name: "check_errors", class: "govuk-body govuk-link submit-button-link" %>
diff --git a/spec/requests/form_controller_spec.rb b/spec/requests/form_controller_spec.rb index e727d8acc..fbbd5fc85 100644 --- a/spec/requests/form_controller_spec.rb +++ b/spec/requests/form_controller_spec.rb @@ -723,6 +723,26 @@ RSpec.describe FormController, type: :request do end end end + + context "with long error messages" do + let(:sales_log) { create(:sales_log, :completed, assigned_to: user) } + let(:page_id) { "purchase_price" } + let(:params) do + { + id: sales_log.id, + sales_log: { + page: page_id, + "value" => 1, + }, + } + end + + it "can deal with long error messages" do + post "/sales-logs/#{sales_log.id}/#{page_id.dasherize}", params: params + follow_redirect! + expect(page).to have_content("There is a problem") + end + end end context "with invalid organisation answers" do diff --git a/spec/views/form/page_view_spec.rb b/spec/views/form/page_view_spec.rb index a95c98e93..83f76572f 100644 --- a/spec/views/form/page_view_spec.rb +++ b/spec/views/form/page_view_spec.rb @@ -29,6 +29,7 @@ RSpec.describe "form/page" do assign(:log, lettings_log) assign(:page, page) assign(:subsection, subsection) + assign(:pages_with_errors_count, 0) assign_attributes(page, page_attributes) assign_attributes(question, question_attributes) render From df283a7a63f30a945531e4e43c9cafb5e3e31a6a Mon Sep 17 00:00:00 2001 From: Manny Dinssa <44172848+Dinssa@users.noreply.github.com> Date: Thu, 12 Dec 2024 14:09:18 +0000 Subject: [PATCH 34/36] Bug fix (#2866) --- app/models/form/sales/questions/buyer2_income_known.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/form/sales/questions/buyer2_income_known.rb b/app/models/form/sales/questions/buyer2_income_known.rb index 0b125f2af..26d4b2fca 100644 --- a/app/models/form/sales/questions/buyer2_income_known.rb +++ b/app/models/form/sales/questions/buyer2_income_known.rb @@ -2,7 +2,7 @@ class Form::Sales::Questions::Buyer2IncomeKnown < ::Form::Question def initialize(id, hsh, page) super @id = "income2nk" - @copy_key = "sales.income_benefits_and_savings.buyer_2_income.income2" + @copy_key = "sales.income_benefits_and_savings.buyer_2_income.income2nk" @type = "radio" @answer_options = ANSWER_OPTIONS @conditional_for = { From 151a443cf6045472fc3dda6b5d0f4b4d74d3146a Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Fri, 13 Dec 2024 08:35:18 +0000 Subject: [PATCH 35/36] CLDC-3789 Update frontend components (#2854) * Update govuk-components * Use govuk_list for lists * Update govuk-frontend and primary navigation * Update govuk_design_system_formbuilder * lint * Remove primary navigation import * Update tests * Remove border on phase banner * lint --- Gemfile | 4 +- Gemfile.lock | 14 ++-- .../primary_navigation_component.html.erb | 22 ++---- app/frontend/styles/_primary-navigation.scss | 69 ------------------- app/frontend/styles/application.scss | 42 ++++++++++- .../forms/prepare_your_file_2024.html.erb | 19 +++-- .../fix_choice.html.erb | 12 +--- .../forms/prepare_your_file_2024.html.erb | 19 +++-- .../fix_choice.html.erb | 14 +--- .../bulk_upload_shared/guidance.html.erb | 7 +- app/views/cookies/show.html.erb | 6 +- app/views/layouts/application.html.erb | 2 +- app/views/logs/delete_duplicates.html.erb | 10 +-- .../merge_requests/merge_request.html.erb | 30 ++++---- .../add_managing_agent.html.erb | 12 ++-- .../add_stock_owner.html.erb | 12 ++-- .../data_sharing_agreement.html.erb | 65 ++++++++--------- .../organisations/duplicate_schemes.html.erb | 53 +++++--------- app/views/schemes/changes.html.erb | 10 +-- app/views/start/guidance.html.erb | 68 +++++++++--------- package.json | 2 +- .../primary_navigation_component_spec.rb | 2 +- .../auth/passwords_controller_spec.rb | 2 +- ...anisation_relationships_controller_spec.rb | 8 +-- .../requests/organisations_controller_spec.rb | 6 +- yarn.lock | 10 +-- 26 files changed, 209 insertions(+), 311 deletions(-) delete mode 100644 app/frontend/styles/_primary-navigation.scss diff --git a/Gemfile b/Gemfile index 027ed10d0..1028f921a 100644 --- a/Gemfile +++ b/Gemfile @@ -18,9 +18,9 @@ gem "jsbundling-rails" # Reduces boot times through caching; required in config/boot.rb gem "bootsnap", ">= 1.4.4", require: false # GOV UK frontend components -gem "govuk-components", "~> 5.1" +gem "govuk-components", "~> 5.7" # GOV UK component form builder DSL -gem "govuk_design_system_formbuilder", "~> 5.0" +gem "govuk_design_system_formbuilder", "~> 5.7" # Convert Markdown into GOV.UK frontend-styled HTML gem "govuk_markdown" gem "redcarpet", "~> 3.6" diff --git a/Gemfile.lock b/Gemfile.lock index 329c92051..66e4c9c41 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -203,11 +203,11 @@ GEM raabro (~> 1.4) globalid (1.2.1) activesupport (>= 6.1) - govuk-components (5.2.1) + govuk-components (5.7.0) html-attributes-utils (~> 1.0.0, >= 1.0.0) - pagy (~> 6.0) - view_component (>= 3.9, < 3.11) - govuk_design_system_formbuilder (5.2.0) + pagy (>= 6, < 10) + view_component (>= 3.9, < 3.17) + govuk_design_system_formbuilder (5.7.1) actionview (>= 6.1) activemodel (>= 6.1) activesupport (>= 6.1) @@ -287,7 +287,7 @@ GEM childprocess (>= 0.6.3, < 6) iniparse (~> 1.4) rexml (~> 3.2) - pagy (6.5.0) + pagy (9.3.2) paper_trail (15.1.0) activerecord (>= 6.1) request_store (~> 1.4) @@ -532,8 +532,8 @@ DEPENDENCIES excon (~> 0.111.0) factory_bot_rails faker - govuk-components (~> 5.1) - govuk_design_system_formbuilder (~> 5.0) + govuk-components (~> 5.7) + govuk_design_system_formbuilder (~> 5.7) govuk_markdown jsbundling-rails json-schema diff --git a/app/components/primary_navigation_component.html.erb b/app/components/primary_navigation_component.html.erb index 9cad3bc64..032c02b81 100644 --- a/app/components/primary_navigation_component.html.erb +++ b/app/components/primary_navigation_component.html.erb @@ -1,17 +1,5 @@ - +<%= govuk_service_navigation(navigation_id: "primary-navigation", classes: "app-service-navigation") do |sn| + items.each do |item| + sn.with_navigation_item(text: item[:text], href: item[:href], classes: "", current: item[:current]) + end + end %> diff --git a/app/frontend/styles/_primary-navigation.scss b/app/frontend/styles/_primary-navigation.scss deleted file mode 100644 index b21d41fc1..000000000 --- a/app/frontend/styles/_primary-navigation.scss +++ /dev/null @@ -1,69 +0,0 @@ -.app-primary-navigation { - @include govuk-font(19, $weight: bold); - background-color: govuk-colour("light-grey"); - border-bottom: 1px solid $govuk-border-colour; -} - -.govuk-phase-banner + .app-primary-navigation { - margin-top: -1px; -} - -.app-primary-navigation__list { - @include govuk-clearfix; - left: govuk-spacing(-3); - list-style: none; - margin: 0; - padding: 0; - position: relative; - right: govuk-spacing(-3); - width: calc(100% + #{govuk-spacing(6)}); -} - -.app-primary-navigation__item { - box-sizing: border-box; - display: block; - float: left; - line-height: 50px; - height: 50px; - padding: 0 govuk-spacing(3); - position: relative; -} - -.app-primary-navigation__item--current { - border-bottom: $govuk-border-width-narrow solid $govuk-link-colour; - - &:hover { - border-bottom-color: $govuk-link-hover-colour; - } - - &:active { - border-bottom-color: $govuk-link-active-colour; - } -} - -.app-primary-navigation__item--align-right { - @include govuk-media-query($from: tablet) { - float: right; - } -} - -.app-primary-navigation__link { - @include govuk-link-common; - @include govuk-link-style-no-visited-state; - @include govuk-link-style-no-underline; - @include govuk-typography-weight-bold; - - // Extend the touch area of the link to the list - &::after { - bottom: 0; - content: ""; - left: 0; - position: absolute; - right: 0; - top: 0; - } -} - -.app-primary-navigation__item--current .app-primary-navigation__link:hover { - text-decoration: none; -} diff --git a/app/frontend/styles/application.scss b/app/frontend/styles/application.scss index 837b0db6d..3e75107bd 100644 --- a/app/frontend/styles/application.scss +++ b/app/frontend/styles/application.scss @@ -45,7 +45,6 @@ $govuk-breakpoints: ( @import "task-list"; @import "template"; @import "panel"; -@import "primary-navigation"; @import "search"; @import "sub-navigation"; @import "unread-notification"; @@ -86,3 +85,44 @@ $govuk-breakpoints: ( .govuk-notification-banner__content > * { max-width: fit-content; } + +.govuk-service-navigation__active-fallback, +.govuk-service-navigation__list { + font-weight: bold; +} + +.govuk-service-navigation__link { + @include govuk-link-common; + @include govuk-link-style-no-visited-state; + @include govuk-link-style-no-underline; + @include govuk-typography-weight-bold; + + // Extend the touch area of the link to the list + &::after { + bottom: 0; + content: ""; + left: 0; + position: absolute; + right: 0; + top: 0; + } +} + +.govuk-service-navigation__item--active { + border-bottom-width: 4px; +} + +.govuk-service-navigation__item { + padding-right: 15px; + padding-left: 15px; + margin: 0; +} + +.govuk-service-navigation__item:not(:last-child) { + margin-right: 0; +} + +.govuk-service-navigation__container { + left: -15px; + position: relative; +} diff --git a/app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2024.html.erb b/app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2024.html.erb index 0e91061c3..3e7f0443c 100644 --- a/app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2024.html.erb +++ b/app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2024.html.erb @@ -19,22 +19,19 @@

Create your file

-
    -
  • Fill in the template with data from your housing management system. Your data should go below the headers, with one row per log. Leave column A blank - the bulk upload fields start in column B.
  • -
  • Make sure each column of your data aligns with the matching headers above. You may need to reorder your data.
  • -
  • Use the <%= govuk_link_to "Lettings bulk upload Specification (2024 to 2025)", @form.specification_path %> to check your data is in the correct format.
  • -
  • Username field: To assign a log to someone else, enter the email address they use to log into CORE.
  • -
  • If you have reordered the headers, keep the headers in the file.
  • -
+ <%= govuk_list [ + "Fill in the template with data from your housing management system. Your data should go below the headers, with one row per log. Leave column A blank - the bulk upload fields start in column B.", + "Make sure each column of your data aligns with the matching headers above. You may need to reorder your data.", + "Use the #{govuk_link_to 'Lettings bulk upload Specification (2024 to 2025)', @form.specification_path} to check your data is in the correct format.".html_safe, + "Username field: To assign a log to someone else, enter the email address they use to log into CORE.".html_safe, + "If you have reordered the headers, keep the headers in the file.", + ], type: :bullet %> <%= govuk_inset_text(text: "You can upload both general needs and supported housing logs in the same file for 2024 to 2025 data.") %>

Save your file

-
    -
  • Save your file as a CSV.
  • -
  • Your file should now be ready to upload.
  • -
+ <%= govuk_list ["Save your file as a CSV.", "Your file should now be ready to upload."], type: :bullet %> <%= f.govuk_submit class: "govuk-!-margin-top-7" %> <% end %> diff --git a/app/views/bulk_upload_lettings_resume/fix_choice.html.erb b/app/views/bulk_upload_lettings_resume/fix_choice.html.erb index 383cdbfa1..225fb07bf 100644 --- a/app/views/bulk_upload_lettings_resume/fix_choice.html.erb +++ b/app/views/bulk_upload_lettings_resume/fix_choice.html.erb @@ -24,17 +24,9 @@ <%= govuk_details(summary_text: "How to choose between fixing errors on the CORE site or in the CSV") do %>

You may find it easier to fix the errors in the CSV file if:

-
    -
  • you have a lot of errors
  • -
  • the CSV file is formatted incorrectly and you can see where the errors are
  • -
  • you need to fix multiple errors at once
  • -
+ <%= govuk_list ["you have a lot of errors", "the CSV file is formatted incorrectly and you can see where the errors are", "you need to fix multiple errors at once"], type: :bullet %>

You may find it easier to fix the errors on the CORE site if:

-
    -
  • you need to see the data in context
  • -
  • you have a smaller file, with a few errors
  • -
  • you are not sure where the errors are
  • -
+ <%= govuk_list ["you need to see the data in context", "you have a smaller file, with a few errors", "you are not sure where the errors are"], type: :bullet %> <% end %> <%= f.govuk_collection_radio_buttons :choice, diff --git a/app/views/bulk_upload_sales_logs/forms/prepare_your_file_2024.html.erb b/app/views/bulk_upload_sales_logs/forms/prepare_your_file_2024.html.erb index 2a64de689..723ae5314 100644 --- a/app/views/bulk_upload_sales_logs/forms/prepare_your_file_2024.html.erb +++ b/app/views/bulk_upload_sales_logs/forms/prepare_your_file_2024.html.erb @@ -19,19 +19,16 @@

There are 8 rows of content in the templates. These rows are called the ‘headers’. They contain the CORE form questions and guidance about which questions are required and how to format your answers.

Create your file

-
    -
  • Fill in the template with data from your housing management system. Your data should go below the headers, with one row per log. The bulk upload fields start at column B. Leave column A blank.
  • -
  • Make sure each column of your data aligns with the matching headers above. You may need to reorder your data.
  • -
  • Use the <%= govuk_link_to "Sales bulk upload Specification (2024 to 2025)", @form.specification_path %> to check your data is in the correct format.
  • -
  • Username field: To assign a log to someone else, enter the email address they use to log into CORE.
  • -
  • If you have reordered the headers, keep the headers in the file.
  • -
+ <%= govuk_list [ + "Fill in the template with data from your housing management system. Your data should go below the headers, with one row per log. The bulk upload fields start at column B. Leave column A blank.", + "Make sure each column of your data aligns with the matching headers above. You may need to reorder your data.", + "Use the #{govuk_link_to 'Sales bulk upload Specification (2024 to 2025)', @form.specification_path} to check your data is in the correct format.".html_safe, + "Username field: To assign a log to someone else, enter the email address they use to log into CORE.".html_safe, + "If you have reordered the headers, keep the headers in the file.", + ], type: :bullet %>

Save your file

-
    -
  • Save your file as a CSV.
  • -
  • Your file should now be ready to upload.
  • -
+ <%= govuk_list ["Save your file as a CSV.", "Your file should now be ready to upload."], type: :bullet %> <%= f.govuk_submit %> <% end %> diff --git a/app/views/bulk_upload_sales_resume/fix_choice.html.erb b/app/views/bulk_upload_sales_resume/fix_choice.html.erb index 946426d0c..b376ee62d 100644 --- a/app/views/bulk_upload_sales_resume/fix_choice.html.erb +++ b/app/views/bulk_upload_sales_resume/fix_choice.html.erb @@ -23,18 +23,10 @@
<%= govuk_details(summary_text: "How to choose between fixing errors on the CORE site or in the CSV") do %> -

You may find it easier to fix the errors in the CSV file if:

-
    -
  • you have a lot of errors
  • -
  • the CSV file is formatted incorrectly and you can see where the errors are
  • -
  • you need to fix multiple errors at once
  • -
+

You may find it easier to fix the errors in the CSV file if:

+ <%= govuk_list ["you have a lot of errors", "the CSV file is formatted incorrectly and you can see where the errors are", "you need to fix multiple errors at once"], type: :bullet %>

You may find it easier to fix the errors on the CORE site if:

-
    -
  • you need to see the data in context
  • -
  • you have a smaller file, with a few errors
  • -
  • you are not sure where the errors are
  • -
+ <%= govuk_list ["you need to see the data in context", "you have a smaller file, with a few errors", "you are not sure where the errors are"], type: :bullet %> <% end %> <%= f.govuk_collection_radio_buttons :choice, diff --git a/app/views/bulk_upload_shared/guidance.html.erb b/app/views/bulk_upload_shared/guidance.html.erb index 4cb8b76b4..d3950fff7 100644 --- a/app/views/bulk_upload_shared/guidance.html.erb +++ b/app/views/bulk_upload_shared/guidance.html.erb @@ -34,12 +34,7 @@

The bulk upload templates contain 8 rows of ‘headers’ with information about how to fill in the template, including:

<% end %> -
    -
  • the CORE form questions and their field numbers
  • -
  • each field’s valid responses
  • -
  • if/when certain fields can be left blank
  • -
  • which fields are used to check for duplicate logs
  • -
+ <%= govuk_list ["the CORE form questions and their field numbers", "each field’s valid responses", "if/when certain fields can be left blank", "which fields are used to check for duplicate logs"], type: :bullet %>

You can paste your data below the headers or copy the headers and insert them above the data in your file. The bulk upload fields start at column B. Leave column A blank.

Make sure that each column of data aligns with the corresponding question in the headers. We recommend ordering your data to match the headers, but you can also reorder the headers to match your data. When processing the file, we check what each column of data represents based on the headers above.

diff --git a/app/views/cookies/show.html.erb b/app/views/cookies/show.html.erb index 56776b711..632e60af8 100644 --- a/app/views/cookies/show.html.erb +++ b/app/views/cookies/show.html.erb @@ -37,11 +37,7 @@

Google is not allowed to use or share our analytics data with anyone.

Google Analytics stores anonymised information about:

-
    -
  • how you got to the service
  • -
  • the pages you visit on the service and how long you spend on them
  • -
  • any errors you see while using the service
  • -
+ <%= govuk_list ["how you got to the service", "the pages you visit on the service and how long you spend on them", "any errors you see while using the service"], type: :bullet %>

diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index d235491a1..c7d5e2230 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -103,7 +103,7 @@ <% feedback_link = govuk_link_to "giving us your feedback (opens in a new tab)", t("feedback_form"), rel: "noreferrer noopener", target: "_blank" %> <%= govuk_phase_banner( - classes: "govuk-width-container", + classes: "#{current_user.present? ? 'no-bottom-border ' : ''}govuk-width-container", tag: govuk_phase_banner_tag(current_user), text: "This is a new service – help us improve it by #{feedback_link}".html_safe, ) %> diff --git a/app/views/logs/delete_duplicates.html.erb b/app/views/logs/delete_duplicates.html.erb index 912b3491d..2c9acd523 100644 --- a/app/views/logs/delete_duplicates.html.erb +++ b/app/views/logs/delete_duplicates.html.erb @@ -15,15 +15,7 @@

<%= @duplicate_logs.count == 1 ? "This log" : "These logs" %> will be deleted:

-
    - <% @duplicate_logs.each do |duplicate_log| %> -
  • - - <%= govuk_link_to "Log #{duplicate_log.id}", url_for(duplicate_log) %> - -
  • - <% end %> -
+ <%= govuk_list(@duplicate_logs.map { |log| "#{govuk_link_to "Log #{log.id}", url_for(log)}".html_safe }) %>
<%= govuk_button_to @duplicate_logs.count == 1 ? "Delete this log" : "Delete these logs", diff --git a/app/views/merge_requests/merge_request.html.erb b/app/views/merge_requests/merge_request.html.erb index d8602b46c..ec3bcab02 100644 --- a/app/views/merge_requests/merge_request.html.erb +++ b/app/views/merge_requests/merge_request.html.erb @@ -14,13 +14,13 @@

Before you start

Regardless of the merge type, you’ll be asked for:

-
    -
  • the organisations merging in CORE
  • -
  • the merge type: for example one organisation absorbing the rest, or them all creating a new organisation
  • -
  • to confirm or update the telephone number
  • -
  • the merge date
  • -
  • if user email addresses will change with this merge
  • -
+ <%= govuk_list [ + "the organisations merging in CORE", + "the merge type: for example one organisation absorbing the rest, or them all creating a new organisation", + "to confirm or update the telephone number", + "the merge date", + "if user email addresses will change with this merge", + ], type: :bullet %>

If email addresses are changing

<%= govuk_inset_text(text: "Update all user email addresses on CORE as soon as they change, so that everyone can still access their CORE account. ") %> @@ -30,14 +30,14 @@

If merging into a new organisation

You'll also be asked for the new organisation’s:

-
    -
  • name
  • -
  • address
  • -
  • telephone number
  • -
  • housing provider type
  • -
  • Regulator of Social Housing registration number
  • -
  • held stock details
  • -
+ <%= govuk_list [ + "name", + "address", + "telephone number", + "housing provider type", + "Regulator of Social Housing registration number", + "held stock details", + ], type: :bullet %> <%= govuk_warning_text text: "You will not be able to submit your request without the above information. Do not start the form until you have obtained all of the information. " %> diff --git a/app/views/organisation_relationships/add_managing_agent.html.erb b/app/views/organisation_relationships/add_managing_agent.html.erb index 25c7c53a2..71e4b5abc 100644 --- a/app/views/organisation_relationships/add_managing_agent.html.erb +++ b/app/views/organisation_relationships/add_managing_agent.html.erb @@ -28,12 +28,10 @@ <%= govuk_button_link_to("Cancel", managing_agents_organisation_path(@organisation), secondary: true) %>
<%= govuk_details(summary_text: "Can't find the managing agent you're looking for?") do %> -
    -
  • Double check the spelling and try again
  • -
  • Type the first few letters to see the suggestions
  • -
  • If you still can't find it, - <%= govuk_link_to("contact the MHCLG service desk", GlobalConstants::HELPDESK_URL, rel: "noreferrer noopener", target: "_blank") %> -
  • -
+ <%= govuk_list [ + "Double check the spelling and try again", + "Type the first few letters to see the suggestions", + "If you still can't find it, #{govuk_link_to('contact the MHCLG service desk', GlobalConstants::HELPDESK_URL, rel: 'noreferrer noopener', target: '_blank')}", + ], type: :bullet %> <% end %> <% end %> diff --git a/app/views/organisation_relationships/add_stock_owner.html.erb b/app/views/organisation_relationships/add_stock_owner.html.erb index 042442125..1bd522f54 100644 --- a/app/views/organisation_relationships/add_stock_owner.html.erb +++ b/app/views/organisation_relationships/add_stock_owner.html.erb @@ -28,12 +28,10 @@ <%= govuk_button_link_to("Cancel", stock_owners_organisation_path(@organisation), secondary: true) %> <%= govuk_details(summary_text: "Can't find the stock owner you're looking for?") do %> -
    -
  • Double check the spelling and try again
  • -
  • Type the first few letters to see the suggestions
  • -
  • If you still can't find it, - <%= govuk_link_to("contact the MHCLG service desk", GlobalConstants::HELPDESK_URL, rel: "noreferrer noopener", target: "_blank") %> -
  • -
+ <%= govuk_list [ + "Double check the spelling and try again", + "Type the first few letters to see the suggestions", + "If you still can't find it, #{govuk_link_to('contact the MHCLG service desk', GlobalConstants::HELPDESK_URL, rel: 'noreferrer noopener', target: '_blank')}", + ], type: :bullet %> <% end %> <% end %> diff --git a/app/views/organisations/data_sharing_agreement.html.erb b/app/views/organisations/data_sharing_agreement.html.erb index 722d9e176..54b6210c4 100644 --- a/app/views/organisations/data_sharing_agreement.html.erb +++ b/app/views/organisations/data_sharing_agreement.html.erb @@ -35,30 +35,26 @@

It is now agreed as follows:

2. Definitions and interpretation

2.1. In this Agreement the following words and phrases shall have the following meanings, unless expressly stated to the contrary:

-
    -
  • “Act” means the Data Protection Act 2018;
  • -
  • “Authorised Representatives” means the nominated lead officer representing each of the - parties with delegated authority to handle the day-to-day matters arising from this Agreement;
  • -
  • “Data Subject” means social housing lettings tenants and participants in discounted sales where their data is reported via the CORE system.
  • -
  • “Data Controller” has the meaning in Article 4(7) of the GDPR and section 5(2) of the Act.
  • -
  • “Data Processor” has the meaning in Article 4(8) of the GDPR.
  • -
  • “Data Protection Legislation” means the Data Protection Act 2018 and all applicable laws and regulations relating to the processing of personal data and privacy, including where applicable the guidance and codes of practice issued by the Information Commissioner; it includes the General Data Protection Regulation (GDPR).
  • -
  • “Data” means the data supplied by the CORE data providers via the CORE system and the data that is calculated or derived via the CORE system based on that initial data;
  • -
  • “GDPR” means the General Data Protection Regulation.
  • -
  • “Parties” means the parties to this Agreement, namely MHCLG and the CORE data providers. CORE data providers include social housing providers and managing organisations that provide data on behalf of the social housing providers.
  • -
  • “Personal Data” has the meaning in Article 4(1) of the GDPR. “Processing” has the meaning in Article 4(2) of the GDPR.
  • -
  • “Request for Information” means a request for information or a request under the Freedom of Information Act 2000.
  • -
  • “Special category personal data” has the meaning in Article 9(1) of the GDPR. In this Agreement: -
      -
    • A. The masculine includes the feminine and neuter;
    • -
    • B. Person means a natural person;
    • -
    • C. The singular includes the plural and vice versa;
    • -
    • D. A reference to any statute, enactment, order, regulation or other similar instrument - shall be construed as a reference to the statute, enactment, order, regulation or instrument as amended by any subsequent statute, enactment, order, regulation or instrument or as contained in any subsequent re-enactment.
    • - -
  • - -
+ <%= govuk_list [ + "“Act” means the Data Protection Act 2018;", + "“Authorised Representatives” means the nominated lead officer representing each of the parties with delegated authority to handle the day-to-day matters arising from this Agreement;", + "“Data Subject” means social housing lettings tenants and participants in discounted sales where their data is reported via the CORE system.", + "“Data Controller” has the meaning in Article 4(7) of the GDPR and section 5(2) of the Act.", + "“Data Processor” has the meaning in Article 4(8) of the GDPR.", + "“Data Protection Legislation” means the Data Protection Act 2018 and all applicable laws and regulations relating to the processing of personal data and privacy, including where applicable the guidance and codes of practice issued by the Information Commissioner; it includes the General Data Protection Regulation (GDPR).", + "“Data” means the data supplied by the CORE data providers via the CORE system and the data that is calculated or derived via the CORE system based on that initial data;", + "“GDPR” means the General Data Protection Regulation.", + "“Parties” means the parties to this Agreement, namely MHCLG and the CORE data providers. CORE data providers include social housing providers and managing organisations that provide data on behalf of the social housing providers.", + "“Personal Data” has the meaning in Article 4(1) of the GDPR. “Processing” has the meaning in Article 4(2) of the GDPR.", + "“Request for Information” means a request for information or a request under the Freedom of Information Act 2000.", + "“Special category personal data” has the meaning in Article 9(1) of the GDPR. In this Agreement: + #{ govuk_list([ + 'A. The masculine includes the feminine and neuter;', + 'B. Person means a natural person;', + 'C. The singular includes the plural and vice versa;', + 'D. A reference to any statute, enactment, order, regulation or other similar instrument shall be construed as a reference to the statute, enactment, order, regulation or instrument as amended by any subsequent statute, enactment, order, regulation or instrument or as contained in any subsequent re-enactment.', + ], type: :bullet)}".html_safe, + ], type: :bullet %>

2.2. Headings are included in this Agreement for ease of reference only and shall not affect the interpretation or construction of this Agreement.

2.3. References in this Agreement to Clauses, Paragraphs and Annexes are, unless otherwise provided, references to the Clauses, Paragraphs and Annexes of this Agreement.

2.4. In the event and to the extent only of any conflict or inconsistency between the provisions of this Agreement and the provisions of any document referred to or referenced herein, the provisions of this Agreement shall prevail.

@@ -95,11 +91,13 @@

8.5. All work carried out by MHCLG will follow appropriate security measures and procedures to ensure the protection of the data.

9. Protection of personal data

9.1. CORE data providers and MHCLG agree that they shall:

-
    -
  • A. Implement appropriate technical and organisational measures to protect the Personal Data against unauthorised or unlawful Processing and against accidental loss, destruction, damage, alteration or disclosure. These measures shall ensure a level of security appropriate to the harm which might result from any unauthorised or unlawful Processing, accidental loss, destruction or damage to the Personal Data and having regard to the nature of the Personal Data which is to be protected;
  • -
  • B. Take reasonable steps to ensure the reliability of any personnel who have access to the Personal Data. MHCLG and Data providers will ensure such personnel will be a limited number of analysts assigned to the data collection.
  • -
+ <%= govuk_list [ + "A. Implement appropriate technical and organisational measures to protect the Personal Data against unauthorised or unlawful Processing and against accidental loss, destruction, damage, alteration or disclosure. These measures shall ensure a level of security appropriate to the harm which might result from any unauthorised or unlawful Processing, accidental loss, destruction or damage to the Personal Data and having regard to the nature of the Personal Data which is to be protected;", + "B. Take reasonable steps to ensure the reliability of any personnel who have access to the Personal Data. MHCLG and Data providers will ensure such personnel will be a limited number of analysts assigned to the data collection.", +], + type: :bullet %> +

9.2. The data providers and MHCLG shall comply at all times with the Data Protection Legislation and shall ensure that they each perform their obligations under this agreement in full compliance with the Data Protection Legislation and any other applicable law, in particular the Human Rights Act 1998 and the common law duty of confidentiality.

9.3. CORE data providers should limit access to CORE to a small number of individuals who can be named on request. CORE access is limited to registered users only via password, but it is the responsibility of the CORE data providers to ensure that all individuals granted access to the datasets should be briefed on the legal requirements around handling and storing the Data from CORE.

10. Freedom of information

@@ -128,15 +126,10 @@

16.1. The Parties shall comply with all relevant legislation, regulations, orders, statutory instruments and any amendments or re-enactments thereof from the commencement of this agreement.

As witness of which the parties have set their hands on the day and year first above written signed for and on behalf of the Data Protection Officer for <%= org_name_for_data_sharing_agreement(@data_protection_confirmation, current_user) %>, by:

-
    -
  • Name: <%= name_for_data_sharing_agreement(@data_protection_confirmation, current_user) %>
  • -
  • Title: Data Protection Officer
  • -
+ <%= govuk_list ["Name: #{name_for_data_sharing_agreement(@data_protection_confirmation, current_user)}".html_safe, "Title: Data Protection Officer"], type: :bullet %> +

SIGNED for and on behalf of the deputy director of the data, analytics & statistics in the Ministry of Housing, Communities and Local Government, by:

-
    -
  • Name: Sandra Tudor
  • -
  • Title: Deputy Director
  • -
+ <%= govuk_list ["Name: Sandra Tudor", "Title: Deputy Director"], type: :bullet %> <% if current_user.is_dpo? && !(@organisation.data_protection_confirmed?) %>
diff --git a/app/views/organisations/duplicate_schemes.html.erb b/app/views/organisations/duplicate_schemes.html.erb index 79f7d435f..a1f341f7f 100644 --- a/app/views/organisations/duplicate_schemes.html.erb +++ b/app/views/organisations/duplicate_schemes.html.erb @@ -29,11 +29,11 @@

Since your organisation recently merged, we’ve reviewed your schemes for possible duplicates.

These sets of schemes and locations might be duplicates because they have the same answers for certain fields.

What you need to do

-
    -
  • Review each set of schemes or locations and decide if they are duplicates.
  • -
  • If they are, choose one to keep and deactivate the others on the date your organisation merged.
  • -
  • When you have resolved all duplicates, confirm below.
  • -
+ <%= govuk_list [ + "Review each set of schemes or locations and decide if they are duplicates.", + "If they are, choose one to keep and deactivate the others on the date your organisation merged.", + "When you have resolved all duplicates, confirm below.", + ], type: :bullet %>

If you need help with this, <%= govuk_link_to "contact the helpdesk (opens in a new tab)", GlobalConstants::HELPDESK_URL, target: "#" %>.

<% if @duplicate_schemes.any? %> @@ -43,17 +43,17 @@

These schemes have the same answers for the following fields:

-
    -
  • Type of scheme
  • -
  • Registered under Care Standards Act 2000
  • -
  • Housing stock owned by
  • -
  • Support services provided by
  • -
  • Primary client group
  • -
  • Has another client group
  • -
  • Secondary client group
  • -
  • Level of support given
  • -
  • Intended length of stay
  • -
+ <%= govuk_list [ + "Type of scheme", + "Registered under Care Standards Act 2000", + "Housing stock owned by", + "Support services provided by", + "Primary client group", + "Has another client group", + "Secondary client group", + "Level of support given", + "Intended length of stay", + ], type: :bullet %> <% end %>

The links below open in a new tab.

@@ -68,13 +68,7 @@ <% @duplicate_schemes.each do |duplicate_set| %> <% body.with_row do |row| %> <% row.with_cell do %> -
    - <% duplicate_set.each do |scheme| %> -
  1. - <%= govuk_link_to scheme.service_name, scheme, target: "#" %> -
  2. - <% end %> -
+ <%= govuk_list duplicate_set.map { |scheme| govuk_link_to(scheme.service_name, scheme, target: "#") }, type: :number %> <% end %> <% end %> <% end %> @@ -89,10 +83,7 @@

These locations belong to the same scheme and have the same answers for the following fields:

-
    -
  • Postcode
  • -
  • Mobility standards
  • -
+ <%= govuk_list ["Postcode", "Mobility standards"], type: :bullet %> <% end %>

The links below open in a new tab.

@@ -108,13 +99,7 @@ <% @duplicate_locations.each do |duplicate_set| %> <% body.with_row do |row| %> <% row.with_cell do %> -
    - <% duplicate_set[:locations].each do |location| %> -
  1. - <%= govuk_link_to location.name, scheme_location_path(location), target: "#" %> -
  2. - <% end %> -
+ <%= govuk_list duplicate_set[:locations].map { |location| govuk_link_to(location.name, scheme_location_path(location), target: "#") }, type: :number %> <% end %> <% row.with_cell do %> <%= govuk_link_to duplicate_set[:scheme].service_name, duplicate_set[:scheme], target: "#" %> diff --git a/app/views/schemes/changes.html.erb b/app/views/schemes/changes.html.erb index 9040d7a05..03ec75e0c 100644 --- a/app/views/schemes/changes.html.erb +++ b/app/views/schemes/changes.html.erb @@ -15,11 +15,11 @@

We have restructured the way we group supported housing scheme data.

On old CORE, a scheme’s data was split into management group and scheme. On new CORE, the data is split into scheme and location.

These are the main changes:

-
    -
  • Schemes now store data about the stock owner and support provider. This was previously stored in management group.
  • -
  • Schemes still store data about the type of support, as they did in old CORE.
  • -
  • Schemes no longer store data about postcodes where support is provided. These are now in locations.
  • -
+ <%= govuk_list [ + "Schemes now store data about the stock owner and support provider. This was previously stored in management group.", + "Schemes still store data about the type of support, as they did in old CORE.", + "Schemes no longer store data about postcodes where support is provided. These are now in locations.", + ], type: :bullet %>

This new structure means data coordinators only needs to fill in support details once.

How schemes are migrated from old CORE

If your organisation has migrated from old CORE to new CORE, your existing schemes are on the <%= govuk_link_to("schemes page", schemes_path) %>, with the same details.

diff --git a/app/views/start/guidance.html.erb b/app/views/start/guidance.html.erb index 6fbd157f0..367c6f05f 100644 --- a/app/views/start/guidance.html.erb +++ b/app/views/start/guidance.html.erb @@ -21,46 +21,50 @@ <%= accordion.with_section(heading_text: "Types of lettings you should create logs for") do %>

You’ll need to create a log for:

-
    -
  • Tenants in general needs housing allocated a new letting. This includes tenants moving into the social rented sector from outside, existing social tenants moving between properties or landlords, and existing social tenants renewing lettings in the same property. If fixed-term and social or affordable rent, only include tenancies of 2 years or more.
  • -
  • Tenants in supported housing (social housing, sheltered accommodation and care homes) allocated a new letting. This includes tenants moving into the social rented sector from outside, existing social tenants moving between properties or landlords, and existing social tenants renewing lettings in the same property. All supported housing tenancies should be reported regardless of length.
  • -
  • Starter tenancies provided by local authorities (LAs) and lettings with an introductory period provided by private registered providers (PRPs) should be completed in CORE at the beginning of the starter or introductory period. The tenancy type and length entered should be based on the tenancy the tenant will roll onto once the starter or introductory period has been completed. You do not need to submit another CORE log once the period has been completed.
  • -
  • Room moves within a shared housing unit that result in a different property type or support needs – this is classed as an internal transfer of an existing social tenant to another property.
  • -
  • Existing tenants who are issued with a new tenancy agreement when stock is acquired, transferred or permanently decanted.
  • -
  • Tenants under the Rough Sleepers Initiative or Rough Sleeping Accommodation Programme, where accommodation is permanent.
  • -
  • Households previously provided with temporary accommodation to meet a duty under the homelessness legislation who are allocated a tenancy as a settled home ending the duty (this may be the same property).
  • -
  • Refugees and asylum seekers who have been granted indefinite leave to remain, humanitarian protection or exceptional leave to remain.
  • -
  • Affordable Rent lettings – where up to 80% of market rent can be charged and a new supply agreement is signed.
  • -
  • London Affordable Rent lettings – a type of Affordable Rent available in London through the Greater London Authority (GLA).
  • -
  • Intermediate Rent lettings – where the rent must not exceed 80% of the current market rate (including any service charges).
  • -
  • Rent to Buy lettings – where a discount of up to 20% market rent is charged for a single rental period for a minimum of 5 years. After that period, the tenant is offered the chance to purchase the property (either shared ownership or outright) at full market value.
  • -
  • London Living Rent lettings – a type of Intermediate Rent available in London through the Greater London Authority (GLA).
  • -
+ <%= govuk_list [ + "Tenants in general needs housing allocated a new letting. This includes tenants moving into the social rented sector from outside, existing social tenants moving between properties or landlords, and existing social tenants renewing lettings in the same property. If fixed-term and social or affordable rent, only include tenancies of 2 years or more.", + "Tenants in supported housing (social housing, sheltered accommodation and care homes) allocated a new letting. This includes tenants moving into the social rented sector from outside, existing social tenants moving between properties or landlords, and existing social tenants renewing lettings in the same property. All supported housing tenancies should be reported regardless of length.", + "Starter tenancies provided by local authorities (LAs) and lettings with an introductory period provided by private registered providers (PRPs) should be completed in CORE at the beginning of the starter or introductory period. The tenancy type and length entered should be based on the tenancy the tenant will roll onto once the starter or introductory period has been completed. You do not need to submit another CORE log once the period has been completed.", + "Room moves within a shared housing unit that result in a different property type or support needs – this is classed as an internal transfer of an existing social tenant to another property.", + "Existing tenants who are issued with a new tenancy agreement when stock is acquired, transferred or permanently decanted.", + "Tenants under the Rough Sleepers Initiative or Rough Sleeping Accommodation Programme, where accommodation is permanent.", + "Households previously provided with temporary accommodation to meet a duty under the homelessness legislation who are allocated a tenancy as a settled home ending the duty (this may be the same property).", + "Refugees and asylum seekers who have been granted indefinite leave to remain, humanitarian protection or exceptional leave to remain.", + "Affordable Rent lettings – where up to 80% of market rent can be charged and a new supply agreement is signed.", + "London Affordable Rent lettings – a type of Affordable Rent available in London through the Greater London Authority (GLA).", + "Intermediate Rent lettings – where the rent must not exceed 80% of the current market rate (including any service charges).", + "Rent to Buy lettings – where a discount of up to 20% market rent is charged for a single rental period for a minimum of 5 years. After that period, the tenant is offered the chance to purchase the property (either shared ownership or outright) at full market value.", + "London Living Rent lettings – a type of Intermediate Rent available in London through the Greater London Authority (GLA).", + ], + type: :bullet %> <% end %> <%= accordion.with_section(heading_text: "Types of lettings you should not create logs for") do %>

You don’t need to create a log for:

-
    -
  • Temporary general needs housing with a fixed period of less than 2 years if they are social or affordable rent. (Temporary lettings for intermediate rent and supported housing should be recorded).
  • -
  • Starter tenancies or lettings with an introductory period that roll onto or convert into the main tenancy. The CORE log should be completed at the beginning of this period.
  • -
  • Changes from sole to joint or joint to sole tenancies, where the number of people in the household has not changed.
  • -
  • Moves within a shared housing unit resulting in the same support needs or property type, even if a new tenancy or licence agreement is issued.
  • -
  • Lettings where no new tenancy agreement is signed.
  • -
  • Where stock is acquired, transferred or permanently decanted and the existing tenants are not issued with a new tenancy agreement.
  • -
  • Mutual exchanges including lettings where registered provider tenants have exchanged homes, for example through the national HOMESWAP system.
  • -
  • Successions and assignments.
  • -
  • Demotion of a secure or assured tenancy, and any subsequent conversion of the demoted tenancy to a secure or assured tenancy.
  • -
  • Lettings made to asylum seekers who are awaiting a decision on their applications for asylum under the Immigration and Asylum Act 1999.
  • -
  • Non-social lettings, including market-rented properties, employer-provided housing where the employer provides financial support, homes for staff of social landlords linked to employment, homes social landlords manage for organisations who are not social landlords, homes social landlords own but lease in entirety to organisations who are not social landlords, and freehold housing with variable charges for services and communal facilities.
  • -
+ <%= govuk_list [ + "Temporary general needs housing with a fixed period of less than 2 years if they are social or affordable rent. (Temporary lettings for intermediate rent and supported housing should be recorded).", + "Starter tenancies or lettings with an introductory period that roll onto or convert into the main tenancy. The CORE log should be completed at the beginning of this period.", + "Changes from sole to joint or joint to sole tenancies, where the number of people in the household has not changed.", + "Moves within a shared housing unit resulting in the same support needs or property type, even if a new tenancy or licence agreement is issued.", + "Lettings where no new tenancy agreement is signed.", + "Where stock is acquired, transferred or permanently decanted and the existing tenants are not issued with a new tenancy agreement.", + "Mutual exchanges including lettings where registered provider tenants have exchanged homes, for example through the national HOMESWAP system.", + "Successions and assignments.", + "Demotion of a secure or assured tenancy, and any subsequent conversion of the demoted tenancy to a secure or assured tenancy.", + "Lettings made to asylum seekers who are awaiting a decision on their applications for asylum under the Immigration and Asylum Act 1999.", + "Non-social lettings, including market-rented properties, employer-provided housing where the employer provides financial support, homes for staff of social landlords linked to employment, homes social landlords manage for organisations who are not social landlords, homes social landlords own but lease in entirety to organisations who are not social landlords, and freehold housing with variable charges for services and communal facilities.", + ], + type: :bullet %> <% end %> <%= accordion.with_section(heading_text: "What if someone is reluctant to answer any questions?") do %>

If a tenant or buyer is reluctant to answer questions as part of a log, you should explain that:

-
    -
  • all information they provide is anonymous and will not affect their housing, benefits or other services they receive.
  • -
  • the data they provide is vital in helping to build a complete picture of social housing in England and is used to inform social housing policy.
  • -
+ <%= govuk_list [ + "all information they provide is anonymous and will not affect their housing, benefits or other services they receive.", + "the data they provide is vital in helping to build a complete picture of social housing in England and is used to inform social housing policy.", + ], + type: :bullet %> +

If a tenant or buyer is still unwilling or unable to answer questions, select the ‘Don’t know’ or ‘Tenant/person prefers not to say’ options.

<% end %> <% end %> diff --git a/package.json b/package.json index ef37dc8aa..32063907c 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "css-loader": "^6.7.1", "custom-event-polyfill": "^1.0.7", "file-loader": "^6.2.0", - "govuk-frontend": "5.1.0", + "govuk-frontend": "5.7.1", "html5shiv": "^3.7.3", "intersection-observer": "^0.12.0", "mini-css-extract-plugin": "^2.6.0", diff --git a/spec/components/primary_navigation_component_spec.rb b/spec/components/primary_navigation_component_spec.rb index f5f41bff0..aeefa6130 100644 --- a/spec/components/primary_navigation_component_spec.rb +++ b/spec/components/primary_navigation_component_spec.rb @@ -14,7 +14,7 @@ RSpec.describe PrimaryNavigationComponent, type: :component do it "then that item appears as selected" do result = render_inline(described_class.new(items:)) - expect(result.css('.app-primary-navigation__link[aria-current="page"]').text).to include("Organisations") + expect(result.css('.govuk-service-navigation__link[aria-current="page"]').text).to include("Organisations") end end diff --git a/spec/requests/auth/passwords_controller_spec.rb b/spec/requests/auth/passwords_controller_spec.rb index 7f9bdfa30..f35df1c5a 100644 --- a/spec/requests/auth/passwords_controller_spec.rb +++ b/spec/requests/auth/passwords_controller_spec.rb @@ -70,7 +70,7 @@ RSpec.describe Auth::PasswordsController, type: :request do put "/account/password", params: update_password_params # Devise redirects once after re-sign in with new password and then root redirects as well. follow_redirect! - expect(page).to have_css("div", class: "govuk-notification-banner__heading", text: message) + expect(page).to have_css("p", class: "govuk-notification-banner__heading", text: message) end end end diff --git a/spec/requests/organisation_relationships_controller_spec.rb b/spec/requests/organisation_relationships_controller_spec.rb index 8733fba4b..1b1b3169e 100644 --- a/spec/requests/organisation_relationships_controller_spec.rb +++ b/spec/requests/organisation_relationships_controller_spec.rb @@ -29,7 +29,7 @@ RSpec.describe OrganisationRelationshipsController, type: :request do end it "shows the tab navigation" do - expected_html = "
Google Analytics cookies