From b47738c4bec6f6b681ef36b9cbffd09d40ae4794 Mon Sep 17 00:00:00 2001 From: Manny Dinssa <44172848+Dinssa@users.noreply.github.com> Date: Mon, 23 Sep 2024 15:31:41 +0100 Subject: [PATCH 01/14] CLDC-3632 Bulk upload hide empty critical errors table (#2627) * Make critical errors table conditionally shown * Add the same margin to buttons, just like in lettings logs and sales logs page --- .../bulk_upload_error_row_component.html.erb | 37 ++++++++++--------- app/views/schemes/index.html.erb | 4 +- app/views/users/index.html.erb | 4 +- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/app/components/bulk_upload_error_row_component.html.erb b/app/components/bulk_upload_error_row_component.html.erb index 9447c9d37..65e38613f 100644 --- a/app/components/bulk_upload_error_row_component.html.erb +++ b/app/components/bulk_upload_error_row_component.html.erb @@ -9,24 +9,27 @@
<% potential_errors, critical_errors = bulk_upload_errors.partition { |error| error.category == "soft_validation" } %> -

Critical errors

-

These errors must be fixed to complete your logs.

- <%= govuk_table do |table| %> - <%= table.with_head do |head| %> - <% head.with_row do |row| %> - <% row.with_cell(header: true, text: "Cell") %> - <% row.with_cell(header: true, text: "Question") %> - <% row.with_cell(header: true, text: "Error") %> - <% row.with_cell(header: true, text: "Specification") %> - <% end %> - <%= table.with_body do |body| %> - <% critical_errors.each do |error| %> - <% body.with_row do |row| %> - <% row.with_cell(text: error.cell) %> - <% row.with_cell(text: question_for_field(error.field), html_attributes: { class: "govuk-!-width-one-half" }) %> - <% row.with_cell(text: error.error.html_safe, html_attributes: { class: "govuk-!-font-weight-bold govuk-!-width-one-half" }) %> - <% row.with_cell(text: error.field.humanize) %> + <% if critical_errors.any? %> +

Critical errors

+

These errors must be fixed to complete your logs.

+ <%= govuk_table do |table| %> + <%= table.with_head do |head| %> + <% head.with_row do |row| %> + <% row.with_cell(header: true, text: "Cell") %> + <% row.with_cell(header: true, text: "Question") %> + <% row.with_cell(header: true, text: "Error") %> + <% row.with_cell(header: true, text: "Specification") %> + <% end %> + + <%= table.with_body do |body| %> + <% critical_errors.each do |error| %> + <% body.with_row do |row| %> + <% row.with_cell(text: error.cell) %> + <% row.with_cell(text: question_for_field(error.field), html_attributes: { class: "govuk-!-width-one-half" }) %> + <% row.with_cell(text: error.error.html_safe, html_attributes: { class: "govuk-!-font-weight-bold govuk-!-width-one-half" }) %> + <% row.with_cell(text: error.field.humanize) %> + <% end %> <% end %> <% end %> <% end %> diff --git a/app/views/schemes/index.html.erb b/app/views/schemes/index.html.erb index d0ae44072..edaed6212 100644 --- a/app/views/schemes/index.html.erb +++ b/app/views/schemes/index.html.erb @@ -6,7 +6,9 @@ <%= render partial: "organisations/headings", locals: current_user.support? ? { main: "Supported housing schemes", sub: nil } : { main: "Supported housing schemes", sub: current_user.organisation.name } %> <% if SchemePolicy.new(current_user, nil).create? %> - <%= govuk_button_link_to "Create a new supported housing scheme", new_scheme_path, html: { method: :post } %> +
+ <%= govuk_button_link_to "Create a new supported housing scheme", new_scheme_path, html: { method: :post } %> +
<% end %>
<%= render partial: "schemes/scheme_filters" %> diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index 7da3deda1..4e5053563 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb @@ -6,7 +6,9 @@ <%= render partial: "organisations/headings", locals: current_user.support? ? { main: "Users", sub: nil } : { main: "Users", sub: current_user.organisation.name } %> <% if current_user.data_coordinator? || current_user.support? %> - <%= govuk_button_link_to "Invite user", new_user_path, html: { method: :get } %> +
+ <%= govuk_button_link_to "Invite user", new_user_path, html: { method: :get } %> +
<% end %>
<%= render partial: "users/user_filters" %> From e604d358b35502ea7be08ad38e2baeadbaf7ae11 Mon Sep 17 00:00:00 2001 From: Manny Dinssa <44172848+Dinssa@users.noreply.github.com> Date: Mon, 23 Sep 2024 15:45:35 +0100 Subject: [PATCH 02/14] CLDC-3627 Problem displaying apostrophes in browser tab title (#2643) --- app/helpers/application_helper.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index bb119b29e..5a9203da8 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -2,6 +2,7 @@ module ApplicationHelper include Pagy::Frontend def browser_title(title, pagy, *resources) + title = sanitize(title)&.gsub("&", "&") if resources.any? { |r| r.present? && r.errors.present? } "Error: #{[title, t('service_name'), 'GOV.UK'].select(&:present?).join(' - ')}" else From 806424992579127dffdee614216ed79d6b9c3af2 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:31:23 +0100 Subject: [PATCH 03/14] Add checked field to log_validations (#2657) --- db/migrate/20240923145326_add_validation_checked_field.rb | 5 +++++ db/schema.rb | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20240923145326_add_validation_checked_field.rb diff --git a/db/migrate/20240923145326_add_validation_checked_field.rb b/db/migrate/20240923145326_add_validation_checked_field.rb new file mode 100644 index 000000000..899992974 --- /dev/null +++ b/db/migrate/20240923145326_add_validation_checked_field.rb @@ -0,0 +1,5 @@ +class AddValidationCheckedField < ActiveRecord::Migration[7.0] + def change + add_column :log_validations, :checked, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index 80463eaad..cd7708957 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_09_18_112702) do +ActiveRecord::Schema[7.0].define(version: 2024_09_23_145326) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -410,6 +410,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_09_18_112702) do t.string "other_validated_models" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.boolean "checked" end create_table "logs_exports", force: :cascade do |t| From d0ce0b87f2390079444a08dd09bd8aa6f81bc4ab Mon Sep 17 00:00:00 2001 From: Manny Dinssa <44172848+Dinssa@users.noreply.github.com> Date: Tue, 24 Sep 2024 11:21:18 +0100 Subject: [PATCH 04/14] CLDC 3623 Support user bulk uploading (#2629) --- .../create_log_actions_component.html.erb | 2 +- .../bulk_upload_lettings_logs_controller.rb | 6 +-- .../bulk_upload_sales_logs_controller.rb | 6 +-- .../bulk_upload_lettings/checking_file.rb | 7 ++- .../forms/bulk_upload_lettings/guidance.rb | 3 +- .../forms/bulk_upload_lettings/needstype.rb | 5 +- .../bulk_upload_lettings/prepare_your_file.rb | 8 +-- .../bulk_upload_lettings/upload_your_file.rb | 7 +-- app/models/forms/bulk_upload_lettings/year.rb | 9 +++- .../forms/bulk_upload_sales/checking_file.rb | 7 ++- .../forms/bulk_upload_sales/guidance.rb | 3 +- .../bulk_upload_sales/prepare_your_file.rb | 7 ++- .../bulk_upload_sales/upload_your_file.rb | 6 ++- app/models/forms/bulk_upload_sales/year.rb | 9 +++- .../lettings/year2024/row_parser.rb | 28 +++++++--- .../bulk_upload/sales/year2024/row_parser.rb | 28 +++++++--- .../forms/checking_file.html.erb | 1 + .../forms/needstype.erb | 1 + .../forms/prepare_your_file_2024.html.erb | 1 + .../forms/upload_your_file.html.erb | 1 + .../forms/year.html.erb | 1 + .../show.html.erb | 2 +- .../summary.html.erb | 2 +- .../forms/checking_file.html.erb | 1 + .../forms/prepare_your_file_2024.html.erb | 1 + .../forms/upload_your_file.html.erb | 1 + .../forms/year.html.erb | 1 + .../bulk_upload_sales_results/show.html.erb | 2 +- .../summary.html.erb | 2 +- .../logs/_create_for_org_actions.html.erb | 10 ++-- ...332_add_organisation_id_to_bulk_uploads.rb | 5 ++ db/schema.rb | 1 + spec/factories/bulk_upload.rb | 1 + spec/features/organisation_spec.rb | 8 +++ .../lettings/year2024/row_parser_spec.rb | 54 +++++++++++++++++++ .../sales/year2024/row_parser_spec.rb | 54 +++++++++++++++++++ 36 files changed, 244 insertions(+), 47 deletions(-) create mode 100644 db/migrate/20240905092332_add_organisation_id_to_bulk_uploads.rb diff --git a/app/components/create_log_actions_component.html.erb b/app/components/create_log_actions_component.html.erb index 1b6bd8aca..5a90587ed 100644 --- a/app/components/create_log_actions_component.html.erb +++ b/app/components/create_log_actions_component.html.erb @@ -3,7 +3,7 @@ <% if create_button_href.present? %> <%= govuk_button_to create_button_copy, create_button_href, class: "govuk-!-margin-right-6" %> <% end %> - <% if upload_button_href.present? %> + <% if upload_button_href.present? && !user.support? %> <%= govuk_button_link_to upload_button_copy, upload_button_href, secondary: true %> <% end %>
diff --git a/app/controllers/bulk_upload_lettings_logs_controller.rb b/app/controllers/bulk_upload_lettings_logs_controller.rb index 465bbc5f6..a8ad14f9e 100644 --- a/app/controllers/bulk_upload_lettings_logs_controller.rb +++ b/app/controllers/bulk_upload_lettings_logs_controller.rb @@ -4,9 +4,9 @@ class BulkUploadLettingsLogsController < ApplicationController def start if have_choice_of_year? - redirect_to bulk_upload_lettings_log_path(id: "year") + redirect_to bulk_upload_lettings_log_path(id: "year", form: { organisation_id: params[:organisation_id] }.compact) else - redirect_to bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year: current_year }) + redirect_to bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year: current_year, organisation_id: params[:organisation_id] }.compact) end end @@ -60,6 +60,6 @@ private end def form_params - params.fetch(:form, {}).permit(:year, :needstype, :file) + params.fetch(:form, {}).permit(:year, :needstype, :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 56bd1d4de..2fd312d10 100644 --- a/app/controllers/bulk_upload_sales_logs_controller.rb +++ b/app/controllers/bulk_upload_sales_logs_controller.rb @@ -4,9 +4,9 @@ class BulkUploadSalesLogsController < ApplicationController def start if have_choice_of_year? - redirect_to bulk_upload_sales_log_path(id: "year") + redirect_to bulk_upload_sales_log_path(id: "year", form: { organisation_id: params[:organisation_id] }.compact) else - redirect_to bulk_upload_sales_log_path(id: "prepare-your-file", form: { year: current_year }) + redirect_to bulk_upload_sales_log_path(id: "prepare-your-file", form: { year: current_year, organisation_id: params[:organisation_id] }.compact) end end @@ -58,6 +58,6 @@ private end def form_params - params.fetch(:form, {}).permit(:year, :file) + params.fetch(:form, {}).permit(:year, :file, :organisation_id) end end diff --git a/app/models/forms/bulk_upload_lettings/checking_file.rb b/app/models/forms/bulk_upload_lettings/checking_file.rb index 8cd9ee696..3b43cb269 100644 --- a/app/models/forms/bulk_upload_lettings/checking_file.rb +++ b/app/models/forms/bulk_upload_lettings/checking_file.rb @@ -6,13 +6,18 @@ module Forms include Rails.application.routes.url_helpers attribute :year, :integer + attribute :organisation_id, :integer def view_path "bulk_upload_lettings_logs/forms/checking_file" end def back_path - bulk_upload_lettings_log_path(id: "start") + if organisation_id.present? + lettings_logs_organisation_path(organisation_id) + else + bulk_upload_lettings_log_path(id: "start") + end end def year_combo diff --git a/app/models/forms/bulk_upload_lettings/guidance.rb b/app/models/forms/bulk_upload_lettings/guidance.rb index b6cf5bf74..24bc531f2 100644 --- a/app/models/forms/bulk_upload_lettings/guidance.rb +++ b/app/models/forms/bulk_upload_lettings/guidance.rb @@ -7,6 +7,7 @@ module Forms attribute :year, :integer attribute :referrer + attribute :organisation_id, :integer def view_path "bulk_upload_shared/guidance" @@ -15,7 +16,7 @@ module Forms def back_path case referrer when "prepare-your-file" - bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year: }) + bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year:, organisation_id: }.compact) when "home" root_path else diff --git a/app/models/forms/bulk_upload_lettings/needstype.rb b/app/models/forms/bulk_upload_lettings/needstype.rb index 0cadd647c..32ed0acf3 100644 --- a/app/models/forms/bulk_upload_lettings/needstype.rb +++ b/app/models/forms/bulk_upload_lettings/needstype.rb @@ -7,6 +7,7 @@ module Forms attribute :needstype, :integer attribute :year, :integer + attribute :organisation_id, :integer validates :needstype, presence: true @@ -19,11 +20,11 @@ module Forms end def back_path - bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year:, needstype: }) + 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: }) + bulk_upload_lettings_log_path(id: "upload-your-file", form: { year:, needstype:, organisation_id: }.compact) end def year_combo diff --git a/app/models/forms/bulk_upload_lettings/prepare_your_file.rb b/app/models/forms/bulk_upload_lettings/prepare_your_file.rb index b5069d908..159436ce1 100644 --- a/app/models/forms/bulk_upload_lettings/prepare_your_file.rb +++ b/app/models/forms/bulk_upload_lettings/prepare_your_file.rb @@ -7,6 +7,7 @@ module Forms attribute :year, :integer attribute :needstype, :integer + attribute :organisation_id, :integer def view_path case year @@ -19,15 +20,16 @@ module Forms def back_path if have_choice_of_year? - Rails.application.routes.url_helpers.bulk_upload_lettings_log_path(id: "year", form: { year: }) + Rails.application.routes.url_helpers.bulk_upload_lettings_log_path(id: "year", form: { year:, organisation_id: }.compact) + elsif organisation_id.present? + lettings_logs_organisation_path(organisation_id) else Rails.application.routes.url_helpers.lettings_logs_path end end def next_path - page_id = year == 2022 ? "needstype" : "upload-your-file" - bulk_upload_lettings_log_path(id: page_id, form: { year:, needstype: }) + bulk_upload_lettings_log_path(id: "upload-your-file", form: { year:, needstype:, organisation_id: }.compact) end def legacy_template_path diff --git a/app/models/forms/bulk_upload_lettings/upload_your_file.rb b/app/models/forms/bulk_upload_lettings/upload_your_file.rb index 9ccec7622..954c6e6e6 100644 --- a/app/models/forms/bulk_upload_lettings/upload_your_file.rb +++ b/app/models/forms/bulk_upload_lettings/upload_your_file.rb @@ -11,6 +11,7 @@ module Forms attribute :needstype, :integer attribute :file attribute :current_user + attribute :organisation_id, :integer validates :file, presence: true validate :validate_file_is_csv @@ -20,8 +21,7 @@ module Forms end def back_path - page_id = year == 2022 ? "needstype" : "prepare-your-file" - bulk_upload_lettings_log_path(id: page_id, form: { year:, needstype: }) + bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year:, needstype:, organisation_id: }.compact) end def year_combo @@ -29,7 +29,7 @@ module Forms end def next_path - bulk_upload_lettings_log_path(id: "checking-file", form: { year: }) + bulk_upload_lettings_log_path(id: "checking-file", form: { year:, organisation_id: }.compact) end def save! @@ -39,6 +39,7 @@ module Forms year:, needstype:, filename: file.original_filename, + organisation_id: (organisation_id if current_user.support?) || current_user.organisation_id, ) storage_service.write_file(bulk_upload.identifier, File.read(file.path)) diff --git a/app/models/forms/bulk_upload_lettings/year.rb b/app/models/forms/bulk_upload_lettings/year.rb index e199559fa..396b91a79 100644 --- a/app/models/forms/bulk_upload_lettings/year.rb +++ b/app/models/forms/bulk_upload_lettings/year.rb @@ -6,6 +6,7 @@ module Forms include Rails.application.routes.url_helpers attribute :year, :integer + attribute :organisation_id, :integer validates :year, presence: true @@ -20,11 +21,15 @@ module Forms end def back_path - lettings_logs_path + if organisation_id.present? + lettings_logs_organisation_path(organisation_id) + else + lettings_logs_path + end end def next_path - bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year: }) + bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year:, organisation_id: }.compact) end def save! diff --git a/app/models/forms/bulk_upload_sales/checking_file.rb b/app/models/forms/bulk_upload_sales/checking_file.rb index a37be3ccb..243d64070 100644 --- a/app/models/forms/bulk_upload_sales/checking_file.rb +++ b/app/models/forms/bulk_upload_sales/checking_file.rb @@ -6,13 +6,18 @@ module Forms include Rails.application.routes.url_helpers attribute :year, :integer + attribute :organisation_id, :integer def view_path "bulk_upload_sales_logs/forms/checking_file" end def back_path - bulk_upload_sales_log_path(id: "start") + if organisation_id.present? + sales_logs_organisation_path(organisation_id) + else + bulk_upload_sales_log_path(id: "start") + end end def year_combo diff --git a/app/models/forms/bulk_upload_sales/guidance.rb b/app/models/forms/bulk_upload_sales/guidance.rb index 28ca6c3b5..ef792a3e4 100644 --- a/app/models/forms/bulk_upload_sales/guidance.rb +++ b/app/models/forms/bulk_upload_sales/guidance.rb @@ -7,6 +7,7 @@ module Forms attribute :year, :integer attribute :referrer + attribute :organisation_id, :integer def view_path "bulk_upload_shared/guidance" @@ -15,7 +16,7 @@ module Forms def back_path case referrer when "prepare-your-file" - bulk_upload_sales_log_path(id: "prepare-your-file", form: { year: }) + bulk_upload_sales_log_path(id: "prepare-your-file", form: { year:, organisation_id: }.compact) when "home" root_path else diff --git a/app/models/forms/bulk_upload_sales/prepare_your_file.rb b/app/models/forms/bulk_upload_sales/prepare_your_file.rb index 387f6239f..4bf0797a8 100644 --- a/app/models/forms/bulk_upload_sales/prepare_your_file.rb +++ b/app/models/forms/bulk_upload_sales/prepare_your_file.rb @@ -6,6 +6,7 @@ module Forms include Rails.application.routes.url_helpers attribute :year, :integer + attribute :organisation_id, :integer def view_path case year @@ -18,14 +19,16 @@ module Forms def back_path if have_choice_of_year? - Rails.application.routes.url_helpers.bulk_upload_sales_log_path(id: "year", form: { year: }) + Rails.application.routes.url_helpers.bulk_upload_sales_log_path(id: "year", form: { year: }.compact) + elsif organisation_id.present? + sales_logs_organisation_path(organisation_id) else Rails.application.routes.url_helpers.sales_logs_path end end def next_path - bulk_upload_sales_log_path(id: "upload-your-file", form: { year: }) + bulk_upload_sales_log_path(id: "upload-your-file", form: { year:, organisation_id: }.compact) end def legacy_template_path diff --git a/app/models/forms/bulk_upload_sales/upload_your_file.rb b/app/models/forms/bulk_upload_sales/upload_your_file.rb index de650c831..5a0114caf 100644 --- a/app/models/forms/bulk_upload_sales/upload_your_file.rb +++ b/app/models/forms/bulk_upload_sales/upload_your_file.rb @@ -10,6 +10,7 @@ module Forms attribute :year, :integer attribute :file attribute :current_user + attribute :organisation_id, :integer validates :file, presence: true validate :validate_file_is_csv @@ -19,7 +20,7 @@ module Forms end def back_path - bulk_upload_sales_log_path(id: "prepare-your-file", form: { year: }) + bulk_upload_sales_log_path(id: "prepare-your-file", form: { year:, organisation_id: }.compact) end def year_combo @@ -27,7 +28,7 @@ module Forms end def next_path - bulk_upload_sales_log_path(id: "checking-file", form: { year: }) + bulk_upload_sales_log_path(id: "checking-file", form: { year:, organisation_id: }.compact) end def save! @@ -36,6 +37,7 @@ module Forms log_type: BulkUpload.log_types[:sales], year:, filename: file.original_filename, + organisation_id: (organisation_id if current_user.support?) || current_user.organisation_id, ) storage_service.write_file(bulk_upload.identifier, File.read(file.path)) diff --git a/app/models/forms/bulk_upload_sales/year.rb b/app/models/forms/bulk_upload_sales/year.rb index 85959c4e5..1de8dec04 100644 --- a/app/models/forms/bulk_upload_sales/year.rb +++ b/app/models/forms/bulk_upload_sales/year.rb @@ -6,6 +6,7 @@ module Forms include Rails.application.routes.url_helpers attribute :year, :integer + attribute :organisation_id, :integer validates :year, presence: true @@ -20,11 +21,15 @@ module Forms end def back_path - sales_logs_path + if organisation_id.present? + sales_logs_organisation_path(organisation_id) + else + sales_logs_path + end end def next_path - bulk_upload_sales_log_path(id: "prepare-your-file", form: { year: }) + bulk_upload_sales_log_path(id: "prepare-your-file", form: { year:, organisation_id: }.compact) end def save! diff --git a/app/services/bulk_upload/lettings/year2024/row_parser.rb b/app/services/bulk_upload/lettings/year2024/row_parser.rb index 8fc913055..dc0b2dca3 100644 --- a/app/services/bulk_upload/lettings/year2024/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2024/row_parser.rb @@ -432,6 +432,7 @@ class BulkUpload::Lettings::Year2024::RowParser validate :validate_assigned_to_exists, on: :after_log validate :validate_assigned_to_related, on: :after_log + validate :validate_assigned_to_when_support, on: :after_log validate :validate_all_charges_given, on: :after_log, if: proc { is_carehome.zero? } validate :validate_address_option_found, on: :after_log, unless: -> { supported_housing? } @@ -582,6 +583,12 @@ private end end + def validate_assigned_to_when_support + if field_3.blank? && bulk_upload.user.support? + errors.add(:field_3, category: :setup, message: I18n.t("validations.not_answered", question: "what is the CORE username of the account this letting log should be assigned to?")) + end + end + def validate_assigned_to_related return unless assigned_to return if assigned_to.organisation == owning_organisation || assigned_to.organisation == managing_organisation @@ -891,12 +898,17 @@ private end def validate_owning_org_permitted - if owning_organisation && !bulk_upload.user.organisation.affiliated_stock_owners.include?(owning_organisation) - block_log_creation! + return unless owning_organisation + return if bulk_upload_organisation.affiliated_stock_owners.include?(owning_organisation) - if errors[:field_1].blank? - errors.add(:field_1, "You do not have permission to add logs for this owning organisation", category: :setup) - end + block_log_creation! + + return if errors[:field_1].present? + + if bulk_upload.user.support? + errors.add(:field_1, "This owning organisation is not affiliated with #{bulk_upload_organisation.name}", category: :setup) + else + errors.add(:field_1, "You do not have permission to add logs for this owning organisation", category: :setup) end end @@ -1137,7 +1149,7 @@ private attributes["renewal"] = renewal attributes["scheme"] = scheme attributes["location"] = location - attributes["assigned_to"] = assigned_to || bulk_upload.user + attributes["assigned_to"] = assigned_to || (bulk_upload.user.support? ? nil : bulk_upload.user) attributes["created_by"] = bulk_upload.user attributes["needstype"] = field_4 attributes["rent_type"] = RENT_TYPE_BU_MAPPING[field_11] @@ -1625,4 +1637,8 @@ private def reason_is_other? field_98 == 20 end + + def bulk_upload_organisation + Organisation.find(bulk_upload.organisation_id) + 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 8be08d62f..0b7a70c27 100644 --- a/app/services/bulk_upload/sales/year2024/row_parser.rb +++ b/app/services/bulk_upload/sales/year2024/row_parser.rb @@ -462,6 +462,7 @@ class BulkUpload::Sales::Year2024::RowParser validate :validate_assigned_to_exists, on: :after_log validate :validate_assigned_to_related, on: :after_log + validate :validate_assigned_to_when_support, on: :after_log validate :validate_managing_org_related, on: :after_log validate :validate_relevant_collection_window, on: :after_log validate :validate_incomplete_soft_validations, on: :after_log @@ -925,7 +926,7 @@ private attributes["owning_organisation"] = owning_organisation attributes["managing_organisation"] = managing_organisation - attributes["assigned_to"] = assigned_to || bulk_upload.user + attributes["assigned_to"] = assigned_to || (bulk_upload.user.support? ? nil : bulk_upload.user) attributes["created_by"] = bulk_upload.user attributes["hhregres"] = field_72 attributes["hhregresstill"] = field_73 @@ -1302,12 +1303,17 @@ private end def validate_owning_org_permitted - if owning_organisation && !bulk_upload.user.organisation.affiliated_stock_owners.include?(owning_organisation) - block_log_creation! + return unless owning_organisation + return if bulk_upload_organisation.affiliated_stock_owners.include?(owning_organisation) - if errors[:field_1].blank? - errors.add(:field_1, "You do not have permission to add logs for this owning organisation", category: :setup) - end + block_log_creation! + + return if errors[:field_1].present? + + if bulk_upload.user.support? + errors.add(:field_1, "This owning organisation is not affiliated with #{bulk_upload_organisation.name}", category: :setup) + else + errors.add(:field_1, "You do not have permission to add logs for this owning organisation", category: :setup) end end @@ -1319,6 +1325,12 @@ private end end + def validate_assigned_to_when_support + if field_3.blank? && bulk_upload.user.support? + errors.add(:field_3, category: :setup, message: I18n.t("validations.not_answered", question: "what is the CORE username of the account this sales log should be assigned to?")) + end + end + def validate_assigned_to_related return unless assigned_to return if assigned_to.organisation == owning_organisation || assigned_to.organisation == managing_organisation @@ -1492,4 +1504,8 @@ private def valid_nationality_options %w[0] + GlobalConstants::COUNTRIES_ANSWER_OPTIONS.keys # 0 is "Prefers not to say" end + + def bulk_upload_organisation + Organisation.find(bulk_upload.organisation_id) + end end diff --git a/app/views/bulk_upload_lettings_logs/forms/checking_file.html.erb b/app/views/bulk_upload_lettings_logs/forms/checking_file.html.erb index 7524072a5..2e6d4cd10 100644 --- a/app/views/bulk_upload_lettings_logs/forms/checking_file.html.erb +++ b/app/views/bulk_upload_lettings_logs/forms/checking_file.html.erb @@ -6,6 +6,7 @@
<%= form_with model: @form, scope: :form, url: bulk_upload_lettings_log_path(id: "prepare-your-file"), method: :patch do |f| %> <%= f.hidden_field :year %> + <%= f.hidden_field :organisation_id %> Upload lettings logs in bulk (<%= @form.year_combo %>)

We’re checking the file

diff --git a/app/views/bulk_upload_lettings_logs/forms/needstype.erb b/app/views/bulk_upload_lettings_logs/forms/needstype.erb index 6deec7e1d..644dd9f5f 100644 --- a/app/views/bulk_upload_lettings_logs/forms/needstype.erb +++ b/app/views/bulk_upload_lettings_logs/forms/needstype.erb @@ -7,6 +7,7 @@ <%= 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, 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 45ce4b385..7657396f1 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 @@ -6,6 +6,7 @@
<%= form_with model: @form, scope: :form, url: bulk_upload_lettings_log_path(id: "prepare-your-file"), method: :patch do |f| %> <%= f.hidden_field :year %> + <%= f.hidden_field :organisation_id %> Upload lettings logs in bulk (<%= @form.year_combo %>)

Prepare your file

diff --git a/app/views/bulk_upload_lettings_logs/forms/upload_your_file.html.erb b/app/views/bulk_upload_lettings_logs/forms/upload_your_file.html.erb index bbafc28b3..2814eb1ea 100644 --- a/app/views/bulk_upload_lettings_logs/forms/upload_your_file.html.erb +++ b/app/views/bulk_upload_lettings_logs/forms/upload_your_file.html.erb @@ -7,6 +7,7 @@ <%= form_with model: @form, scope: :form, url: bulk_upload_lettings_log_path(id: "upload-your-file"), method: :patch do |f| %> <%= f.hidden_field :year %> <%= f.hidden_field :needstype %> + <%= f.hidden_field :organisation_id %> <%= f.govuk_error_summary %> diff --git a/app/views/bulk_upload_lettings_logs/forms/year.html.erb b/app/views/bulk_upload_lettings_logs/forms/year.html.erb index 8ba1c280f..4fef16dd7 100644 --- a/app/views/bulk_upload_lettings_logs/forms/year.html.erb +++ b/app/views/bulk_upload_lettings_logs/forms/year.html.erb @@ -4,6 +4,7 @@ <%= form_with model: @form, scope: :form, url: bulk_upload_lettings_log_path(id: "year"), method: :patch do |f| %> <%= f.govuk_error_summary %> + <%= f.hidden_field :organisation_id %> <%= f.govuk_collection_radio_buttons :year, @form.options, diff --git a/app/views/bulk_upload_lettings_results/show.html.erb b/app/views/bulk_upload_lettings_results/show.html.erb index 30a6fd585..15c486b91 100644 --- a/app/views/bulk_upload_lettings_results/show.html.erb +++ b/app/views/bulk_upload_lettings_results/show.html.erb @@ -25,4 +25,4 @@
-<%= govuk_button_link_to "Upload your file again", start_bulk_upload_lettings_logs_path %> +<%= govuk_button_link_to "Upload your file again", start_bulk_upload_lettings_logs_path(organisation_id: @bulk_upload.organisation_id) %> diff --git a/app/views/bulk_upload_lettings_results/summary.html.erb b/app/views/bulk_upload_lettings_results/summary.html.erb index b144793bf..8a59e8999 100644 --- a/app/views/bulk_upload_lettings_results/summary.html.erb +++ b/app/views/bulk_upload_lettings_results/summary.html.erb @@ -29,4 +29,4 @@ <% end %>
-<%= govuk_button_link_to "Upload your file again", start_bulk_upload_lettings_logs_path %> +<%= govuk_button_link_to "Upload your file again", start_bulk_upload_lettings_logs_path(organisation_id: @bulk_upload.organisation_id) %> diff --git a/app/views/bulk_upload_sales_logs/forms/checking_file.html.erb b/app/views/bulk_upload_sales_logs/forms/checking_file.html.erb index c47a97d5e..8bcef5cad 100644 --- a/app/views/bulk_upload_sales_logs/forms/checking_file.html.erb +++ b/app/views/bulk_upload_sales_logs/forms/checking_file.html.erb @@ -6,6 +6,7 @@
<%= form_with model: @form, scope: :form, url: bulk_upload_sales_log_path(id: "prepare-your-file"), method: :patch do |f| %> <%= f.hidden_field :year %> + <%= f.hidden_field :organisation_id %><%= f.hidden_field :organisation_id %> Upload sales logs in bulk (<%= @form.year_combo %>)

We’re checking the file

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 81c947e15..2a64de689 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 @@ -6,6 +6,7 @@
<%= form_with model: @form, scope: :form, url: bulk_upload_sales_log_path(id: "prepare-your-file"), method: :patch do |f| %> <%= f.hidden_field :year %> + <%= f.hidden_field :organisation_id %> Upload sales logs in bulk (<%= @form.year_combo %>)

Prepare your file

diff --git a/app/views/bulk_upload_sales_logs/forms/upload_your_file.html.erb b/app/views/bulk_upload_sales_logs/forms/upload_your_file.html.erb index 36e7240dc..75350ae9e 100644 --- a/app/views/bulk_upload_sales_logs/forms/upload_your_file.html.erb +++ b/app/views/bulk_upload_sales_logs/forms/upload_your_file.html.erb @@ -6,6 +6,7 @@
<%= form_with model: @form, scope: :form, url: bulk_upload_sales_log_path(id: "upload-your-file"), method: :patch do |f| %> <%= f.hidden_field :year %> + <%= f.hidden_field :organisation_id %> <%= f.govuk_error_summary %> diff --git a/app/views/bulk_upload_sales_logs/forms/year.html.erb b/app/views/bulk_upload_sales_logs/forms/year.html.erb index d8aa09172..2aaa7c0f8 100644 --- a/app/views/bulk_upload_sales_logs/forms/year.html.erb +++ b/app/views/bulk_upload_sales_logs/forms/year.html.erb @@ -4,6 +4,7 @@ <%= form_with model: @form, scope: :form, url: bulk_upload_sales_log_path(id: "year"), method: :patch do |f| %> <%= f.govuk_error_summary %> + <%= f.hidden_field :organisation_id %> <%= f.govuk_collection_radio_buttons :year, @form.options, diff --git a/app/views/bulk_upload_sales_results/show.html.erb b/app/views/bulk_upload_sales_results/show.html.erb index 0d645db33..776fdfa2f 100644 --- a/app/views/bulk_upload_sales_results/show.html.erb +++ b/app/views/bulk_upload_sales_results/show.html.erb @@ -25,4 +25,4 @@
-<%= govuk_button_link_to "Upload your file again", start_bulk_upload_sales_logs_path %> +<%= govuk_button_link_to "Upload your file again", start_bulk_upload_sales_logs_path(organisation_id: @bulk_upload.organisation_id) %> diff --git a/app/views/bulk_upload_sales_results/summary.html.erb b/app/views/bulk_upload_sales_results/summary.html.erb index 0fa51b9dc..0e423621d 100644 --- a/app/views/bulk_upload_sales_results/summary.html.erb +++ b/app/views/bulk_upload_sales_results/summary.html.erb @@ -29,4 +29,4 @@ <% end %>
-<%= govuk_button_link_to "Upload your file again", start_bulk_upload_sales_logs_path %> +<%= govuk_button_link_to "Upload your file again", start_bulk_upload_sales_logs_path(organisation_id: @bulk_upload.organisation_id) %> diff --git a/app/views/logs/_create_for_org_actions.html.erb b/app/views/logs/_create_for_org_actions.html.erb index f6ed9ad82..0564e678a 100644 --- a/app/views/logs/_create_for_org_actions.html.erb +++ b/app/views/logs/_create_for_org_actions.html.erb @@ -1,10 +1,12 @@
<% if @organisation.data_protection_confirmed? %> <% if current_page?(controller: 'organisations', action: 'lettings_logs') %> - <%= govuk_button_to "Create a new lettings log for this organisation", lettings_logs_path(lettings_log: { owning_organisation_id: @organisation.id }, method: :post) %> - <% end %> + <%= govuk_button_to "Create a new lettings log for this organisation", lettings_logs_path(lettings_log: { owning_organisation_id: @organisation.id }, method: :post), class: "govuk-!-margin-right-6" %> + <%= govuk_button_link_to "Upload lettings logs in bulk", bulk_upload_lettings_log_path(id: "start", organisation_id: @organisation.id), secondary: true %> +<% end %> <% if current_page?(controller: 'organisations', action: 'sales_logs') %> - <%= govuk_button_to "Create a new sales log for this organisation", sales_logs_path(sales_log: { owning_organisation_id: @organisation.id }, method: :post) %> - <% end %> + <%= govuk_button_to "Create a new sales log for this organisation", sales_logs_path(sales_log: { owning_organisation_id: @organisation.id }, method: :post), class: "govuk-!-margin-right-6" %> + <%= govuk_button_link_to "Upload sales logs in bulk", bulk_upload_sales_log_path(id: "start", organisation_id: @organisation.id), secondary: true %> +<% end %> <% end %>
diff --git a/db/migrate/20240905092332_add_organisation_id_to_bulk_uploads.rb b/db/migrate/20240905092332_add_organisation_id_to_bulk_uploads.rb new file mode 100644 index 000000000..d9f4f16da --- /dev/null +++ b/db/migrate/20240905092332_add_organisation_id_to_bulk_uploads.rb @@ -0,0 +1,5 @@ +class AddOrganisationIdToBulkUploads < ActiveRecord::Migration[7.0] + def change + add_column :bulk_uploads, :organisation_id, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index cd7708957..4d6f18b84 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -42,6 +42,7 @@ ActiveRecord::Schema[7.0].define(version: 2024_09_23_145326) do t.text "choice" t.integer "total_logs_count" t.string "rent_type_fix_status", default: "not_applied" + t.integer "organisation_id" t.integer "moved_user_id" t.index ["identifier"], name: "index_bulk_uploads_on_identifier", unique: true t.index ["user_id"], name: "index_bulk_uploads_on_user_id" diff --git a/spec/factories/bulk_upload.rb b/spec/factories/bulk_upload.rb index c848fb071..cefe95c2b 100644 --- a/spec/factories/bulk_upload.rb +++ b/spec/factories/bulk_upload.rb @@ -9,6 +9,7 @@ FactoryBot.define do sequence(:filename) { |n| "bulk-upload-#{n}.csv" } needstype { 1 } rent_type_fix_status { BulkUpload.rent_type_fix_statuses.values.sample } + organisation_id { user.organisation_id } trait(:sales) do log_type { BulkUpload.log_types[:sales] } diff --git a/spec/features/organisation_spec.rb b/spec/features/organisation_spec.rb index 1867285eb..3d65cda87 100644 --- a/spec/features/organisation_spec.rb +++ b/spec/features/organisation_spec.rb @@ -139,6 +139,10 @@ RSpec.describe "User Features" do expect(page).to have_button("Create a new lettings log for this organisation") end + it "shows a upload lettings logs in bulk link" do + expect(page).to have_link("Upload lettings logs in bulk") + end + context "when creating a log for that organisation" do it "pre-fills the value for owning organisation for that log" do click_button("Create a new lettings log for this organisation") @@ -230,6 +234,10 @@ RSpec.describe "User Features" do expect(page).to have_button("Create a new sales log for this organisation") end + it "shows a upload sales logs in bulk link" do + expect(page).to have_link("Upload sales logs in bulk") + end + context "when creating a log for that organisation" do it "pre-fills the value for owning organisation for that log" do click_button("Create a new sales log for this organisation") 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 3faa0a699..68de5d22b 100644 --- a/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb @@ -815,6 +815,23 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do end end + context "when blank and bulk upload user is support" do + let(:bulk_upload) { create(:bulk_upload, :sales, user: create(:user, :support), year: 2024) } + + let(:attributes) { setup_section_params.merge(bulk_upload:, field_3: nil) } + + it "is not permitted" do + parser.valid? + expect(parser.errors[:field_3]).to be_present + expect(parser.errors[:field_3]).to include("You must answer what is the CORE username of the account this letting log should be assigned to?") + end + + it "blocks log creation" do + parser.valid? + expect(parser).to be_block_log_creation + end + end + context "when user could not be found" do let(:attributes) { { bulk_upload:, field_3: "idonotexist@example.com" } } @@ -1439,6 +1456,43 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do expect(parser.errors[:field_10]).to include(/Enter a date when the owning and managing organisation was active/) end end + + context "when user is an unaffiliated non-support user and bulk upload organisation is affiliated with the owning organisation" do + let(:affiliated_org) { create(:organisation, :with_old_visible_id) } + let(:unaffiliated_user) { create(:user, organisation: create(:organisation)) } + let(:attributes) { { bulk_upload:, field_1: affiliated_org.old_visible_id } } + let(:organisation_id) { unaffiliated_user.organisation_id } + + before do + create(:organisation_relationship, parent_organisation: owning_org, child_organisation: affiliated_org) + bulk_upload.update!(organisation_id:, user: unaffiliated_user) + end + + it "blocks log creation and adds an error to field_1" do + parser = described_class.new(attributes) + parser.valid? + expect(parser).to be_block_log_creation + expect(parser.errors[:field_1]).to include("You do not have permission to add logs for this owning organisation") + end + end + + context "when user is an unaffiliated support user and bulk upload organisation is affiliated with the owning organisation" do + let(:affiliated_org) { create(:organisation, :with_old_visible_id) } + let(:unaffiliated_support_user) { create(:user, :support, organisation: create(:organisation)) } + let(:attributes) { { bulk_upload:, field_1: affiliated_org.old_visible_id } } + let(:organisation_id) { affiliated_org.id } + + before do + create(:organisation_relationship, parent_organisation: owning_org, child_organisation: affiliated_org) + bulk_upload.update!(organisation_id:, user: unaffiliated_support_user) + end + + it "does not block log creation and does not add an error to field_1" do + parser = described_class.new(attributes) + parser.valid? + expect(parser.errors[:field_1]).not_to include("You do not have permission to add logs for this owning organisation") + end + end end describe "#field_2" do # managing org 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 e428f7792..ca8f29e92 100644 --- a/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb +++ b/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb @@ -554,6 +554,43 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do expect(parser.errors[:field_6]).to include(/Enter a date when the owning organisation was active/) end end + + context "when user is an unaffiliated non-support user and bulk upload organisation is affiliated with the owning organisation" do + let(:affiliated_org) { create(:organisation, :with_old_visible_id) } + let(:unaffiliated_user) { create(:user, organisation: create(:organisation)) } + let(:attributes) { { bulk_upload:, field_1: affiliated_org.old_visible_id } } + let(:organisation_id) { unaffiliated_user.organisation_id } + + before do + create(:organisation_relationship, parent_organisation: owning_org, child_organisation: affiliated_org) + bulk_upload.update!(organisation_id:, user: unaffiliated_user) + end + + it "blocks log creation and adds an error to field_1" do + parser = described_class.new(attributes) + parser.valid? + expect(parser).to be_block_log_creation + expect(parser.errors[:field_1]).to include("You do not have permission to add logs for this owning organisation") + end + end + + context "when user is an unaffiliated support user and bulk upload organisation is affiliated with the owning organisation" do + let(:affiliated_org) { create(:organisation, :with_old_visible_id) } + let(:unaffiliated_support_user) { create(:user, :support, organisation: create(:organisation)) } + let(:attributes) { { bulk_upload:, field_1: affiliated_org.old_visible_id } } + let(:organisation_id) { affiliated_org.id } + + before do + create(:organisation_relationship, parent_organisation: owning_org, child_organisation: affiliated_org) + bulk_upload.update!(organisation_id:, user: unaffiliated_support_user) + end + + it "does not block log creation and does not add an error to field_1" do + parser = described_class.new(attributes) + parser.valid? + expect(parser.errors[:field_1]).not_to include("You do not have permission to add logs for this owning organisation") + end + end end describe "#field_3" do # username for assigned_to @@ -576,6 +613,23 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do end end + context "when blank and bulk upload user is support" do + let(:bulk_upload) { create(:bulk_upload, :sales, user: create(:user, :support), year: 2024) } + + let(:attributes) { setup_section_params.merge(bulk_upload:, field_3: nil) } + + it "is not permitted" do + parser.valid? + expect(parser.errors[:field_3]).to be_present + expect(parser.errors[:field_3]).to include("You must answer what is the CORE username of the account this sales log should be assigned to?") + end + + it "blocks log creation" do + parser.valid? + expect(parser).to be_block_log_creation + end + end + context "when user could not be found" do let(:attributes) { { bulk_upload:, field_3: "idonotexist@example.com" } } From 997e9bd62d2a45c808ec523a9e9ac764f5deda61 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Wed, 25 Sep 2024 09:36:03 +0100 Subject: [PATCH 05/14] CLDC-3615 Add pending email change banner (#2642) * Add pending email change banner * Update pending_email_change_banner_text and test descriptions * Fix closing tag --- app/helpers/user_helper.rb | 19 ++++++++++ app/views/users/show.html.erb | 9 +++++ spec/helpers/user_helper_spec.rb | 61 ++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) diff --git a/app/helpers/user_helper.rb b/app/helpers/user_helper.rb index 06fe2bc7d..bbcb0acae 100644 --- a/app/helpers/user_helper.rb +++ b/app/helpers/user_helper.rb @@ -56,4 +56,23 @@ module UserHelper user.errors.add(attribute, message) end end + + def display_pending_email_change_banner?(user) + user.unconfirmed_email.present? && user.email != user.unconfirmed_email + end + + def pending_email_change_title_text(current_user, user) + if current_user == user + "You have requested to change your email address to #{user.unconfirmed_email}." + else + "There has been a request to change this user’s email address to #{user.unconfirmed_email}." + end + end + + def pending_email_change_banner_text(current_user) + text = "A confirmation link has been sent to the new email address. The current email will continue to work until the change is confirmed." + text += " Deactivating this user will cancel the email change request." if current_user.support? || current_user.data_coordinator? + + text + end end diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 7f42107f8..a43b03f82 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -11,6 +11,15 @@ <% end %> <% end %> +<% if display_pending_email_change_banner?(@user) %> + <%= govuk_notification_banner(title_text: "Important") do %> +

+ <%= pending_email_change_title_text(current_user, @user) %> +

+ <%= pending_email_change_banner_text(current_user) %> + <% end %> +<% end %> +

diff --git a/spec/helpers/user_helper_spec.rb b/spec/helpers/user_helper_spec.rb index c88790aa0..eb0a672db 100644 --- a/spec/helpers/user_helper_spec.rb +++ b/spec/helpers/user_helper_spec.rb @@ -105,6 +105,32 @@ RSpec.describe UserHelper do end end + describe "display_pending_email_change_banner?" do + context "when the user doesn't have an unconfirmed email" do + let(:user) { FactoryBot.create(:user, :data_provider, unconfirmed_email: nil) } + + it "does not display pending email change banner" do + expect(display_pending_email_change_banner?(user)).to be false + end + end + + context "when the user has the same unconfirmed email as current email" do + let(:user) { FactoryBot.create(:user, :data_provider, unconfirmed_email: "updated_email@example.com", email: "updated_email@example.com") } + + it "does not display pending email change banner" do + expect(display_pending_email_change_banner?(user)).to be false + end + end + + context "when the user has a different unconfirmed email" do + let(:user) { FactoryBot.create(:user, :data_provider, unconfirmed_email: "updated_email@example.com", email: "old_email@example.com") } + + it "displays pending email change banner" do + expect(display_pending_email_change_banner?(user)).to be true + end + end + end + describe "organisation_change_confirmation_warning" do context "when user owns logs" do before do @@ -147,4 +173,39 @@ RSpec.describe UserHelper do end end end + + describe "pending_email_change_title_text" do + let(:user) { FactoryBot.create(:user, :data_provider, unconfirmed_email: "updated_email@example.com", email: "old_email@example.com") } + let(:current_user) { FactoryBot.create(:user, :support) } + + context "when viewing own profile" do + it "returns the correct text" do + expect(pending_email_change_title_text(user, user)).to eq("You have requested to change your email address to updated_email@example.com.") + end + end + + context "when viewing another user's profile" do + it "returns the correct text" do + expect(pending_email_change_title_text(current_user, user)).to eq("There has been a request to change this user’s email address to updated_email@example.com.") + end + end + end + + describe "pending_email_change_banner_text" do + context "with provider user" do + let(:user) { FactoryBot.create(:user, :data_provider) } + + it "returns the correct text" do + expect(pending_email_change_banner_text(user)).to eq("A confirmation link has been sent to the new email address. The current email will continue to work until the change is confirmed.") + end + end + + context "with support user" do + let(:user) { FactoryBot.create(:user, :support) } + + it "returns the correct text" do + expect(pending_email_change_banner_text(user)).to eq("A confirmation link has been sent to the new email address. The current email will continue to work until the change is confirmed. Deactivating this user will cancel the email change request.") + end + end + end end From 2786653641061a5ec87794fefb80339143483a8e Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Thu, 26 Sep 2024 16:51:37 +0100 Subject: [PATCH 06/14] CLDC-3639 Allow some users to update to accounts all roles on staging (#2656) * Allow some users to update to accounts all roles on staging * Add tests for non staging environement --- app/models/user.rb | 4 ++ config/credentials.yml.enc | 2 +- spec/models/user_spec.rb | 122 +++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index 0a26a254b..aa9b5a507 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -207,6 +207,10 @@ class User < ApplicationRecord end def assignable_roles + if Rails.env.staging? && Rails.application.credentials[:staging_role_update_email_allowlist].include?(email.split("@").last.downcase) + return ROLES + end + return {} unless data_coordinator? || support? return ROLES if support? diff --git a/config/credentials.yml.enc b/config/credentials.yml.enc index c9d564782..9cd4bba71 100644 --- a/config/credentials.yml.enc +++ b/config/credentials.yml.enc @@ -1 +1 @@ -EZNV2LiNWzf52erbQ41Dz3Bh+2f3Uih8liEyhXp5XzHCLzAbmN6/IJqr7b9cTZiCiroFo4n/dFoG3yYrospp3frKsDXxF1K2/MTCJWjpgnn7wc+HiPQWG0W3HRtQCNkyyrHes0YKcYyDWIP6kztYv1I/Me3p0pGEx6t3CpSTg1v46eRnOlDWiUz3rVxPauwq9IYZ75gmnThqvg/Z8wcYsWLx0arago0SXtRPASCNj4uO/lbqTcAfyIXOTSiOlcAIjoPFRSQY7UqY0o2p8jRR/1L16SmGDsk8ijm+UygNmMexa3Khy5WcKctpQICakHs4NRjHNflqgXpXKL9dVBmNc9d7h+gbhbGJQ53Y0d+a35UbhPRMiv4SRH98FwB+WEsLCDdGSHvdmM6ArfOLljTrqrsmSRf0JfUrvzyVYmMCxjv4xgJwUS/TD5lQD1yPwkp2ss00kQJqzNmB7qwFhA8a3e2iNzV8qtAV/Nj+tMlr99Hb7vZZs98/38G2p5RAsE/5Xl9taKhc/ACnVc/bwJND4JWaBB7duCa08xVB8nkjlt5cCwMurzAcy1ZT+e8JepR+g6s8fpScMEWVJXE0hd8=--rZ41rY9TMXmiBUJw--QiLRVNVXZzTW446s7cec1g== \ No newline at end of file +QGn9IiI91BaO4IGAtfy92FrNP46X9T2jJErRv+o/PRG9LrimEGeuOE+FwhArKZQ5cTipaDqo8u9Ajv45Kitv3c0GynOOvz0r3OjPRHO/p4hW8BFWQDv581cWWPsyZT2JO51zZ5LnwNFvWrjEB2q49YESgtfADPkJWmtx/By5Cg2/PVIRxvhGKOnheme5cih050wqg/43BdiF0PD9FDTZXJDLJg/QQ8nQYkvQe2jN4nM4mTVpkQkmzDKgGknmUWFfW3qWFzlsdMkdkPdeP9wLnJVbFTeyaaJT3wv6l19d2rKqo8iVvacdaQjRev+LVXqOsNAjVHwcPNQVq9s8pxG24HLk3aQ14Eyjf6tHAuZAV4jLnNqQtBQ0AIldWeOl6SKmlTom1P1tcLp9KpajEADplmWSwUktIGmaakFjk/ApYaUBiYTku2iLHMrT/xSc3jPj5W/ZggeJ0Ij6nuGYE1cmBxWGxda9PzOrDP8coEK9vPHiNeDDM1RoukVmf8gwDmshILi5EwIAsO2gJXM1wtPYMu41+H4/y3c0GIwgfv9QP11q+nqhG1MMcOrAUKGhypAS+M+uLwfGQudfQDKP9Zv3VCnOk3mkKlpIzMMD4UdJxQeE/8sfwIsEhWggEo3oa93ptbRdvJ7YYcVvmMmkVBxk0KWFprl4i/BkFHLWrKNl5LBOGA==--ziMOTnYBB5TDyXYU--3FJMs8e6R8lheqcqB8p8uQ== \ No newline at end of file diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 5cb6cb580..beb3d589e 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -243,6 +243,128 @@ RSpec.describe User, type: :model do expect(user.need_two_factor_authentication?(nil)).to be false end end + + context "when the user is in non staging environment" do + before do + allow(Rails.env).to receive(:staging?).and_return(false) + end + + context "and the user is in the staging role update email allowlist" do + before do + allow(Rails.application.credentials).to receive(:[]).with(:staging_role_update_email_allowlist).and_return(["example.com"]) + end + + context "when the user is a data provider" do + it "cannot assign roles" do + expect(user.assignable_roles).to eq({}) + end + end + + context "when the user is a data coordinator" do + let(:user) { create(:user, :data_coordinator) } + + it "can assign all roles except support" do + expect(user.assignable_roles).to eq({ + data_provider: 1, + data_coordinator: 2, + }) + end + end + + context "when the user is a Support user" do + let(:user) { create(:user, :support) } + + it "can assign all roles" do + expect(user.assignable_roles).to eq({ + data_provider: 1, + data_coordinator: 2, + support: 99, + }) + end + end + end + end + + context "when the user is in staging environment" do + before do + allow(Rails.env).to receive(:staging?).and_return(true) + end + + context "and the user is not in the staging role update email allowlist" do + context "when the user is a data provider" do + let(:user) { create(:user, :data_provider) } + + it "cannot assign roles" do + expect(user.assignable_roles).to eq({}) + end + end + + context "when the user is a data coordinator" do + let(:user) { create(:user, :data_coordinator) } + + it "can assign all roles except support" do + expect(user.assignable_roles).to eq({ + data_provider: 1, + data_coordinator: 2, + }) + end + end + + context "when the user is a Support user" do + let(:user) { create(:user, :support) } + + it "can assign all roles" do + expect(user.assignable_roles).to eq({ + data_provider: 1, + data_coordinator: 2, + support: 99, + }) + end + end + end + + context "and the user is in the staging role update email allowlist" do + before do + allow(Rails.application.credentials).to receive(:[]).with(:staging_role_update_email_allowlist).and_return(["example.com"]) + end + + context "when the user is a data provider" do + let(:user) { create(:user, :data_provider) } + + it "can assign all roles" do + expect(user.assignable_roles).to eq({ + data_provider: 1, + data_coordinator: 2, + support: 99, + }) + end + end + + context "when the user is a data coordinator" do + let(:user) { create(:user, :data_coordinator) } + + it "can assign all roles" do + expect(user.assignable_roles).to eq({ + data_provider: 1, + data_coordinator: 2, + support: 99, + }) + end + end + + context "when the user is a Support user" do + let(:user) { create(:user, :support) } + + it "can assign all roles" do + expect(user.assignable_roles).to eq({ + data_provider: 1, + data_coordinator: 2, + support: 99, + }) + end + end + end + end end describe "paper trail" do From d3668493543e44727e3ae53d44136c54250ebf0d Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Fri, 27 Sep 2024 11:06:17 +0100 Subject: [PATCH 07/14] Sanitise search input for order (#2662) * Sanitise search input for order * Update scope --- app/models/lettings_log.rb | 18 +++++++++++++----- app/models/sales_log.rb | 13 +++++++++---- spec/models/lettings_log_spec.rb | 7 +++++++ spec/models/sales_log_spec.rb | 7 +++++++ 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/app/models/lettings_log.rb b/app/models/lettings_log.rb index 7bf963212..d70f9cbff 100644 --- a/app/models/lettings_log.rb +++ b/app/models/lettings_log.rb @@ -59,17 +59,25 @@ class LettingsLog < Log query.all } scope :search_by, lambda { |param| - by_id = Arel.sql("CASE WHEN lettings_logs.id = #{param.to_i} THEN 0 ELSE 1 END") - by_tenant_code = Arel.sql("CASE WHEN tenancycode = '#{param}' THEN 0 WHEN tenancycode ILIKE '%#{param}%' THEN 1 ELSE 2 END") - by_propcode = Arel.sql("CASE WHEN propcode = '#{param}' THEN 0 WHEN propcode ILIKE '%#{param}%' THEN 1 ELSE 2 END") - by_postcode = Arel.sql("CASE WHEN REPLACE(postcode_full, ' ', '') = '#{param.delete(' ')}' THEN 0 when REPLACE(postcode_full, ' ', '') ILIKE '%#{param.delete(' ')}%' then 1 ELSE 2 END") + sanitized_param = ActiveRecord::Base.sanitize_sql(param) + param_without_spaces = sanitized_param.delete(" ") + + by_id = Arel.sql("CASE WHEN lettings_logs.id = ? THEN 0 ELSE 1 END") + by_tenant_code = Arel.sql("CASE WHEN tenancycode = ? THEN 0 WHEN tenancycode ILIKE ? THEN 1 ELSE 2 END") + by_propcode = Arel.sql("CASE WHEN propcode = ? THEN 0 WHEN propcode ILIKE ? THEN 1 ELSE 2 END") + by_postcode = Arel.sql("CASE WHEN REPLACE(postcode_full, ' ', '') = ? THEN 0 WHEN REPLACE(postcode_full, ' ', '') ILIKE ? THEN 1 ELSE 2 END") filter_by_location_postcode(param) .or(filter_by_tenant_code(param)) .or(filter_by_propcode(param)) .or(filter_by_postcode(param)) .or(filter_by_id(param.gsub(/log/i, ""))) - .order(by_id, by_tenant_code, by_propcode, by_postcode) + .order( + [by_id, sanitized_param.to_i], + [by_tenant_code, sanitized_param, sanitized_param], + [by_propcode, sanitized_param, sanitized_param], + [by_postcode, param_without_spaces, param_without_spaces], + ) } scope :after_date, ->(date) { where("lettings_logs.startdate >= ?", date) } scope :before_date, ->(date) { where("lettings_logs.startdate < ?", date) } diff --git a/app/models/sales_log.rb b/app/models/sales_log.rb index 8312b8bff..aca80ef94 100644 --- a/app/models/sales_log.rb +++ b/app/models/sales_log.rb @@ -46,14 +46,19 @@ class SalesLog < Log } scope :filter_by_purchaser_code, ->(purchid) { where("purchid ILIKE ?", "%#{purchid}%") } scope :search_by, lambda { |param| - by_id = Arel.sql("CASE WHEN id = #{param.to_i} THEN 0 ELSE 1 END") - by_purchaser_code = Arel.sql("CASE WHEN purchid = '#{param}' THEN 0 WHEN purchid ILIKE '%#{param}%' THEN 1 ELSE 2 END") - by_postcode = Arel.sql("CASE WHEN REPLACE(postcode_full, ' ', '') = '#{param.delete(' ')}' THEN 0 WHEN REPLACE(postcode_full, ' ', '') ILIKE '%#{param.delete(' ')}%' THEN 1 ELSE 2 END") + sanitized_param = ActiveRecord::Base.sanitize_sql(param) + param_without_spaces = sanitized_param.delete(" ") + + by_id = Arel.sql("CASE WHEN id = ? THEN 0 ELSE 1 END") + by_purchaser_code = Arel.sql("CASE WHEN purchid = ? THEN 0 WHEN purchid ILIKE ? THEN 1 ELSE 2 END") + by_postcode = Arel.sql("CASE WHEN REPLACE(postcode_full, ' ', '') = ? THEN 0 WHEN REPLACE(postcode_full, ' ', '') ILIKE ? THEN 1 ELSE 2 END") filter_by_purchaser_code(param) .or(filter_by_postcode(param)) .or(filter_by_id(param.gsub(/log/i, ""))) - .order(by_id, by_purchaser_code, by_postcode) + .order([by_id, sanitized_param.to_i], + [by_purchaser_code, sanitized_param, sanitized_param], + [by_postcode, param_without_spaces, param_without_spaces]) } scope :age1_answered, -> { where.not(age1: nil).or(where(age1_known: [1, 2])) } scope :duplicate_logs, lambda { |log| diff --git a/spec/models/lettings_log_spec.rb b/spec/models/lettings_log_spec.rb index 92452f197..e28f0f2c5 100644 --- a/spec/models/lettings_log_spec.rb +++ b/spec/models/lettings_log_spec.rb @@ -1345,6 +1345,13 @@ RSpec.describe LettingsLog do expect(result.third.id).to eq lettings_log_with_postcode.id end end + + it "sanitises input for order" do + lettings_log_to_search.update!(tenancycode: "' 1234") + result = described_class.search_by(lettings_log_to_search.tenancycode) + expect(result.count).to eq(1) + expect(result.first.id).to eq lettings_log_to_search.id + end end end diff --git a/spec/models/sales_log_spec.rb b/spec/models/sales_log_spec.rb index a568ca330..ae9b00d4c 100644 --- a/spec/models/sales_log_spec.rb +++ b/spec/models/sales_log_spec.rb @@ -214,6 +214,13 @@ RSpec.describe SalesLog, type: :model do expect(result).to include(have_attributes(id: sales_log_to_search.id)) end end + + it "sanitises input for order" do + sales_log_to_search.update!(purchid: "' 123456") + result = described_class.search_by(sales_log_to_search.purchid) + expect(result.count).to be >= 1 + expect(result).to include(have_attributes(id: sales_log_to_search.id)) + end end context "when filtering by year or nil" do From f99bd9756ceffdd18ee3cde4e17c54ce8af8686d Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Wed, 2 Oct 2024 08:40:42 +0100 Subject: [PATCH 08/14] Capture failed bulk uploads (#2667) * Capture failed bulk uploads * Update capture exception to capture message --- app/services/bulk_upload/lettings/validator.rb | 17 ++++++++++++++--- app/services/bulk_upload/sales/validator.rb | 17 ++++++++++++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/app/services/bulk_upload/lettings/validator.rb b/app/services/bulk_upload/lettings/validator.rb index 8c1d33fe0..4da5b2c40 100644 --- a/app/services/bulk_upload/lettings/validator.rb +++ b/app/services/bulk_upload/lettings/validator.rb @@ -42,14 +42,25 @@ class BulkUpload::Lettings::Validator def create_logs? return false if any_setup_errors? - return false if row_parsers.any?(&:block_log_creation?) - return false if any_logs_already_exist? && FeatureToggle.bulk_upload_duplicate_log_check_enabled? + + if row_parsers.any?(&:block_log_creation?) + Sentry.capture_message("Bulk upload log creation blocked: #{bulk_upload.id}.") + return false + 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 + end row_parsers.each do |row_parser| row_parser.log.blank_invalid_non_setup_fields! end - return false if any_logs_invalid? + 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 + end true end diff --git a/app/services/bulk_upload/sales/validator.rb b/app/services/bulk_upload/sales/validator.rb index 32e9f7533..777424349 100644 --- a/app/services/bulk_upload/sales/validator.rb +++ b/app/services/bulk_upload/sales/validator.rb @@ -41,14 +41,25 @@ class BulkUpload::Sales::Validator def create_logs? return false if any_setup_errors? - return false if row_parsers.any?(&:block_log_creation?) - return false if any_logs_already_exist? && FeatureToggle.bulk_upload_duplicate_log_check_enabled? + + if row_parsers.any?(&:block_log_creation?) + Sentry.capture_exception("Bulk upload log creation blocked: #{bulk_upload.id}.") + return false + end + + if any_logs_already_exist? && FeatureToggle.bulk_upload_duplicate_log_check_enabled? + Sentry.capture_exception("Bulk upload log creation blocked due to duplicate logs: #{bulk_upload.id}.") + return false + end row_parsers.each do |row_parser| row_parser.log.blank_invalid_non_setup_fields! end - return false if any_logs_invalid? + if any_logs_invalid? + Sentry.capture_exception("Bulk upload log creation blocked due to invalid logs after blanking non setup fields: #{bulk_upload.id}.") + return false + end true end From f269a88abcd786f360cf4c4feb37ec28dd3b9733 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Wed, 2 Oct 2024 08:54:27 +0100 Subject: [PATCH 09/14] Update xml exports (#2543) * Refactor manifest creation into a separate job * Add users export service * Call user export service * Rename exports table * Update data export job spec * Update naming * Refactor shared logic into parent class * Update initialize * Allow exporting users individually * Update data export task tests * Move method and update task argument * Add phone extension to the user export * Add static period to filename * Make recent logs export depend on the collection * CLDC-3534 Export organisation data (#2599) * Add organisation export service * Call organisations export and write manifest * Add some additional fields to export * Add period to organisation export filename * Update provider_type and add new fields * Filter exports by the collection * Update tests * Update fields exported in lettings export (#2652) * Add new fields for user ids (#2661) * Undo lettings export field changes --- app/jobs/data_export_xml_job.rb | 4 +- app/models/export.rb | 2 + app/models/logs_export.rb | 2 - app/services/exports/export_service.rb | 78 ++++ .../exports/lettings_log_export_service.rb | 138 +------- .../exports/organisation_export_constants.rb | 27 ++ .../exports/organisation_export_service.rb | 72 ++++ app/services/exports/user_export_constants.rb | 18 + app/services/exports/user_export_service.rb | 68 ++++ app/services/exports/xml_export_service.rb | 97 +++++ .../20240802093255_rename_export_table.rb | 5 + db/schema.rb | 18 +- lib/tasks/data_export.rake | 10 +- spec/fixtures/exports/organisation.xml | 26 ++ spec/fixtures/exports/user.xml | 17 + spec/jobs/data_export_xml_job_spec.rb | 20 +- spec/lib/tasks/data_export_spec.rb | 10 +- spec/services/exports/export_service_spec.rb | 333 ++++++++++++++++++ .../lettings_log_export_service_spec.rb | 119 ++----- .../organisation_export_service_spec.rb | 219 ++++++++++++ .../exports/user_export_service_spec.rb | 219 ++++++++++++ 21 files changed, 1266 insertions(+), 236 deletions(-) create mode 100644 app/models/export.rb delete mode 100644 app/models/logs_export.rb create mode 100644 app/services/exports/export_service.rb create mode 100644 app/services/exports/organisation_export_constants.rb create mode 100644 app/services/exports/organisation_export_service.rb create mode 100644 app/services/exports/user_export_constants.rb create mode 100644 app/services/exports/user_export_service.rb create mode 100644 app/services/exports/xml_export_service.rb create mode 100644 db/migrate/20240802093255_rename_export_table.rb create mode 100644 spec/fixtures/exports/organisation.xml create mode 100644 spec/fixtures/exports/user.xml create mode 100644 spec/services/exports/export_service_spec.rb create mode 100644 spec/services/exports/organisation_export_service_spec.rb create mode 100644 spec/services/exports/user_export_service_spec.rb diff --git a/app/jobs/data_export_xml_job.rb b/app/jobs/data_export_xml_job.rb index 8b825f6df..76ce32ec1 100644 --- a/app/jobs/data_export_xml_job.rb +++ b/app/jobs/data_export_xml_job.rb @@ -3,8 +3,8 @@ class DataExportXmlJob < ApplicationJob def perform(full_update: false) storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["EXPORT_BUCKET"]) - export_service = Exports::LettingsLogExportService.new(storage_service) + export_service = Exports::ExportService.new(storage_service) - export_service.export_xml_lettings_logs(full_update:) + export_service.export_xml(full_update:) end end diff --git a/app/models/export.rb b/app/models/export.rb new file mode 100644 index 000000000..2ee04fe3d --- /dev/null +++ b/app/models/export.rb @@ -0,0 +1,2 @@ +class Export < ApplicationRecord +end diff --git a/app/models/logs_export.rb b/app/models/logs_export.rb deleted file mode 100644 index 0ae2e4179..000000000 --- a/app/models/logs_export.rb +++ /dev/null @@ -1,2 +0,0 @@ -class LogsExport < ApplicationRecord -end diff --git a/app/services/exports/export_service.rb b/app/services/exports/export_service.rb new file mode 100644 index 000000000..bf98a4edf --- /dev/null +++ b/app/services/exports/export_service.rb @@ -0,0 +1,78 @@ +module Exports + class ExportService + include CollectionTimeHelper + + def initialize(storage_service, logger = Rails.logger) + @storage_service = storage_service + @logger = logger + end + + def export_xml(full_update: false, collection: nil) + start_time = Time.zone.now + daily_run_number = get_daily_run_number + lettings_archives_for_manifest = {} + users_archives_for_manifest = {} + organisations_archives_for_manifest = {} + + if collection.present? + case collection + when "users" + users_archives_for_manifest = get_user_archives(start_time, full_update) + 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) + 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) + end + + write_master_manifest(daily_run_number, lettings_archives_for_manifest.merge(users_archives_for_manifest).merge(organisations_archives_for_manifest)) + end + + private + + def get_daily_run_number + today = Time.zone.today + Export.where(created_at: today.beginning_of_day..today.end_of_day).select(:started_at).distinct.count + 1 + end + + def write_master_manifest(daily_run, archive_datetimes) + today = Time.zone.today + increment_number = daily_run.to_s.rjust(4, "0") + month = today.month.to_s.rjust(2, "0") + day = today.day.to_s.rjust(2, "0") + file_path = "Manifest_#{today.year}_#{month}_#{day}_#{increment_number}.csv" + string_io = build_manifest_csv_io(archive_datetimes) + @storage_service.write_file(file_path, string_io) + end + + def build_manifest_csv_io(archive_datetimes) + headers = ["zip-name", "date-time zipped folder generated", "zip-file-uri"] + csv_string = CSV.generate do |csv| + csv << headers + archive_datetimes.each do |(archive, datetime)| + csv << [archive, datetime, "#{archive}.zip"] + end + end + StringIO.new(csv_string) + end + + def get_user_archives(start_time, full_update) + users_export_service = Exports::UserExportService.new(@storage_service, start_time) + users_export_service.export_xml_users(full_update:) + end + + def get_organisation_archives(start_time, full_update) + organisations_export_service = Exports::OrganisationExportService.new(@storage_service, start_time) + organisations_export_service.export_xml_organisations(full_update:) + end + + def get_lettings_archives(start_time, full_update, collection) + lettings_export_service = Exports::LettingsLogExportService.new(@storage_service, start_time) + lettings_export_service.export_xml_lettings_logs(full_update:, collection_year: collection) + end + end +end diff --git a/app/services/exports/lettings_log_export_service.rb b/app/services/exports/lettings_log_export_service.rb index 0c3b6eec4..b21099a06 100644 --- a/app/services/exports/lettings_log_export_service.rb +++ b/app/services/exports/lettings_log_export_service.rb @@ -1,22 +1,15 @@ module Exports - class LettingsLogExportService + class LettingsLogExportService < Exports::XmlExportService include Exports::LettingsLogExportConstants include CollectionTimeHelper - def initialize(storage_service, logger = Rails.logger) - @storage_service = storage_service - @logger = logger - end - def export_xml_lettings_logs(full_update: false, collection_year: nil) - start_time = Time.zone.now - daily_run_number = get_daily_run_number archives_for_manifest = {} - recent_export = LogsExport.order("started_at").last collection_years_to_export(collection_year).each do |collection| - base_number = LogsExport.where(empty_export: false, collection:).maximum(:base_number) || 1 - export = build_export_run(collection, start_time, base_number, full_update) - archives = write_export_archive(export, collection, start_time, recent_export, full_update) + 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) archives_for_manifest.merge!(archives) @@ -24,46 +17,11 @@ module Exports export.save! end - write_master_manifest(daily_run_number, archives_for_manifest) + archives_for_manifest end private - def get_daily_run_number - today = Time.zone.today - LogsExport.where(created_at: today.beginning_of_day..today.end_of_day).select(:started_at).distinct.count + 1 - end - - def build_export_run(collection, current_time, base_number, full_update) - @logger.info("Building export run for #{collection}") - previous_exports_with_data = LogsExport.where(collection:, empty_export: false) - - increment_number = previous_exports_with_data.where(base_number:).maximum(:increment_number) || 1 - - if full_update - base_number += 1 if LogsExport.any? # Only increment when it's not the first run - increment_number = 1 - else - increment_number += 1 - end - - if previous_exports_with_data.empty? - return LogsExport.new(collection:, base_number:, started_at: current_time) - end - - LogsExport.new(collection:, started_at: current_time, base_number:, increment_number:) - end - - def write_master_manifest(daily_run, archive_datetimes) - today = Time.zone.today - increment_number = daily_run.to_s.rjust(4, "0") - month = today.month.to_s.rjust(2, "0") - day = today.day.to_s.rjust(2, "0") - file_path = "Manifest_#{today.year}_#{month}_#{day}_#{increment_number}.csv" - string_io = build_manifest_csv_io(archive_datetimes) - @storage_service.write_file(file_path, string_io) - end - def get_archive_name(collection, base_number, increment) return unless collection @@ -72,88 +30,14 @@ module Exports "core_#{collection}_#{collection + 1}_apr_mar_#{base_number_str}_#{increment_str}".downcase end - def write_export_archive(export, collection, start_time, 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 (?) - - initial_logs_count = retrieve_lettings_logs(start_time, recent_export, full_update).filter_by_year(collection).count - @logger.info("Creating #{archive} - #{initial_logs_count} logs") - return {} if initial_logs_count.zero? - - zip_file = Zip::File.open_buffer(StringIO.new) - - part_number = 1 - last_processed_marker = nil - logs_count_after_export = 0 - - loop do - lettings_logs_slice = if last_processed_marker.present? - retrieve_lettings_logs(start_time, recent_export, full_update).filter_by_year(collection) - .where("created_at > ?", last_processed_marker) - .order(:created_at) - .limit(MAX_XML_RECORDS).to_a - else - retrieve_lettings_logs(start_time, recent_export, full_update).filter_by_year(collection) - .order(:created_at) - .limit(MAX_XML_RECORDS).to_a - end - - break if lettings_logs_slice.empty? - - data_xml = build_export_xml(lettings_logs_slice) - part_number_str = "pt#{part_number.to_s.rjust(3, '0')}" - zip_file.add("#{archive}_#{part_number_str}.xml", data_xml) - part_number += 1 - last_processed_marker = lettings_logs_slice.last.created_at - logs_count_after_export += lettings_logs_slice.count - @logger.info("Added #{archive}_#{part_number_str}.xml") - end - - manifest_xml = build_manifest_xml(logs_count_after_export) - zip_file.add("manifest.xml", manifest_xml) - - # Required by S3 to avoid Aws::S3::Errors::BadDigest - zip_io = zip_file.write_buffer - zip_io.rewind - @logger.info("Writing #{archive}.zip") - @storage_service.write_file("#{archive}.zip", zip_io) - { archive => Time.zone.now } - end - - def retrieve_lettings_logs(start_time, recent_export, full_update) + def retrieve_resources(recent_export, full_update, collection) 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) + 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) else - params = { to: start_time } - LettingsLog.exportable.where("updated_at <= :to", params) - end - end - - def build_manifest_csv_io(archive_datetimes) - headers = ["zip-name", "date-time zipped folder generated", "zip-file-uri"] - csv_string = CSV.generate do |csv| - csv << headers - archive_datetimes.each do |(archive, datetime)| - csv << [archive, datetime, "#{archive}.zip"] - end + params = { to: @start_time } + LettingsLog.exportable.where("updated_at <= :to", params).filter_by_year(collection) end - StringIO.new(csv_string) - end - - def xml_doc_to_temp_file(xml_doc) - file = Tempfile.new - xml_doc.write_xml_to(file, encoding: "UTF-8") - file.rewind - file - end - - def build_manifest_xml(record_number) - doc = Nokogiri::XML("") - doc.at("report") << doc.create_element("form-data-summary") - doc.at("form-data-summary") << doc.create_element("records") - doc.at("records") << doc.create_element("count-of-records", record_number) - - xml_doc_to_temp_file(doc) end def apply_cds_transformation(lettings_log, export_mode) diff --git a/app/services/exports/organisation_export_constants.rb b/app/services/exports/organisation_export_constants.rb new file mode 100644 index 000000000..3a1c5fb48 --- /dev/null +++ b/app/services/exports/organisation_export_constants.rb @@ -0,0 +1,27 @@ +module Exports::OrganisationExportConstants + MAX_XML_RECORDS = 10_000 + + EXPORT_FIELDS = Set[ + "id", + "name", + "phone", + "provider_type", + "address_line1", + "address_line2", + "postcode", + "holds_own_stock", + "housing_registration_no", + "active", + "old_org_id", + "old_visible_id", + "merge_date", + "absorbing_organisation_id", + "available_from", + "deleted_at", + "dsa_signed", + "dsa_signed_at", + "dpo_email", + "profit_status", + "group" + ] +end diff --git a/app/services/exports/organisation_export_service.rb b/app/services/exports/organisation_export_service.rb new file mode 100644 index 000000000..71eccf60a --- /dev/null +++ b/app/services/exports/organisation_export_service.rb @@ -0,0 +1,72 @@ +module Exports + class OrganisationExportService < Exports::XmlExportService + include Exports::OrganisationExportConstants + include CollectionTimeHelper + + def export_xml_organisations(full_update: false) + collection = "organisations" + 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_for_manifest = write_export_archive(export, collection, recent_export, full_update) + + export.empty_export = archives_for_manifest.empty? + export.save! + + archives_for_manifest + end + + private + + def get_archive_name(collection, base_number, increment) + return unless collection + + 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 + end + + def retrieve_resources(recent_export, full_update, _collection) + if !full_update && recent_export + params = { from: recent_export.started_at, to: @start_time } + Organisation.where("(updated_at >= :from AND updated_at <= :to)", params) + else + params = { to: @start_time } + Organisation.where("updated_at <= :to", params) + end + end + + def build_export_xml(organisations) + doc = Nokogiri::XML("") + + organisations.each do |organisation| + attribute_hash = apply_cds_transformation(organisation) + form = doc.create_element("form") + doc.at("forms") << form + attribute_hash.each do |key, value| + if !EXPORT_FIELDS.include?(key) + next + else + form << doc.create_element(key, value) + end + end + end + + xml_doc_to_temp_file(doc) + end + + def apply_cds_transformation(organisation) + attribute_hash = organisation.attributes + attribute_hash["deleted_at"] = organisation.discarded_at + attribute_hash["dsa_signed"] = organisation.data_protection_confirmed? + attribute_hash["dsa_signed_at"] = organisation.data_protection_confirmation&.signed_at + attribute_hash["dpo_email"] = organisation.data_protection_confirmation&.data_protection_officer_email + attribute_hash["provider_type"] = organisation.provider_type_before_type_cast + attribute_hash["profit_status"] = nil # will need update when we add the field to the org + attribute_hash["group"] = nil # will need update when we add the field to the org + + attribute_hash + end + end +end diff --git a/app/services/exports/user_export_constants.rb b/app/services/exports/user_export_constants.rb new file mode 100644 index 000000000..9ce5840d9 --- /dev/null +++ b/app/services/exports/user_export_constants.rb @@ -0,0 +1,18 @@ +module Exports::UserExportConstants + MAX_XML_RECORDS = 10_000 + + EXPORT_FIELDS = Set[ + "id", + "email", + "name", + "phone", + "organisation_id", + "organisation_name", + "role", + "is_dpo", + "is_key_contact", + "active", + "sign_in_count", + "last_sign_in_at", + ] +end diff --git a/app/services/exports/user_export_service.rb b/app/services/exports/user_export_service.rb new file mode 100644 index 000000000..907a1cc86 --- /dev/null +++ b/app/services/exports/user_export_service.rb @@ -0,0 +1,68 @@ +module Exports + class UserExportService < Exports::XmlExportService + include Exports::UserExportConstants + include CollectionTimeHelper + + def export_xml_users(full_update: false) + collection = "users" + 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_for_manifest = write_export_archive(export, collection, recent_export, full_update) + + export.empty_export = archives_for_manifest.empty? + export.save! + + archives_for_manifest + end + + private + + def get_archive_name(collection, base_number, increment) + return unless collection + + 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 + end + + def retrieve_resources(recent_export, full_update, _collection) + if !full_update && recent_export + params = { from: recent_export.started_at, to: @start_time } + User.where("(updated_at >= :from AND updated_at <= :to)", params) + else + params = { to: @start_time } + User.where("updated_at <= :to", params) + end + end + + def build_export_xml(users) + doc = Nokogiri::XML("") + + users.each do |user| + attribute_hash = apply_cds_transformation(user) + form = doc.create_element("form") + doc.at("forms") << form + attribute_hash.each do |key, value| + if !EXPORT_FIELDS.include?(key) + next + else + form << doc.create_element(key, value) + end + end + end + + xml_doc_to_temp_file(doc) + end + + def apply_cds_transformation(user) + attribute_hash = user.attributes_before_type_cast + attribute_hash["role"] = user.role + attribute_hash["organisation_name"] = user.organisation.name + attribute_hash["active"] = user.active? + attribute_hash["phone"] = [user.phone, user.phone_extension].compact.join(" ") + attribute_hash + end + end +end diff --git a/app/services/exports/xml_export_service.rb b/app/services/exports/xml_export_service.rb new file mode 100644 index 000000000..009a1b306 --- /dev/null +++ b/app/services/exports/xml_export_service.rb @@ -0,0 +1,97 @@ +module Exports + class XmlExportService + include Exports::LettingsLogExportConstants + include CollectionTimeHelper + + def initialize(storage_service, start_time, logger = Rails.logger) + @storage_service = storage_service + @logger = logger + @start_time = start_time + end + + 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) + + increment_number = previous_exports_with_data.where(base_number:).maximum(:increment_number) || 1 + + if full_update + base_number += 1 if Export.any? # Only increment when it's not the first run + increment_number = 1 + else + increment_number += 1 + end + + if previous_exports_with_data.empty? + return Export.new(collection:, base_number:, started_at: @start_time) + end + + Export.new(collection:, 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 (?) + + initial_count = retrieve_resources(recent_export, full_update, collection).count + @logger.info("Creating #{archive} - #{initial_count} resources") + return {} if initial_count.zero? + + zip_file = Zip::File.open_buffer(StringIO.new) + + part_number = 1 + last_processed_marker = nil + count_after_export = 0 + + loop do + slice = if last_processed_marker.present? + retrieve_resources(recent_export, full_update, collection) + .where("created_at > ?", last_processed_marker) + .order(:created_at) + .limit(MAX_XML_RECORDS).to_a + else + retrieve_resources(recent_export, full_update, collection) + .order(:created_at) + .limit(MAX_XML_RECORDS).to_a + end + + break if slice.empty? + + data_xml = build_export_xml(slice) + part_number_str = "pt#{part_number.to_s.rjust(3, '0')}" + zip_file.add("#{archive}_#{part_number_str}.xml", data_xml) + part_number += 1 + last_processed_marker = slice.last.created_at + count_after_export += slice.count + @logger.info("Added #{archive}_#{part_number_str}.xml") + end + + manifest_xml = build_manifest_xml(count_after_export) + zip_file.add("manifest.xml", manifest_xml) + + # Required by S3 to avoid Aws::S3::Errors::BadDigest + zip_io = zip_file.write_buffer + zip_io.rewind + @logger.info("Writing #{archive}.zip") + @storage_service.write_file("#{archive}.zip", zip_io) + { archive => Time.zone.now } + end + + def xml_doc_to_temp_file(xml_doc) + file = Tempfile.new + xml_doc.write_xml_to(file, encoding: "UTF-8") + file.rewind + file + end + + def build_manifest_xml(record_number) + doc = Nokogiri::XML("") + doc.at("report") << doc.create_element("form-data-summary") + doc.at("form-data-summary") << doc.create_element("records") + doc.at("records") << doc.create_element("count-of-records", record_number) + + xml_doc_to_temp_file(doc) + end + end +end diff --git a/db/migrate/20240802093255_rename_export_table.rb b/db/migrate/20240802093255_rename_export_table.rb new file mode 100644 index 000000000..68c14d0a9 --- /dev/null +++ b/db/migrate/20240802093255_rename_export_table.rb @@ -0,0 +1,5 @@ +class RenameExportTable < ActiveRecord::Migration[7.0] + def change + rename_table :logs_exports, :exports + end +end diff --git a/db/schema.rb b/db/schema.rb index 4d6f18b84..ff1f913df 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -78,6 +78,15 @@ ActiveRecord::Schema[7.0].define(version: 2024_09_23_145326) do t.index ["organisation_id"], name: "index_data_protection_confirmations_on_organisation_id" end + create_table "exports", force: :cascade do |t| + t.datetime "created_at", default: -> { "CURRENT_TIMESTAMP" } + t.datetime "started_at", null: false + t.integer "base_number", default: 1, null: false + t.integer "increment_number", default: 1, null: false + t.boolean "empty_export", default: false, null: false + t.string "collection" + end + create_table "la_rent_ranges", force: :cascade do |t| t.integer "ranges_rent_id" t.integer "lettype" @@ -414,15 +423,6 @@ ActiveRecord::Schema[7.0].define(version: 2024_09_23_145326) do t.boolean "checked" end - create_table "logs_exports", force: :cascade do |t| - t.datetime "created_at", default: -> { "CURRENT_TIMESTAMP" } - t.datetime "started_at", null: false - t.integer "base_number", default: 1, null: false - t.integer "increment_number", default: 1, null: false - t.boolean "empty_export", default: false, null: false - t.string "collection" - end - create_table "merge_request_organisations", force: :cascade do |t| t.integer "merge_request_id" t.integer "merging_organisation_id" diff --git a/lib/tasks/data_export.rake b/lib/tasks/data_export.rake index 719462cb4..7a9a90bc8 100644 --- a/lib/tasks/data_export.rake +++ b/lib/tasks/data_export.rake @@ -7,11 +7,13 @@ namespace :core do end desc "Export all data XMLs for import into Central Data System (CDS)" - task :full_data_export_xml, %i[year] => :environment do |_task, args| - collection_year = args[:year].present? ? args[:year].to_i : nil + task :full_data_export_xml, %i[collection] => :environment do |_task, args| + collection = args[:collection].presence + collection = collection.to_i if collection.present? && collection.scan(/\D/).empty? + storage_service = Storage::S3Service.new(Configuration::EnvConfigurationService.new, ENV["EXPORT_BUCKET"]) - export_service = Exports::LettingsLogExportService.new(storage_service) + export_service = Exports::ExportService.new(storage_service) - export_service.export_xml_lettings_logs(full_update: true, collection_year:) + export_service.export_xml(full_update: true, collection:) end end diff --git a/spec/fixtures/exports/organisation.xml b/spec/fixtures/exports/organisation.xml new file mode 100644 index 000000000..8d87da16c --- /dev/null +++ b/spec/fixtures/exports/organisation.xml @@ -0,0 +1,26 @@ + + +
+ {id} + MHCLG + + 1 + 2 Marsham Street + London + SW1P 4DF + true + true + 1234 + + + + + + + true + {dsa_signed_at} + {dpo_email} + + + +
diff --git a/spec/fixtures/exports/user.xml b/spec/fixtures/exports/user.xml new file mode 100644 index 000000000..5652ac9c6 --- /dev/null +++ b/spec/fixtures/exports/user.xml @@ -0,0 +1,17 @@ + + +
+ {id} + {email} + Danny Rojas + {organisation_id} + 5 + + data_provider + 1234512345123 123 + false + false + true + MHCLG + +
diff --git a/spec/jobs/data_export_xml_job_spec.rb b/spec/jobs/data_export_xml_job_spec.rb index c029dad71..3712e115c 100644 --- a/spec/jobs/data_export_xml_job_spec.rb +++ b/spec/jobs/data_export_xml_job_spec.rb @@ -1,25 +1,33 @@ require "rails_helper" describe DataExportXmlJob do - let(:storage_service) { instance_double(Storage::S3Service) } + let(:storage_service) { instance_double(Storage::S3Service, write_file: nil) } let(:env_config_service) { instance_double(Configuration::EnvConfigurationService) } - let(:export_service) { instance_double(Exports::LettingsLogExportService) } + let(:lettings_export_service) { instance_double(Exports::LettingsLogExportService, export_xml_lettings_logs: {}) } + let(:user_export_service) { instance_double(Exports::UserExportService, export_xml_users: {}) } + let(:organisation_export_service) { instance_double(Exports::OrganisationExportService, export_xml_organisations: {}) } before do allow(Storage::S3Service).to receive(:new).and_return(storage_service) allow(Configuration::EnvConfigurationService).to receive(:new).and_return(env_config_service) - allow(Exports::LettingsLogExportService).to receive(:new).and_return(export_service) + allow(Exports::LettingsLogExportService).to receive(:new).and_return(lettings_export_service) + allow(Exports::UserExportService).to receive(:new).and_return(user_export_service) + allow(Exports::OrganisationExportService).to receive(:new).and_return(organisation_export_service) end - it "calls the export service" do - expect(export_service).to receive(:export_xml_lettings_logs) + it "calls the export services" do + expect(lettings_export_service).to receive(:export_xml_lettings_logs) + expect(user_export_service).to receive(:export_xml_users) + expect(organisation_export_service).to receive(:export_xml_organisations) described_class.perform_now end context "with full update enabled" do it "calls the export service" do - expect(export_service).to receive(:export_xml_lettings_logs).with(full_update: true) + expect(lettings_export_service).to receive(:export_xml_lettings_logs).with(full_update: true, collection_year: nil) + expect(user_export_service).to receive(:export_xml_users).with(full_update: true) + expect(organisation_export_service).to receive(:export_xml_organisations).with(full_update: true) described_class.perform_now(full_update: true) end diff --git a/spec/lib/tasks/data_export_spec.rb b/spec/lib/tasks/data_export_spec.rb index afb99f872..8b5dd5fbe 100644 --- a/spec/lib/tasks/data_export_spec.rb +++ b/spec/lib/tasks/data_export_spec.rb @@ -4,7 +4,7 @@ require "rake" describe "rake core:data_export", type: task do let(:export_bucket) { "export_bucket" } let(:storage_service) { instance_double(Storage::S3Service) } - let(:export_service) { instance_double(Exports::LettingsLogExportService) } + let(:export_service) { instance_double(Exports::ExportService) } before do Rake.application.rake_require("tasks/data_export") @@ -12,7 +12,7 @@ describe "rake core:data_export", type: task do task.reenable allow(Storage::S3Service).to receive(:new).and_return(storage_service) - allow(Exports::LettingsLogExportService).to receive(:new).and_return(export_service) + allow(Exports::ExportService).to receive(:new).and_return(export_service) allow(ENV).to receive(:[]) allow(ENV).to receive(:[]).with("EXPORT_BUCKET").and_return(export_bucket) end @@ -30,15 +30,15 @@ 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_lettings_logs).with(full_update: true, collection_year: nil) + expect(export_service).to receive(:export_xml).with(full_update: true, collection: nil) task.invoke end end - context "with a specific year" do + context "with a specific collection" do it "calls the export service" do - expect(export_service).to receive(:export_xml_lettings_logs).with(full_update: true, collection_year: 2022) + expect(export_service).to receive(:export_xml).with(full_update: true, collection: 2022) task.invoke("2022") end diff --git a/spec/services/exports/export_service_spec.rb b/spec/services/exports/export_service_spec.rb new file mode 100644 index 000000000..fb52c5274 --- /dev/null +++ b/spec/services/exports/export_service_spec.rb @@ -0,0 +1,333 @@ +require "rails_helper" + +RSpec.describe Exports::ExportService do + subject(:export_service) { described_class.new(storage_service) } + + let(:storage_service) { instance_double(Storage::S3Service) } + let(:expected_master_manifest_filename) { "Manifest_2022_05_01_0001.csv" } + let(:start_time) { Time.zone.local(2022, 5, 1) } + let(:user) { FactoryBot.create(:user, email: "test1@example.com") } + let(:organisations_export_service) { instance_double("Exports::OrganisationExportService", export_xml_organisations: {}) } + let(:users_export_service) { instance_double("Exports::UserExportService", export_xml_users: {}) } + + before do + Timecop.freeze(start_time) + Singleton.__init__(FormHandler) + allow(storage_service).to receive(:write_file) + allow(Exports::LettingsLogExportService).to receive(:new).and_return(lettings_logs_export_service) + allow(Exports::UserExportService).to receive(:new).and_return(users_export_service) + allow(Exports::OrganisationExportService).to receive(:new).and_return(organisations_export_service) + end + + after do + Timecop.return + end + + context "when exporting daily XMLs" do + context "and no lettings archives get created in lettings logs export" do + let(:lettings_logs_export_service) { instance_double("Exports::LettingsLogExportService", export_xml_lettings_logs: {}) } + + context "and no user or organisation archives get created in user export" do + it "generates a master manifest with the correct name" do + expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) + export_service.export_xml + end + + it "generates a master manifest with CSV headers but no data" do + actual_content = nil + expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\n" + allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string } + + export_service.export_xml + expect(actual_content).to eq(expected_content) + end + end + + context "and one user archive gets created in user export" do + let(:users_export_service) { instance_double("Exports::UserExportService", export_xml_users: { "some_user_file_base_name" => start_time }) } + + it "generates a master manifest with the correct name" do + expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) + export_service.export_xml + end + + it "generates a master manifest with CSV headers and correct data" do + actual_content = nil + expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\nsome_user_file_base_name,2022-05-01 00:00:00 +0100,some_user_file_base_name.zip\n" + allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string } + + export_service.export_xml + expect(actual_content).to eq(expected_content) + end + end + + context "and one organisation archive gets created in organisation export" do + let(:organisations_export_service) { instance_double("Exports::OrganisationExportService", export_xml_organisations: { "some_organisation_file_base_name" => start_time }) } + + it "generates a master manifest with the correct name" do + expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) + export_service.export_xml + end + + it "generates a master manifest with CSV headers and correct data" do + actual_content = nil + expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\nsome_organisation_file_base_name,2022-05-01 00:00:00 +0100,some_organisation_file_base_name.zip\n" + allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string } + + export_service.export_xml + expect(actual_content).to eq(expected_content) + end + end + + context "and user and organisation archive gets created in organisation export" do + let(:organisations_export_service) { instance_double("Exports::OrganisationExportService", export_xml_organisations: { "some_organisation_file_base_name" => start_time }) } + let(:users_export_service) { instance_double("Exports::UserExportService", export_xml_users: { "some_user_file_base_name" => start_time }) } + + it "generates a master manifest with the correct name" do + expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) + export_service.export_xml + end + + it "generates a master manifest with CSV headers and correct data" do + actual_content = nil + expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\nsome_user_file_base_name,2022-05-01 00:00:00 +0100,some_user_file_base_name.zip\nsome_organisation_file_base_name,2022-05-01 00:00:00 +0100,some_organisation_file_base_name.zip\n" + allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string } + + export_service.export_xml + expect(actual_content).to eq(expected_content) + end + end + end + + context "and one lettings archive gets created in lettings logs export" do + let(:lettings_logs_export_service) { instance_double("Exports::LettingsLogExportService", export_xml_lettings_logs: { "some_file_base_name" => start_time }) } + + context "and no user archives get created in user export" do + it "generates a master manifest with the correct name" do + expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) + export_service.export_xml + end + + it "generates a master manifest with CSV headers and correct data" do + actual_content = nil + expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\nsome_file_base_name,2022-05-01 00:00:00 +0100,some_file_base_name.zip\n" + allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string } + + export_service.export_xml + expect(actual_content).to eq(expected_content) + end + end + + context "and one user archive gets created in user export" do + let(:users_export_service) { instance_double("Exports::UserExportService", export_xml_users: { "some_user_file_base_name" => start_time }) } + + it "generates a master manifest with the correct name" do + expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) + export_service.export_xml + end + + it "generates a master manifest with CSV headers and correct data" do + actual_content = nil + expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\nsome_file_base_name,2022-05-01 00:00:00 +0100,some_file_base_name.zip\nsome_user_file_base_name,2022-05-01 00:00:00 +0100,some_user_file_base_name.zip\n" + allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string } + + export_service.export_xml + expect(actual_content).to eq(expected_content) + end + end + end + + context "and multiple lettings archives get created in lettings logs export" do + let(:lettings_logs_export_service) { instance_double("Exports::LettingsLogExportService", export_xml_lettings_logs: { "some_file_base_name" => start_time, "second_file_base_name" => start_time }) } + + context "and no user archives get created in user export" do + it "generates a master manifest with the correct name" do + expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) + export_service.export_xml + end + + it "generates a master manifest with CSV headers and correct data" do + actual_content = nil + expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\nsome_file_base_name,2022-05-01 00:00:00 +0100,some_file_base_name.zip\nsecond_file_base_name,2022-05-01 00:00:00 +0100,second_file_base_name.zip\n" + allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string } + + export_service.export_xml + expect(actual_content).to eq(expected_content) + end + end + + context "and multiple user archive gets created in user export" do + let(:users_export_service) { instance_double("Exports::UserExportService", export_xml_users: { "some_user_file_base_name" => start_time, "second_user_file_base_name" => start_time }) } + + it "generates a master manifest with the correct name" do + expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) + export_service.export_xml + end + + it "generates a master manifest with CSV headers and correct data" do + actual_content = nil + expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\nsome_file_base_name,2022-05-01 00:00:00 +0100,some_file_base_name.zip\nsecond_file_base_name,2022-05-01 00:00:00 +0100,second_file_base_name.zip\nsome_user_file_base_name,2022-05-01 00:00:00 +0100,some_user_file_base_name.zip\nsecond_user_file_base_name,2022-05-01 00:00:00 +0100,second_user_file_base_name.zip\n" + allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string } + + export_service.export_xml + expect(actual_content).to eq(expected_content) + end + end + + context "and multiple user and organisation archives gets created in user export" do + let(:users_export_service) { instance_double("Exports::UserExportService", export_xml_users: { "some_user_file_base_name" => start_time, "second_user_file_base_name" => start_time }) } + let(:organisations_export_service) { instance_double("Exports::OrganisationExportService", export_xml_organisations: { "some_organisation_file_base_name" => start_time, "second_organisation_file_base_name" => start_time }) } + + it "generates a master manifest with the correct name" do + expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) + export_service.export_xml + end + + it "generates a master manifest with CSV headers and correct data" do + actual_content = nil + expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\nsome_file_base_name,2022-05-01 00:00:00 +0100,some_file_base_name.zip\nsecond_file_base_name,2022-05-01 00:00:00 +0100,second_file_base_name.zip\nsome_user_file_base_name,2022-05-01 00:00:00 +0100,some_user_file_base_name.zip\nsecond_user_file_base_name,2022-05-01 00:00:00 +0100,second_user_file_base_name.zip\nsome_organisation_file_base_name,2022-05-01 00:00:00 +0100,some_organisation_file_base_name.zip\nsecond_organisation_file_base_name,2022-05-01 00:00:00 +0100,second_organisation_file_base_name.zip\n" + allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string } + + export_service.export_xml + expect(actual_content).to eq(expected_content) + end + end + end + end + + context "when exporting specific lettings log collection" do + context "and no lettings archives get created in lettings logs export" do + let(:lettings_logs_export_service) { instance_double("Exports::LettingsLogExportService", export_xml_lettings_logs: {}) } + + context "and user archive gets created in user export" do + let(:users_export_service) { instance_double("Exports::UserExportService", export_xml_users: { "some_user_file_base_name" => start_time }) } + + it "generates a master manifest with the correct name" do + expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) + export_service.export_xml(full_update: true, collection: "2022") + end + + it "does not write user data" do + actual_content = nil + expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\n" + allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string } + + export_service.export_xml(full_update: true, collection: "2022") + expect(actual_content).to eq(expected_content) + end + end + end + + context "and lettings archive gets created in lettings logs export" do + let(:lettings_logs_export_service) { instance_double("Exports::LettingsLogExportService", export_xml_lettings_logs: { "some_file_base_name" => start_time }) } + + context "and user archive gets created in user export" do + let(:users_export_service) { instance_double("Exports::UserExportService", export_xml_users: { "some_user_file_base_name" => start_time }) } + + it "generates a master manifest with the correct name" do + expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) + export_service.export_xml(full_update: true, collection: "2023") + end + + it "does not write user data" do + actual_content = nil + expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\nsome_file_base_name,2022-05-01 00:00:00 +0100,some_file_base_name.zip\n" + allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string } + + export_service.export_xml(full_update: true, collection: "2023") + expect(actual_content).to eq(expected_content) + end + end + end + end + + context "when exporting user collection" do + context "and no user archives get created in users export" do + context "and lettings log archive gets created in lettings logs export" do + let(:lettings_logs_export_service) { instance_double("Exports::LettingsLogExportService", export_xml_lettings_logs: { "some_file_base_name" => start_time }) } + + it "generates a master manifest with the correct name" do + expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) + export_service.export_xml(full_update: true, collection: "users") + end + + it "does not write lettings log data" do + actual_content = nil + expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\n" + allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string } + + export_service.export_xml(full_update: true, collection: "users") + expect(actual_content).to eq(expected_content) + end + end + end + + context "and users archive gets created in users export" do + let(:lettings_logs_export_service) { instance_double("Exports::LettingsLogExportService", export_xml_lettings_logs: { "some_file_base_name" => start_time }) } + + context "and lettings log archive gets created in lettings log export" do + let(:users_export_service) { instance_double("Exports::UserExportService", export_xml_users: { "some_user_file_base_name" => start_time }) } + + it "generates a master manifest with the correct name" do + expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) + export_service.export_xml(full_update: true, collection: "users") + end + + it "does not write lettings log data" do + actual_content = nil + expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\nsome_user_file_base_name,2022-05-01 00:00:00 +0100,some_user_file_base_name.zip\n" + allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string } + + export_service.export_xml(full_update: true, collection: "users") + expect(actual_content).to eq(expected_content) + end + end + end + end + + context "when exporting organisation collection" do + context "and no organisation archives get created in organisations export" do + let(:organisations_export_service) { instance_double("Exports::OrganisationExportService", export_xml_organisations: {}) } + + context "and lettings log archive gets created in lettings logs export" do + let(:lettings_logs_export_service) { instance_double("Exports::LettingsLogExportService", export_xml_lettings_logs: { "some_file_base_name" => start_time }) } + + it "generates a master manifest with the correct name" do + expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) + export_service.export_xml(full_update: true, collection: "organisations") + end + + it "does not write lettings log data" do + actual_content = nil + expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\n" + allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string } + + export_service.export_xml(full_update: true, collection: "organisations") + expect(actual_content).to eq(expected_content) + end + end + end + + context "and organisations archive gets created in organisations export" do + let(:lettings_logs_export_service) { instance_double("Exports::LettingsLogExportService", export_xml_lettings_logs: { "some_file_base_name" => start_time }) } + + context "and lettings log archive gets created in lettings log export" do + let(:organisations_export_service) { instance_double("Exports::OrganisationExportService", export_xml_organisations: { "some_organisation_file_base_name" => start_time }) } + + it "generates a master manifest with the correct name" do + expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) + export_service.export_xml(full_update: true, collection: "organisations") + end + + it "does not write lettings log data" do + actual_content = nil + expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\nsome_organisation_file_base_name,2022-05-01 00:00:00 +0100,some_organisation_file_base_name.zip\n" + allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string } + + export_service.export_xml(full_update: true, collection: "organisations") + expect(actual_content).to eq(expected_content) + end + 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 b3f33f24f..6f7d88c91 100644 --- a/spec/services/exports/lettings_log_export_service_spec.rb +++ b/spec/services/exports/lettings_log_export_service_spec.rb @@ -1,7 +1,7 @@ require "rails_helper" RSpec.describe Exports::LettingsLogExportService do - subject(:export_service) { described_class.new(storage_service) } + subject(:export_service) { described_class.new(storage_service, start_time) } let(:storage_service) { instance_double(Storage::S3Service) } @@ -11,8 +11,6 @@ RSpec.describe Exports::LettingsLogExportService do let(:real_2021_2022_form) { Form.new("config/forms/2021_2022.json") } let(:real_2022_2023_form) { Form.new("config/forms/2022_2023.json") } - let(:expected_master_manifest_filename) { "Manifest_2022_05_01_0001.csv" } - let(:expected_master_manifest_rerun) { "Manifest_2022_05_01_0002.csv" } let(:expected_zip_filename) { "core_2021_2022_apr_mar_f0001_inc0001.zip" } let(:expected_data_filename) { "core_2021_2022_apr_mar_f0001_inc0001_pt001.xml" } let(:expected_manifest_filename) { "manifest.xml" } @@ -49,18 +47,9 @@ RSpec.describe Exports::LettingsLogExportService do context "when exporting daily lettings logs in XML" do context "and no lettings logs is available for export" do - it "generates a master manifest with the correct name" do - expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) - export_service.export_xml_lettings_logs - end - - it "generates a master manifest with CSV headers but no data" do - actual_content = nil - expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\n" - allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string } - - export_service.export_xml_lettings_logs - expect(actual_content).to eq(expected_content) + it "returns an empty archives list" do + expect(storage_service).not_to receive(:write_file) + expect(export_service.export_xml_lettings_logs).to eq({}) end end @@ -83,13 +72,9 @@ RSpec.describe Exports::LettingsLogExportService do ) end - it "generates a master manifest with CSV headers but no data" do - actual_content = nil - expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\n" - allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string } - - export_service.export_xml_lettings_logs - expect(actual_content).to eq(expected_content) + it "returns empty archives list for archives manifest" do + expect(storage_service).not_to receive(:write_file) + expect(export_service.export_xml_lettings_logs).to eq({}) end end @@ -101,15 +86,6 @@ RSpec.describe Exports::LettingsLogExportService do export_service.export_xml_lettings_logs end - it "generates an XML manifest file with the expected filename within the ZIP file" do - expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content| - entry = Zip::File.open_buffer(content).find_entry(expected_manifest_filename) - expect(entry).not_to be_nil - expect(entry.name).to eq(expected_manifest_filename) - end - export_service.export_xml_lettings_logs - end - it "generates an XML export file with the expected filename within the ZIP file" do expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content| entry = Zip::File.open_buffer(content).find_entry(expected_data_filename) @@ -141,13 +117,8 @@ RSpec.describe Exports::LettingsLogExportService do export_service.export_xml_lettings_logs end - it "generates a master manifest with CSV headers" do - actual_content = nil - expected_content = "zip-name,date-time zipped folder generated,zip-file-uri\ncore_2021_2022_apr_mar_f0001_inc0001,2022-05-01 00:00:00 +0100,#{expected_zip_filename}\n" - allow(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) { |_, arg2| actual_content = arg2&.string } - - export_service.export_xml_lettings_logs - expect(actual_content).to eq(expected_content) + it "returns the list with correct archive" do + expect(export_service.export_xml_lettings_logs).to eq({ expected_zip_filename.gsub(".zip", "") => start_time }) end end @@ -178,8 +149,10 @@ RSpec.describe Exports::LettingsLogExportService do end context "with 23/24 collection period" do + let(:start_time) { Time.zone.local(2023, 4, 3) } + before do - Timecop.freeze(Time.zone.local(2023, 4, 3)) + Timecop.freeze(start_time) Singleton.__init__(FormHandler) stub_request(:get, "https://api.os.uk/search/places/v1/uprn?dataset=DPA,LPI&key=OS_DATA_KEY&uprn=100023336956") .to_return(status: 200, body: '{"status":200,"results":[{"DPA":{ @@ -234,15 +207,15 @@ RSpec.describe Exports::LettingsLogExportService 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("Creating core_2021_2022_apr_mar_f0001_inc0001 - 1 logs") + 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("Creating core_2022_2023_apr_mar_f0001_inc0001 - 1 logs") + 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("Creating core_2023_2024_apr_mar_f0001_inc0001 - 0 logs") + expect(Rails.logger).to receive(:info).with("Creating core_2023_2024_apr_mar_f0001_inc0001 - 0 resources") export_service.export_xml_lettings_logs end @@ -250,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("Creating core_2022_2023_apr_mar_f0001_inc0001 - 1 logs") + 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") @@ -262,22 +235,22 @@ RSpec.describe Exports::LettingsLogExportService do let(:expected_zip_filename2) { "core_2022_2023_apr_mar_f0001_inc0001.zip" } before do - LogsExport.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: 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("Creating core_2021_2022_apr_mar_f0007_inc0004 - 1 logs") + 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("Creating core_2022_2023_apr_mar_f0001_inc0001 - 1 logs") + 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("Creating core_2023_2024_apr_mar_f0001_inc0001 - 0 logs") + expect(Rails.logger).to receive(:info).with("Creating core_2023_2024_apr_mar_f0001_inc0001 - 0 resources") export_service.export_xml_lettings_logs end @@ -304,18 +277,13 @@ RSpec.describe Exports::LettingsLogExportService do it "creates a logs export record in a database with correct time" do expect { export_service.export_xml_lettings_logs } - .to change(LogsExport, :count).by(3) - expect(LogsExport.last.started_at).to be_within(2.seconds).of(start_time) + .to change(Export, :count).by(3) + expect(Export.last.started_at).to be_within(2.seconds).of(start_time) end context "when this is the first export (full)" do - it "records a ZIP archive in the master manifest (existing lettings logs)" do - expect(storage_service).to receive(:write_file).with(expected_master_manifest_filename, any_args) do |_, csv_content| - csv = CSV.parse(csv_content, headers: true) - expect(csv&.count).to be > 0 - end - - export_service.export_xml_lettings_logs + it "returns a ZIP archive for the master manifest (existing lettings logs)" do + expect(export_service.export_xml_lettings_logs).to eq({ expected_zip_filename.gsub(".zip", "").gsub(".zip", "") => start_time }) end end @@ -360,15 +328,12 @@ RSpec.describe Exports::LettingsLogExportService do context "when this is a second export (partial)" do before do start_time = Time.zone.local(2022, 6, 1) - LogsExport.new(started_at: start_time).save! + Export.new(started_at: start_time, collection: 2021).save! end - it "does not add any entry in the master manifest (no lettings logs)" do - expect(storage_service).to receive(:write_file).with(expected_master_manifest_rerun, any_args) do |_, csv_content| - csv = CSV.parse(csv_content, headers: true) - expect(csv&.count).to eq(0) - end - export_service.export_xml_lettings_logs + it "does not add any entry for the master manifest (no lettings logs)" do + expect(storage_service).not_to receive(:write_file) + expect(export_service.export_xml_lettings_logs).to eq({}) end end end @@ -379,28 +344,19 @@ RSpec.describe Exports::LettingsLogExportService do export_service.export_xml_lettings_logs end - it "increments the master manifest number by 1" do - expect(storage_service).to receive(:write_file).with(expected_master_manifest_rerun, any_args) - export_service.export_xml_lettings_logs - end - context "and we trigger another full update" do it "increments the base number" do export_service.export_xml_lettings_logs(full_update: true) - expect(LogsExport.last.base_number).to eq(2) + expect(Export.last.base_number).to eq(2) end it "resets the increment number" do export_service.export_xml_lettings_logs(full_update: true) - expect(LogsExport.last.increment_number).to eq(1) + expect(Export.last.increment_number).to eq(1) end - it "records a ZIP archive in the master manifest (existing lettings logs)" do - expect(storage_service).to receive(:write_file).with(expected_master_manifest_rerun, any_args) do |_, csv_content| - csv = CSV.parse(csv_content, headers: true) - expect(csv&.count).to be > 0 - end - export_service.export_xml_lettings_logs(full_update: true) + it "returns a correct archives list for manifest file" do + expect(export_service.export_xml_lettings_logs(full_update: true)).to eq({ "core_2021_2022_apr_mar_f0002_inc0001" => start_time }) end it "generates a ZIP export file with the expected filename" do @@ -416,7 +372,7 @@ RSpec.describe Exports::LettingsLogExportService do it "doesn't increment the manifest number by 1" do export_service.export_xml_lettings_logs - expect(LogsExport.last.increment_number).to eq(1) + expect(Export.last.increment_number).to eq(1) end end @@ -424,19 +380,18 @@ RSpec.describe Exports::LettingsLogExportService do before do FactoryBot.create(:lettings_log, startdate: Time.zone.local(2022, 2, 1), updated_at: Time.zone.local(2022, 4, 27), values_updated_at: Time.zone.local(2022, 4, 29)) FactoryBot.create(:lettings_log, startdate: Time.zone.local(2022, 2, 1), updated_at: Time.zone.local(2022, 4, 27), values_updated_at: Time.zone.local(2022, 4, 29)) - LogsExport.create!(started_at: Time.zone.local(2022, 4, 28), base_number: 1, increment_number: 1) + Export.create!(started_at: Time.zone.local(2022, 4, 28), base_number: 1, increment_number: 1) end it "generates an XML manifest file with the expected content within the ZIP file" do expected_content = replace_record_number(local_manifest_file.read, 2) - expect(storage_service).to receive(:write_file).with(expected_master_manifest_rerun, any_args) expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content| entry = Zip::File.open_buffer(content).find_entry(expected_manifest_filename) expect(entry).not_to be_nil expect(entry.get_input_stream.read).to eq(expected_content) end - export_service.export_xml_lettings_logs + expect(export_service.export_xml_lettings_logs).to eq({ expected_zip_filename.gsub(".zip", "") => start_time }) end end @@ -461,8 +416,10 @@ RSpec.describe Exports::LettingsLogExportService do end context "with 24/25 collection period" do + let(:start_time) { Time.zone.local(2024, 4, 3) } + before do - Timecop.freeze(Time.zone.local(2024, 4, 3)) + Timecop.freeze(start_time) Singleton.__init__(FormHandler) end diff --git a/spec/services/exports/organisation_export_service_spec.rb b/spec/services/exports/organisation_export_service_spec.rb new file mode 100644 index 000000000..4de0e84a8 --- /dev/null +++ b/spec/services/exports/organisation_export_service_spec.rb @@ -0,0 +1,219 @@ +require "rails_helper" + +RSpec.describe Exports::OrganisationExportService do + subject(:export_service) { described_class.new(storage_service, start_time) } + + let(:storage_service) { instance_double(Storage::S3Service) } + + let(:xml_export_file) { File.open("spec/fixtures/exports/organisation.xml", "r:UTF-8") } + let(:local_manifest_file) { File.open("spec/fixtures/exports/manifest.xml", "r:UTF-8") } + + let(:expected_zip_filename) { "organisations_2024_2025_apr_mar_f0001_inc0001.zip" } + let(:expected_data_filename) { "organisations_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) } + + def replace_entity_ids(organisation, export_template) + export_template.sub!(/\{id\}/, organisation["id"].to_s) + 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 + + def replace_record_number(export_template, record_number) + export_template.sub!(/\{recno\}/, record_number.to_s) + end + + before do + Timecop.freeze(start_time) + Singleton.__init__(FormHandler) + allow(storage_service).to receive(:write_file) + end + + after do + Timecop.return + end + + context "when exporting daily organisations in XML" do + context "and no organisations are available for export" do + it "returns an empty archives list" do + expect(export_service.export_xml_organisations).to eq({}) + end + end + + context "and one organisation is available for export" do + let!(:organisation) { create(:organisation) } + + it "generates a ZIP export file with the expected filename" do + expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) + export_service.export_xml_organisations + end + + it "generates an XML export file with the expected filename within the ZIP file" do + expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content| + entry = Zip::File.open_buffer(content).find_entry(expected_data_filename) + expect(entry).not_to be_nil + expect(entry.name).to eq(expected_data_filename) + end + export_service.export_xml_organisations + end + + it "generates an XML manifest file with the expected content within the ZIP file" do + expected_content = replace_record_number(local_manifest_file.read, 1) + expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content| + entry = Zip::File.open_buffer(content).find_entry(expected_manifest_filename) + expect(entry).not_to be_nil + expect(entry.get_input_stream.read).to eq(expected_content) + end + + export_service.export_xml_organisations + end + + it "generates an XML export file with the expected content within the ZIP file" do + expected_content = replace_entity_ids(organisation, xml_export_file.read) + expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content| + entry = Zip::File.open_buffer(content).find_entry(expected_data_filename) + expect(entry).not_to be_nil + expect(entry.get_input_stream.read).to eq(expected_content) + end + + export_service.export_xml_organisations + end + + it "returns the list with correct archive" do + expect(export_service.export_xml_organisations).to eq({ expected_zip_filename.gsub(".zip", "") => start_time }) + end + end + + context "and multiple organisations are available for export" do + before do + create(:organisation) + create(:organisation) + end + + it "generates an XML manifest file with the expected content within the ZIP file" do + expected_content = replace_record_number(local_manifest_file.read, 2) + expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content| + entry = Zip::File.open_buffer(content).find_entry(expected_manifest_filename) + expect(entry).not_to be_nil + expect(entry.get_input_stream.read).to eq(expected_content) + end + + export_service.export_xml_organisations + end + + it "creates an export record in a database with correct time" do + expect { export_service.export_xml_organisations } + .to change(Export, :count).by(1) + expect(Export.last.started_at).to be_within(2.seconds).of(start_time) + end + + context "when this is the first export (full)" do + it "returns a ZIP archive for the master manifest" do + expect(export_service.export_xml_organisations).to eq({ expected_zip_filename.gsub(".zip", "").gsub(".zip", "") => start_time }) + end + end + + context "and underlying data changes between getting the organisations and writting the manifest" do + def remove_organisations(organisations) + organisations.each(&:destroy) + file = Tempfile.new + doc = Nokogiri::XML("") + doc.write_xml_to(file, encoding: "UTF-8") + file.rewind + file + end + + def create_fake_maifest + file = Tempfile.new + doc = Nokogiri::XML("") + doc.write_xml_to(file, encoding: "UTF-8") + file.rewind + file + end + + it "maintains the same record number" do + # rubocop:disable RSpec/SubjectStub + allow(export_service).to receive(:build_export_xml) do |organisations| + remove_organisations(organisations) + end + allow(export_service).to receive(:build_manifest_xml) do + create_fake_maifest + end + + expect(export_service).to receive(:build_manifest_xml).with(2) + # rubocop:enable RSpec/SubjectStub + export_service.export_xml_organisations + end + end + + 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: "organisations").save! # this should be organisation export + end + + it "does not add any entry for the master manifest (no organisations)" do + expect(export_service.export_xml_organisations).to eq({}) + end + end + end + + context "and a previous export has run the same day having organisations" do + before do + create(:organisation) + export_service.export_xml_organisations + end + + context "and we trigger another full update" do + it "increments the base number" do + export_service.export_xml_organisations(full_update: true) + expect(Export.last.base_number).to eq(2) + end + + it "resets the increment number" do + export_service.export_xml_organisations(full_update: true) + expect(Export.last.increment_number).to eq(1) + end + + it "returns a correct archives list for manifest file" do + expect(export_service.export_xml_organisations(full_update: true)).to eq({ "organisations_2024_2025_apr_mar_f0002_inc0001" => start_time }) + end + + it "generates a ZIP export file with the expected filename" do + expect(storage_service).to receive(:write_file).with("organisations_2024_2025_apr_mar_f0002_inc0001.zip", any_args) + export_service.export_xml_organisations(full_update: true) + end + end + end + + context "and a previous export has run having no organisations" do + before { export_service.export_xml_organisations } + + it "doesn't increment the manifest number by 1" do + export_service.export_xml_organisations + + expect(Export.last.increment_number).to eq(1) + end + end + + context "and an organisation has been migrated since the previous partial export" do + before do + create(:organisation, updated_at: Time.zone.local(2022, 4, 27)) + create(:organisation, updated_at: Time.zone.local(2022, 4, 27)) + Export.create!(started_at: Time.zone.local(2022, 4, 26), base_number: 1, increment_number: 1) + end + + it "generates an XML manifest file with the expected content within the ZIP file" do + expected_content = replace_record_number(local_manifest_file.read, 2) + expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content| + entry = Zip::File.open_buffer(content).find_entry(expected_manifest_filename) + expect(entry).not_to be_nil + expect(entry.get_input_stream.read).to eq(expected_content) + end + + expect(export_service.export_xml_organisations).to eq({ expected_zip_filename.gsub(".zip", "") => start_time }) + end + end + end +end diff --git a/spec/services/exports/user_export_service_spec.rb b/spec/services/exports/user_export_service_spec.rb new file mode 100644 index 000000000..713d6f907 --- /dev/null +++ b/spec/services/exports/user_export_service_spec.rb @@ -0,0 +1,219 @@ +require "rails_helper" + +RSpec.describe Exports::UserExportService do + subject(:export_service) { described_class.new(storage_service, start_time) } + + let(:storage_service) { instance_double(Storage::S3Service) } + + let(:xml_export_file) { File.open("spec/fixtures/exports/user.xml", "r:UTF-8") } + let(:local_manifest_file) { File.open("spec/fixtures/exports/manifest.xml", "r:UTF-8") } + + let(:expected_zip_filename) { "users_2024_2025_apr_mar_f0001_inc0001.zip" } + 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) } + + 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!(/\{email\}/, user["email"].to_s) + end + + def replace_record_number(export_template, record_number) + export_template.sub!(/\{recno\}/, record_number.to_s) + end + + before do + Timecop.freeze(start_time) + Singleton.__init__(FormHandler) + allow(storage_service).to receive(:write_file) + end + + after do + Timecop.return + end + + context "when exporting daily users in XML" do + context "and no users are available for export" do + it "returns an empty archives list" do + expect(export_service.export_xml_users).to eq({}) + end + end + + context "and one user is available for export" do + let!(:user) { create(:user, organisation:, 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) + export_service.export_xml_users + end + + it "generates an XML export file with the expected filename within the ZIP file" do + expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content| + entry = Zip::File.open_buffer(content).find_entry(expected_data_filename) + expect(entry).not_to be_nil + expect(entry.name).to eq(expected_data_filename) + end + export_service.export_xml_users + end + + it "generates an XML manifest file with the expected content within the ZIP file" do + expected_content = replace_record_number(local_manifest_file.read, 1) + expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content| + entry = Zip::File.open_buffer(content).find_entry(expected_manifest_filename) + expect(entry).not_to be_nil + expect(entry.get_input_stream.read).to eq(expected_content) + end + + export_service.export_xml_users + end + + it "generates an XML export file with the expected content within the ZIP file" do + expected_content = replace_entity_ids(user, xml_export_file.read) + expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content| + entry = Zip::File.open_buffer(content).find_entry(expected_data_filename) + expect(entry).not_to be_nil + expect(entry.get_input_stream.read).to eq(expected_content) + end + + export_service.export_xml_users + end + + it "returns the list with correct archive" do + expect(export_service.export_xml_users).to eq({ expected_zip_filename.gsub(".zip", "") => start_time }) + end + end + + context "and multiple users are available for export" do + before do + create(:user, organisation:) + create(:user, organisation:) + end + + it "generates an XML manifest file with the expected content within the ZIP file" do + expected_content = replace_record_number(local_manifest_file.read, 2) + expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content| + entry = Zip::File.open_buffer(content).find_entry(expected_manifest_filename) + expect(entry).not_to be_nil + expect(entry.get_input_stream.read).to eq(expected_content) + end + + export_service.export_xml_users + end + + it "creates an export record in a database with correct time" do + expect { export_service.export_xml_users } + .to change(Export, :count).by(1) + expect(Export.last.started_at).to be_within(2.seconds).of(start_time) + end + + context "when this is the first export (full)" do + it "returns a ZIP archive for the master manifest (existing lettings logs)" do + expect(export_service.export_xml_users).to eq({ expected_zip_filename.gsub(".zip", "").gsub(".zip", "") => start_time }) + end + end + + context "and underlying data changes between getting the users and writting the manifest" do + def remove_users(users) + users.each(&:destroy) + file = Tempfile.new + doc = Nokogiri::XML("") + doc.write_xml_to(file, encoding: "UTF-8") + file.rewind + file + end + + def create_fake_maifest + file = Tempfile.new + doc = Nokogiri::XML("") + doc.write_xml_to(file, encoding: "UTF-8") + file.rewind + file + end + + it "maintains the same record number" do + # rubocop:disable RSpec/SubjectStub + allow(export_service).to receive(:build_export_xml) do |users| + remove_users(users) + end + allow(export_service).to receive(:build_manifest_xml) do + create_fake_maifest + end + + expect(export_service).to receive(:build_manifest_xml).with(2) + # rubocop:enable RSpec/SubjectStub + export_service.export_xml_users + end + end + + 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: "users").save! # this should be user export + end + + it "does not add any entry for the master manifest (no users)" do + expect(export_service.export_xml_users).to eq({}) + end + end + end + + context "and a previous export has run the same day having users" do + before do + create(:user, organisation:) + export_service.export_xml_users + end + + context "and we trigger another full update" do + it "increments the base number" do + export_service.export_xml_users(full_update: true) + expect(Export.last.base_number).to eq(2) + end + + it "resets the increment number" do + export_service.export_xml_users(full_update: true) + expect(Export.last.increment_number).to eq(1) + end + + it "returns a correct archives list for manifest file" do + expect(export_service.export_xml_users(full_update: true)).to eq({ "users_2024_2025_apr_mar_f0002_inc0001" => start_time }) + end + + it "generates a ZIP export file with the expected filename" do + expect(storage_service).to receive(:write_file).with("users_2024_2025_apr_mar_f0002_inc0001.zip", any_args) + export_service.export_xml_users(full_update: true) + end + end + end + + context "and a previous export has run having no users" do + before { export_service.export_xml_users } + + it "doesn't increment the manifest number by 1" do + export_service.export_xml_users + + expect(Export.last.increment_number).to eq(1) + end + end + + context "and a user has been migrated since the previous partial export" do + before do + create(:user, updated_at: Time.zone.local(2022, 4, 27), organisation:) + create(:user, updated_at: Time.zone.local(2022, 4, 27), organisation:) + Export.create!(started_at: Time.zone.local(2022, 4, 26), base_number: 1, increment_number: 1) + end + + it "generates an XML manifest file with the expected content within the ZIP file" do + expected_content = replace_record_number(local_manifest_file.read, 2) + expect(storage_service).to receive(:write_file).with(expected_zip_filename, any_args) do |_, content| + entry = Zip::File.open_buffer(content).find_entry(expected_manifest_filename) + expect(entry).not_to be_nil + expect(entry.get_input_stream.read).to eq(expected_content) + end + + expect(export_service.export_xml_users).to eq({ expected_zip_filename.gsub(".zip", "") => start_time }) + end + end + end +end From fcbc969017804d8aa37ceb53aedb238773dba4b3 Mon Sep 17 00:00:00 2001 From: Rachael Booth Date: Wed, 2 Oct 2024 10:28:01 +0100 Subject: [PATCH 10/14] CLDC-3616: Adjust PR link for review apps to new domain (#2669) --- .github/workflows/review_pipeline.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/review_pipeline.yml b/.github/workflows/review_pipeline.yml index a1c5e1e2e..887c77673 100644 --- a/.github/workflows/review_pipeline.yml +++ b/.github/workflows/review_pipeline.yml @@ -52,6 +52,6 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - msg: "Created review app at https://review.submit-social-housing-data.levellingup.gov.uk/${{ github.event.pull_request.number }}" + msg: "Created review app at https://review.submit-social-housing-data.communities.gov.uk/${{ github.event.pull_request.number }}" check_for_duplicate_msg: true duplicate_msg_pattern: Created review app at* From ec0d7a027a604ebb98a3f91dd906f6d57ef4f3b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 11:25:59 +0100 Subject: [PATCH 11/14] Bump webrick from 1.8.1 to 1.8.2 in /docs (#2670) Bumps [webrick](https://github.com/ruby/webrick) from 1.8.1 to 1.8.2. - [Release notes](https://github.com/ruby/webrick/releases) - [Commits](https://github.com/ruby/webrick/compare/v1.8.1...v1.8.2) --- updated-dependencies: - dependency-name: webrick dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 2a406a953..8270007c8 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -253,7 +253,7 @@ GEM unf_ext unf_ext (0.0.8.2) unicode-display_width (1.8.0) - webrick (1.8.1) + webrick (1.8.2) PLATFORMS arm64-darwin-21 From 971229da4e59340f829001131223390ce9499a3b Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Wed, 2 Oct 2024 12:44:15 +0100 Subject: [PATCH 12/14] Make collection_year_radio_options same as side filter options (#2668) --- app/helpers/filters_helper.rb | 10 +++--- spec/helpers/filters_helper_spec.rb | 50 +++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 5 deletions(-) diff --git a/app/helpers/filters_helper.rb b/app/helpers/filters_helper.rb index 5f8488bc9..d8a991b87 100644 --- a/app/helpers/filters_helper.rb +++ b/app/helpers/filters_helper.rb @@ -146,11 +146,11 @@ module FiltersHelper end def collection_year_radio_options - { - current_collection_start_year.to_s => { label: year_combo(current_collection_start_year) }, - previous_collection_start_year.to_s => { label: year_combo(previous_collection_start_year) }, - archived_collection_start_year.to_s => { label: year_combo(archived_collection_start_year) }, - } + options = {} + collection_year_options.map do |year, label| + options[year] = { label: } + end + options end def filters_applied_text(filter_type) diff --git a/spec/helpers/filters_helper_spec.rb b/spec/helpers/filters_helper_spec.rb index a2b658cdf..58c82f0c9 100644 --- a/spec/helpers/filters_helper_spec.rb +++ b/spec/helpers/filters_helper_spec.rb @@ -527,6 +527,56 @@ RSpec.describe FiltersHelper do end end + describe "#collection_year_radio_options" do + context "with 23/24 as the current collection year" do + before do + allow(Time).to receive(:now).and_return(Time.zone.local(2023, 5, 1)) + end + + context "and in crossover period" do + before do + allow(FormHandler.instance).to receive(:in_crossover_period?).and_return(true) + end + + it "has the correct options" do + expect(collection_year_radio_options).to eq( + { + "2023" => { label: "2023/24" }, "2022" => { label: "2022/23" }, "2021" => { label: "2021/22" } + }, + ) + end + end + + context "and not in crossover period" do + before do + allow(FormHandler.instance).to receive(:in_crossover_period?).and_return(false) + end + + it "has the correct options" do + expect(collection_year_radio_options).to eq( + { + "2023" => { label: "2023/24" }, "2022" => { label: "2022/23" } + }, + ) + end + end + end + + context "with 24/25 as the current collection year" do + before do + allow(Time).to receive(:now).and_return(Time.zone.local(2024, 5, 1)) + end + + it "has the correct options" do + expect(collection_year_radio_options).to eq( + { + "2024" => { label: "2024/25" }, "2023" => { label: "2023/24" }, "2022" => { label: "2022/23" } + }, + ) + end + end + end + describe "#filters_applied_text" do let(:filter_type) { "lettings_logs" } let(:result) { filters_applied_text(filter_type) } From 53c3b3e61a4e3886681dbc038853c5cd195ad541 Mon Sep 17 00:00:00 2001 From: Manny Dinssa <44172848+Dinssa@users.noreply.github.com> Date: Thu, 3 Oct 2024 09:26:06 +0100 Subject: [PATCH 13/14] CLDC-3633 Full stops in error messages (#2628) * Make full stops consistent in error messages * Fix test * Update few hardcoded errors * Update join in remaining row parsers * Update some more tests * Update more hard coded BU errors * Fix typo I introduced - bedrooms to bedroom --------- Co-authored-by: Kat Co-authored-by: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> --- app/controllers/auth/sessions_controller.rb | 6 +- .../lettings/year2023/row_parser.rb | 24 +- .../lettings/year2024/row_parser.rb | 24 +- .../bulk_upload/sales/year2023/row_parser.rb | 20 +- .../bulk_upload/sales/year2024/row_parser.rb | 18 +- config/locales/en.yml | 715 +++++++++--------- .../interruption_screen_helper_spec.rb | 4 +- ...ate_schemes_and_locations_from_csv_spec.rb | 2 +- .../lettings/questions/declaration_spec.rb | 4 +- .../form/lettings/questions/offered_spec.rb | 2 +- .../form/lettings/questions/uprn_spec.rb | 2 +- .../form/sales/questions/prevown_spec.rb | 4 +- .../sales/questions/privacy_notice_spec.rb | 8 +- .../sales/questions/staircase_owned_spec.rb | 4 +- spec/models/form/sales/questions/uprn_spec.rb | 2 +- .../location_deactivation_period_spec.rb | 6 +- spec/models/notification_spec.rb | 4 +- .../models/scheme_deactivation_period_spec.rb | 4 +- .../validations/financial_validations_spec.rb | 46 +- .../validations/household_validations_spec.rb | 4 +- .../validations/property_validations_spec.rb | 4 +- .../sales/financial_validations_spec.rb | 12 +- .../sales/household_validations_spec.rb | 8 +- .../sales/property_validations_spec.rb | 20 +- .../sale_information_validations_spec.rb | 12 +- .../sales/setup_validations_spec.rb | 14 +- .../validations/setup_validations_spec.rb | 4 +- .../validations/shared_validations_spec.rb | 4 +- .../requests/lettings_logs_controller_spec.rb | 2 +- spec/requests/sales_logs_controller_spec.rb | 2 +- .../bulk_upload/lettings/validator_spec.rb | 8 +- .../lettings/year2023/row_parser_spec.rb | 72 +- .../lettings/year2024/row_parser_spec.rb | 74 +- .../bulk_upload/sales/validator_spec.rb | 4 +- .../sales/year2023/row_parser_spec.rb | 14 +- .../sales/year2024/row_parser_spec.rb | 42 +- 36 files changed, 599 insertions(+), 600 deletions(-) diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb index d2d6a6dd1..bb5dbe83e 100644 --- a/app/controllers/auth/sessions_controller.rb +++ b/app/controllers/auth/sessions_controller.rb @@ -4,12 +4,12 @@ class Auth::SessionsController < Devise::SessionsController def create self.resource = User.new if params.dig("user", "email").empty? - resource.errors.add :email, "Enter an email address" + resource.errors.add :email, "Enter an email address." elsif !email_valid?(params.dig("user", "email")) - resource.errors.add :email, "Enter an email address in the correct format, like name@example.com" + resource.errors.add :email, "Enter an email address in the correct format, like name@example.com." end if params.dig("user", "password").empty? - resource.errors.add :password, "Enter a password" + resource.errors.add :password, "Enter a password." end if resource.errors.present? render :new, status: :unprocessable_entity diff --git a/app/services/bulk_upload/lettings/year2023/row_parser.rb b/app/services/bulk_upload/lettings/year2023/row_parser.rb index a09dc2bfe..481b399dd 100644 --- a/app/services/bulk_upload/lettings/year2023/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2023/row_parser.rb @@ -555,7 +555,7 @@ private return if field_3.blank? unless assigned_to - errors.add(:field_3, "User with the specified email could not be found") + errors.add(:field_3, "User with the specified email could not be found.") end end @@ -565,7 +565,7 @@ private return if assigned_to.organisation == owning_organisation&.absorbing_organisation || assigned_to.organisation == managing_organisation&.absorbing_organisation block_log_creation! - errors.add(:field_3, "User must be related to owning organisation or managing organisation") + errors.add(:field_3, "User must be related to owning organisation or managing organisation.") end def assigned_to @@ -588,7 +588,7 @@ private field_mapping_for_errors[interruption_screen_question_id.to_sym]&.each do |field| if errors.none? { |e| field_mapping_for_errors[interruption_screen_question_id.to_sym].include?(e.attribute) } - error_message = [display_title_text(question.page.title_text, log), display_informative_text(question.page.informative_text, log)].reject(&:empty?).join(". ") + error_message = [display_title_text(question.page.title_text, log), display_informative_text(question.page.informative_text, log)].reject(&:empty?).join(" ") errors.add(field, message: error_message, category: :soft_validation) end end @@ -780,7 +780,7 @@ private def validate_related_location_exists if scheme && location_id.present? && location.nil? && location_field.present? block_log_creation! - errors.add(location_field, "#{location_or_scheme.capitalize} code must relate to a #{location_or_scheme} that is owned by the owning organisation or managing organisation", category: :setup) + errors.add(location_field, "#{location_or_scheme.capitalize} code must relate to a #{location_or_scheme} that is owned by the owning organisation or managing organisation.", category: :setup) end end @@ -794,7 +794,7 @@ private def validate_related_scheme_exists if scheme_id.present? && scheme_field.present? && owning_organisation.present? && managing_organisation.present? && scheme.nil? block_log_creation! - errors.add(scheme_field, "This #{scheme_or_management_group} code does not belong to the owning organisation or managing organisation", category: :setup) + errors.add(scheme_field, "This #{scheme_or_management_group} code does not belong to the owning organisation or managing organisation.", category: :setup) end end @@ -810,7 +810,7 @@ private block_log_creation! if errors[:field_2].blank? - errors.add(:field_2, "This managing organisation does not have a relationship with the owning organisation", category: :setup) + errors.add(:field_2, "This managing organisation does not have a relationship with the owning organisation.", category: :setup) end end end @@ -820,7 +820,7 @@ private block_log_creation! if errors[:field_2].blank? - errors.add(:field_2, "The managing organisation code is incorrect", category: :setup) + errors.add(:field_2, "The managing organisation code is incorrect.", category: :setup) end end end @@ -828,7 +828,7 @@ private def validate_managing_org_data_given if field_2.blank? block_log_creation! - errors.add(:field_2, "The managing organisation code is incorrect", category: :setup) + errors.add(:field_2, "The managing organisation code is incorrect.", category: :setup) end end @@ -837,7 +837,7 @@ private block_log_creation! if errors[:field_1].blank? - errors.add(:field_1, "The owning organisation code provided is for an organisation that does not own stock", category: :setup) + errors.add(:field_1, "The owning organisation code provided is for an organisation that does not own stock.", category: :setup) end end end @@ -847,7 +847,7 @@ private block_log_creation! if errors[:field_1].blank? - errors.add(:field_1, "The owning organisation code is incorrect", category: :setup) + errors.add(:field_1, "The owning organisation code is incorrect.", category: :setup) end end end @@ -864,7 +864,7 @@ private block_log_creation! if errors[:field_1].blank? - errors.add(:field_1, "You do not have permission to add logs for this owning organisation", category: :setup) + errors.add(:field_1, "You do not have permission to add logs for this owning organisation.", category: :setup) end end end @@ -887,7 +887,7 @@ private def validate_if_log_already_exists if log_already_exists? - error_message = "This is a duplicate log" + error_message = "This is a duplicate log." errors.add(:field_1, error_message) # owning_organisation errors.add(:field_7, error_message) # startdate diff --git a/app/services/bulk_upload/lettings/year2024/row_parser.rb b/app/services/bulk_upload/lettings/year2024/row_parser.rb index dc0b2dca3..3a18ac633 100644 --- a/app/services/bulk_upload/lettings/year2024/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2024/row_parser.rb @@ -579,7 +579,7 @@ private return if field_3.blank? unless assigned_to - errors.add(:field_3, "User with the specified email could not be found") + errors.add(:field_3, "User with the specified email could not be found.") end end @@ -595,7 +595,7 @@ private return if assigned_to.organisation == owning_organisation&.absorbing_organisation || assigned_to.organisation == managing_organisation&.absorbing_organisation block_log_creation! - errors.add(:field_3, "User must be related to owning organisation or managing organisation") + errors.add(:field_3, "User must be related to owning organisation or managing organisation.") end def assigned_to @@ -650,7 +650,7 @@ private field_mapping_for_errors[interruption_screen_question_id.to_sym]&.each do |field| if errors.none? { |e| field_mapping_for_errors[interruption_screen_question_id.to_sym].include?(e.attribute) } - error_message = [display_title_text(question.page.title_text, log), display_informative_text(question.page.informative_text, log)].reject(&:empty?).join(". ") + error_message = [display_title_text(question.page.title_text, log), display_informative_text(question.page.informative_text, log)].reject(&:empty?).join(" ") errors.add(field, message: error_message, category: :soft_validation) end end @@ -832,14 +832,14 @@ private def validate_related_location_exists if scheme && field_6.present? && location.nil? && :field_6.present? block_log_creation! - errors.add(:field_6, "Location code must relate to a location that is owned by the owning organisation or managing organisation", category: :setup) + errors.add(:field_6, "Location code must relate to a location that is owned by the owning organisation or managing organisation.", category: :setup) end end def validate_related_scheme_exists if field_5.present? && :field_5.present? && owning_organisation.present? && managing_organisation.present? && scheme.nil? block_log_creation! - errors.add(:field_5, "This scheme code does not belong to the owning organisation or managing organisation", category: :setup) + errors.add(:field_5, "This scheme code does not belong to the owning organisation or managing organisation.", category: :setup) end end @@ -848,7 +848,7 @@ private block_log_creation! if errors[:field_2].blank? - errors.add(:field_2, "This managing organisation does not have a relationship with the owning organisation", category: :setup) + errors.add(:field_2, "This managing organisation does not have a relationship with the owning organisation.", category: :setup) end end end @@ -858,7 +858,7 @@ private block_log_creation! if field_2.present? && errors[:field_2].blank? - errors.add(:field_2, "The managing organisation code is incorrect", category: :setup) + errors.add(:field_2, "The managing organisation code is incorrect.", category: :setup) end end end @@ -875,7 +875,7 @@ private block_log_creation! if errors[:field_1].blank? - errors.add(:field_1, "The owning organisation code provided is for an organisation that does not own stock", category: :setup) + errors.add(:field_1, "The owning organisation code provided is for an organisation that does not own stock.", category: :setup) end end end @@ -885,7 +885,7 @@ private block_log_creation! if field_1.present? && errors[:field_1].blank? - errors.add(:field_1, "The owning organisation code is incorrect", category: :setup) + errors.add(:field_1, "The owning organisation code is incorrect.", category: :setup) end end end @@ -906,9 +906,9 @@ private return if errors[:field_1].present? if bulk_upload.user.support? - errors.add(:field_1, "This owning organisation is not affiliated with #{bulk_upload_organisation.name}", category: :setup) + errors.add(:field_1, "This owning organisation is not affiliated with #{bulk_upload_organisation.name}.", category: :setup) else - errors.add(:field_1, "You do not have permission to add logs for this owning organisation", category: :setup) + errors.add(:field_1, "You do not have permission to add logs for this owning organisation.", category: :setup) end end @@ -943,7 +943,7 @@ private def validate_if_log_already_exists if log_already_exists? - error_message = "This is a duplicate log" + error_message = "This is a duplicate log." errors.add(:field_1, error_message) # owning_organisation errors.add(:field_8, error_message) # startdate diff --git a/app/services/bulk_upload/sales/year2023/row_parser.rb b/app/services/bulk_upload/sales/year2023/row_parser.rb index b122a02dc..715a8983a 100644 --- a/app/services/bulk_upload/sales/year2023/row_parser.rb +++ b/app/services/bulk_upload/sales/year2023/row_parser.rb @@ -1158,7 +1158,7 @@ private block_log_creation! if errors[:field_1].blank? - errors.add(:field_1, "The owning organisation code is incorrect", category: :setup) + errors.add(:field_1, "The owning organisation code is incorrect.", category: :setup) end end end @@ -1168,7 +1168,7 @@ private block_log_creation! if errors[:field_1].blank? - errors.add(:field_1, "The owning organisation code is incorrect", category: :setup) + errors.add(:field_1, "The owning organisation code is incorrect.", category: :setup) end end end @@ -1178,7 +1178,7 @@ private block_log_creation! if errors[:field_1].blank? - errors.add(:field_1, "The owning organisation code provided is for an organisation that does not own stock", category: :setup) + errors.add(:field_1, "The owning organisation code provided is for an organisation that does not own stock.", category: :setup) end end end @@ -1188,7 +1188,7 @@ private block_log_creation! if errors[:field_1].blank? - errors.add(:field_1, "You do not have permission to add logs for this owning organisation", category: :setup) + errors.add(:field_1, "You do not have permission to add logs for this owning organisation.", category: :setup) end end end @@ -1197,7 +1197,7 @@ private return if field_2.blank? unless assigned_to - errors.add(:field_2, "User with the specified email could not be found") + errors.add(:field_2, "User with the specified email could not be found.") end end @@ -1207,7 +1207,7 @@ private return if assigned_to.organisation == owning_organisation&.absorbing_organisation || assigned_to.organisation == managing_organisation&.absorbing_organisation block_log_creation! - errors.add(:field_2, "User must be related to owning organisation or managing organisation", category: :setup) + errors.add(:field_2, "User must be related to owning organisation or managing organisation.", category: :setup) end def managing_organisation @@ -1221,7 +1221,7 @@ private block_log_creation! if errors[:field_2].blank? - errors.add(:field_2, "This user belongs to an organisation that does not have a relationship with the owning organisation", category: :setup) + errors.add(:field_2, "This user belongs to an organisation that does not have a relationship with the owning organisation.", category: :setup) end end end @@ -1296,7 +1296,7 @@ private def validate_if_log_already_exists if log_already_exists? - error_message = "This is a duplicate log" + error_message = "This is a duplicate log." errors.add(:field_1, error_message) # Owning org errors.add(:field_3, error_message) # Sale completion date @@ -1321,7 +1321,7 @@ private field_mapping_for_errors[interruption_screen_question_id.to_sym]&.each do |field| if errors.none? { |e| e.options[:category] == :soft_validation && field_mapping_for_errors[interruption_screen_question_id.to_sym].include?(e.attribute) } - error_message = [display_title_text(question.page.title_text, log), display_informative_text(question.page.informative_text, log)].reject(&:empty?).join(". ") + error_message = [display_title_text(question.page.title_text, log), display_informative_text(question.page.informative_text, log)].reject(&:empty?).join(" ") errors.add(field, message: error_message, category: :soft_validation) end end @@ -1331,7 +1331,7 @@ private def validate_buyer1_economic_status if field_35 == 9 - errors.add(:field_35, "Buyer 1 cannot be a child under 16") + errors.add(:field_35, "Buyer 1 cannot be a child under 16.") end 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 0b7a70c27..df1a06528 100644 --- a/app/services/bulk_upload/sales/year2024/row_parser.rb +++ b/app/services/bulk_upload/sales/year2024/row_parser.rb @@ -1287,7 +1287,7 @@ private block_log_creation! if field_1.present? && errors[:field_1].blank? - errors.add(:field_1, "The owning organisation code is incorrect", category: :setup) + errors.add(:field_1, "The owning organisation code is incorrect.", category: :setup) end end end @@ -1297,7 +1297,7 @@ private block_log_creation! if errors[:field_1].blank? - errors.add(:field_1, "The owning organisation code provided is for an organisation that does not own stock", category: :setup) + errors.add(:field_1, "The owning organisation code provided is for an organisation that does not own stock.", category: :setup) end end end @@ -1311,9 +1311,9 @@ private return if errors[:field_1].present? if bulk_upload.user.support? - errors.add(:field_1, "This owning organisation is not affiliated with #{bulk_upload_organisation.name}", category: :setup) + errors.add(:field_1, "This owning organisation is not affiliated with #{bulk_upload_organisation.name}.", category: :setup) else - errors.add(:field_1, "You do not have permission to add logs for this owning organisation", category: :setup) + errors.add(:field_1, "You do not have permission to add logs for this owning organisation.", category: :setup) end end @@ -1321,7 +1321,7 @@ private return if field_3.blank? unless assigned_to - errors.add(:field_3, "User with the specified email could not be found") + errors.add(:field_3, "User with the specified email could not be found.") end end @@ -1337,7 +1337,7 @@ private return if assigned_to.organisation == owning_organisation&.absorbing_organisation || assigned_to.organisation == managing_organisation&.absorbing_organisation block_log_creation! - errors.add(:field_3, "User must be related to owning organisation or managing organisation", category: :setup) + errors.add(:field_3, "User must be related to owning organisation or managing organisation.", category: :setup) end def managing_organisation @@ -1357,7 +1357,7 @@ private block_log_creation! if errors[:field_2].blank? - errors.add(:field_2, "This organisation does not have a relationship with the owning organisation", category: :setup) + errors.add(:field_2, "This organisation does not have a relationship with the owning organisation.", category: :setup) end end end @@ -1432,7 +1432,7 @@ private def validate_if_log_already_exists if log_already_exists? - error_message = "This is a duplicate log" + error_message = "This is a duplicate log." errors.add(:field_1, error_message) # Owning org errors.add(:field_4, error_message) # Sale completion date @@ -1457,7 +1457,7 @@ private field_mapping_for_errors[interruption_screen_question_id.to_sym]&.each do |field| if errors.none? { |e| e.options[:category] == :soft_validation && field_mapping_for_errors[interruption_screen_question_id.to_sym].include?(e.attribute) } - error_message = [display_title_text(question.page.title_text, log), display_informative_text(question.page.informative_text, log)].reject(&:empty?).join(". ") + error_message = [display_title_text(question.page.title_text, log), display_informative_text(question.page.informative_text, log)].reject(&:empty?).join(" ") errors.add(field, message: error_message, category: :soft_validation) end end diff --git a/config/locales/en.yml b/config/locales/en.yml index c666746da..50a821ce0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -33,13 +33,13 @@ en: service_name: "Submit social housing lettings and sales data (CORE)" feedback_form: "https://forms.office.com/Pages/ResponsePage.aspx?id=EGg0v32c3kOociSi7zmVqC4YDsCJ3llAvEZelBFBLUBURFVUTzFDTUJPQlM4M0laTE5DTlNFSjJBQi4u" organisation: - created: "%{organisation} was created" - updated: "Organisation details updated" + created: "%{organisation} was created." + updated: "Organisation details updated." reactivated: "%{organisation} has been reactivated." deactivated: "%{organisation} has been deactivated." user: - create_password: "Create a password to finish setting up your account" - reset_password: "Reset your password" + create_password: "Create a password to finish setting up your account." + reset_password: "Reset your password." active_notifications: zero: "There are no active notifications" @@ -50,8 +50,8 @@ en: errors: models: bulk_upload/row_parser: &bulk_upload__row_parser__base - inclusion: Enter a valid value for %{question} - spreadsheet_dupe: This is a duplicate of a log in your file + inclusion: "Enter a valid value for %{question}" + spreadsheet_dupe: "This is a duplicate of a log in your file." bulk_upload/lettings/year2024/row_parser: <<: *bulk_upload__row_parser__base bulk_upload/lettings/year2023/row_parser: @@ -63,118 +63,118 @@ en: bulk_upload/lettings/validator: attributes: base: - blank_file: Template is blank - The template must be filled in for us to create the logs and check if data is correct. - wrong_field_numbers_count: "Incorrect number of fields, please ensure you have used the correct template" - over_max_column_count: "Too many columns, please ensure you have used the correct template" - wrong_template: "Incorrect start dates, please ensure you have used the correct template" + blank_file: "Template is blank - The template must be filled in for us to create the logs and check if data is correct." + wrong_field_numbers_count: "Incorrect number of fields, please ensure you have used the correct template." + over_max_column_count: "Too many columns, please ensure you have used the correct template." + wrong_template: "Incorrect start dates, please ensure you have used the correct template." no_headers: "Your file does not contain the required header rows. Add or check the header rows and upload your file again. [Read more about using the template headers](%{guidance_link})." bulk_upload/sales/validator: attributes: base: - blank_file: Template is blank - The template must be filled in for us to create the logs and check if data is correct. - wrong_field_numbers_count: "Incorrect number of fields, please ensure you have used the correct template" - over_max_column_count: "Too many columns, please ensure you have used the correct template" - wrong_template: "Incorrect sale dates, please ensure you have used the correct template" + blank_file: "Template is blank - The template must be filled in for us to create the logs and check if data is correct." + wrong_field_numbers_count: "Incorrect number of fields, please ensure you have used the correct template." + over_max_column_count: "Too many columns, please ensure you have used the correct template." + wrong_template: "Incorrect sale dates, please ensure you have used the correct template." no_headers: "Your file does not contain the required header rows. Add or check the header rows and upload your file again. [Read more about using the template headers](%{guidance_link})." forms/bulk_upload_lettings/year: attributes: year: - blank: You must select a collection period to upload for + blank: "You must select a collection period to upload for." forms/bulk_upload_sales/year: attributes: year: - blank: You must select a collection period to upload for + blank: "You must select a collection period to upload for." forms/bulk_upload_lettings/upload_your_file: attributes: file: - blank: Select which file to upload - not_csv: Your file must be in CSV format + blank: "Select which file to upload." + not_csv: "Your file must be in CSV format." forms/bulk_upload_sales/upload_your_file: attributes: file: - blank: Select which file to upload - not_csv: Your file must be in CSV format + blank: "Select which file to upload." + not_csv: "Your file must be in CSV format." forms/bulk_upload_lettings/needstype: attributes: needstype: - blank: You must answer needs type + blank: "You must answer needs type." forms/bulk_upload_lettings_resume/fix_choice: attributes: choice: - blank: Select how you would like to fix these errors - inclusion: You must select one of the following options for how you would like to fix these errors + blank: "Select how you would like to fix these errors." + inclusion: "You must select one of the following options for how you would like to fix these errors." forms/bulk_upload_sales_resume/fix_choice: attributes: choice: - blank: Select how you would like to fix these errors - inclusion: You must select one of the following options for how you would like to fix these errors + blank: "Select how you would like to fix these errors." + inclusion: "You must select one of the following options for how you would like to fix these errors." forms/bulk_upload_lettings_soft_validations_check/confirm_soft_errors: attributes: confirm_soft_errors: - blank: You must select if there are errors in these fields + blank: "You must select if there are errors in these fields." forms/bulk_upload_sales_soft_validations_check/confirm_soft_errors: attributes: confirm_soft_errors: - blank: You must select if there are errors in these fields + blank: "You must select if there are errors in these fields." activerecord: attributes: user: - email: email + email: "email" errors: models: scheme: attributes: owning_organisation_id: - invalid: "Enter the name of the organisation that owns the housing stock" + invalid: "Enter the name of the organisation that owns the housing stock." service_name: - invalid: "Enter the name of the scheme" + invalid: "Enter the name of the scheme." scheme_type: - invalid: "Select the type of scheme" + invalid: "Select the type of scheme." registered_under_care_act: - invalid: "Select if this scheme is registered under the Care Standards Act 2000" + invalid: "Select if this scheme is registered under the Care Standards Act 2000." primary_client_group: - invalid: "Select what client group this scheme is intended for" + invalid: "Select what client group this scheme is intended for." secondary_client_group: - invalid: "Select the other client group for this scheme" + invalid: "Select the other client group for this scheme." support_type: - invalid: "Select the level of support provided by this scheme" + invalid: "Select the level of support provided by this scheme." intended_stay: - invalid: "Select the intended length of stay" + invalid: "Select the intended length of stay." has_other_client_group: - invalid: "Select if this scheme provides for another client group" + invalid: "Select if this scheme provides for another client group." arrangement_type: - invalid: "Select who provides the support services used by this scheme" + invalid: "Select who provides the support services used by this scheme." location: attributes: startdate: - invalid: "Enter a date in the correct format, for example 31 1 2022" + invalid: "Enter a date in the correct format, for example 31 1 2022." units: - blank: "Enter the total number of units at this location" + blank: "Enter the total number of units at this location." type_of_unit: - blank: "Select the most common type of unit at this location" + blank: "Select the most common type of unit at this location." mobility_type: - blank: "Select the mobility standards for the majority of units in this location" + blank: "Select the mobility standards for the majority of units in this location." user: attributes: organisation_id: - blank: "Select the user’s organisation" - invalid: "Select the user’s organisation" + blank: "Select the user’s organisation." + invalid: "Select the user’s organisation." name: - blank: "Enter a name" + blank: "Enter a name." email: - invalid: "Enter an email address in the correct format, like name@example.com" - blank: "Enter an email address" - taken: "Enter an email address that hasn’t already been used to sign up" + invalid: "Enter an email address in the correct format, like name@example.com." + blank: "Enter an email address." + taken: "Enter an email address that hasn’t already been used to sign up." phone: - invalid: "Enter a telephone number in the correct format" - blank: "Enter a telephone number" + invalid: "Enter a telephone number in the correct format." + blank: "Enter a telephone number." role: - invalid: "Role must be data accessor, data provider or data coordinator" - blank: "Select role" + invalid: "Role must be data accessor, data provider or data coordinator." + blank: "Select role." password: - blank: Enter a password - too_short: The password you entered is too short. Enter a password that is %{count} characters or longer. + blank: "Enter a password." + too_short: "The password you entered is too short. Enter a password that is %{count} characters or longer." reset_password_token: invalid: "That link is invalid. Check you are using the correct link." log_reassignment: @@ -184,20 +184,20 @@ en: merge_request: attributes: absorbing_organisation_id: - blank: "Select the absorbing organisation" + blank: "Select the absorbing organisation." merge_date: - blank: "Enter a merge date" - invalid: "Enter a valid merge date" + blank: "Enter a merge date." + invalid: "Enter a valid merge date." existing_absorbing_organisation: blank: "You must answer absorbing organisation already active?" notification: attributes: title: - blank: "Enter a title" + blank: "Enter a title." link_text: - blank_when_additional_page_set: "Enter the link text" + blank_when_additional_page_set: "Enter the link text." page_content: - blank_when_additional_page_set: "Enter the page content" + blank_when_additional_page_set: "Enter the page content." notification: logs_deleted: @@ -209,8 +209,8 @@ en: duplicate_logs: deduplication_success_banner: "%{log_link} is no longer a duplicate and has been removed from the list.

You changed the %{changed_question_label}.

" duplicate_sets: - one: "There is %{count} set of duplicate logs" - other: "There are %{count} sets of duplicate logs" + one: "There is %{count} set of duplicate logs." + other: "There are %{count} sets of duplicate logs." location_deleted: "%{postcode} has been deleted." scheme_deleted: "%{service_name} has been deleted." user_deleted: "%{name} has been deleted." @@ -218,72 +218,72 @@ en: validations: 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" - provider_type_missing: "Select the organisation type" + 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." + provider_type_missing: "Select the organisation type." stock_owner: - blank: "You must choose a stock owner" - already_added: "You have already added this stock owner" + blank: "You must choose a stock owner." + already_added: "You have already added this stock owner." does_not_own_stock: "You can only add stock owners who own stock, which this organisation does not." managing_agent: - blank: "You must choose a managing agent" - already_added: "You have already added this managing agent" + blank: "You must choose a managing agent." + already_added: "You have already added this managing agent." merged: "That organisation has already been merged. Select a different organisation." not_answered: "You must answer %{question}" invalid_option: "Enter a valid value for %{question}" invalid_number: "Enter a number for %{question}" no_address_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." - other_field_missing: "If %{main_field_label} is other then %{other_field_label} must be provided" - other_field_not_required: "%{other_field_label} must not be provided if %{main_field_label} was not other" + other_field_missing: "If %{main_field_label} is other then %{other_field_label} must be provided." + other_field_not_required: "%{other_field_label} must not be provided if %{main_field_label} was not other." numeric: - within_range: "%{field} must be between %{min} and %{max}" - above_min: "%{field} must be at least %{min}" - whole_number: "%{field} must be a whole number" - nearest_ten: "%{field} must be given to the nearest ten" - nearest_hundredth: "%{field} must be given to the nearest hundredth" - normal_format: "Enter a number" - format: "%{field} must be a number" + within_range: "%{field} must be between %{min} and %{max}." + above_min: "%{field} must be at least %{min}." + whole_number: "%{field} must be a whole number." + nearest_ten: "%{field} must be given to the nearest ten." + nearest_hundredth: "%{field} must be given to the nearest hundredth." + normal_format: "Enter a number." + format: "%{field} must be a number." date: - invalid_date: "Enter a date in the correct format, for example 31 1 2022" - outside_collection_window: Enter a date within the %{year_combo} collection year, which is between 1st April %{start_year} and 31st March %{end_year} - postcode: "Enter a postcode in the correct format, for example AA1 1AA" - location_admin_district: "Select a local authority" + invalid_date: "Enter a date in the correct format, for example 31 1 2024." + outside_collection_window: "Enter a date within the %{year_combo} collection year, which is between 1st April %{start_year} and 31st March %{end_year}." + postcode: "Enter a postcode in the correct format, for example AA1 1AA." + location_admin_district: "Select a local authority." email: - taken: "Enter an email address that hasn’t already been used to sign up" - invalid: "Enter an email address in the correct format, like name@example.com" - blank: "Enter an email address" + taken: "Enter an email address that hasn’t already been used to sign up." + invalid: "Enter an email address in the correct format, like name@example.com." + blank: "Enter an email address." role: - invalid: "Role must be data accessor, data provider or data coordinator" + invalid: "Role must be data accessor, data provider or data coordinator." setup: intermediate_rent_product_name: - blank: "Enter name of other intermediate rent product" + blank: "Enter name of other intermediate rent product." saledate: - later_than_14_days_after: "Sale completion date must not be later than 14 days from today’s date" + later_than_14_days_after: "Sale completion date must not be later than 14 days from today’s date." current_collection_year: - Enter a date within the %{current_start_year_short}/%{current_end_year_short} collection year, which is between %{current_start_year_long} and %{current_end_year_long} + "Enter a date within the %{current_start_year_short}/%{current_end_year_short} collection year, which is between %{current_start_year_long} and %{current_end_year_long}." previous_and_current_collection_year: - "Enter a date within the %{previous_start_year_short}/%{previous_end_year_short} or %{previous_end_year_short}/%{current_end_year_short} collection years, which is between %{previous_start_year_long} and %{current_end_year_long}" - year_not_two_digits: "Sale completion year must be 2 digits" + "Enter a date within the %{previous_start_year_short}/%{previous_end_year_short} or %{previous_end_year_short}/%{current_end_year_short} collection years, which is between %{previous_start_year_long} and %{current_end_year_long}." + year_not_two_digits: "Sale completion year must be 2 digits." invalid_merged_organisations_saledate: "Enter a date when the owning organisation was active. %{owning_organisation} became inactive on %{owning_organisation_merge_date} and was replaced by %{owning_absorbing_organisation}." invalid_absorbing_organisations_saledate: "Enter a date when the owning organisation was active. %{owning_organisation} became active on %{owning_organisation_available_from}." type: - percentage_bought_must_be_at_least_threshold: "The minimum increase in equity while staircasing is %{threshold}% for this shared ownership type" + percentage_bought_must_be_at_least_threshold: "The minimum increase in equity while staircasing is %{threshold}% for this shared ownership type." startdate: current_collection_year: - Enter a date within the %{current_start_year_short}/%{current_end_year_short} collection year, which is between %{current_start_year_long} and %{current_end_year_long} + "Enter a date within the %{current_start_year_short}/%{current_end_year_short} collection year, which is between %{current_start_year_long} and %{current_end_year_long}." previous_and_current_collection_year: - "Enter a date within the %{previous_start_year_short}/%{previous_end_year_short} or %{previous_end_year_short}/%{current_end_year_short} collection years, which is between %{previous_start_year_long} and %{current_end_year_long}" - later_than_14_days_after: "The tenancy start date must not be later than 14 days from today’s date" - before_scheme_end_date: "The tenancy start date must be before the end date for this supported housing scheme" - after_void_date: "Enter a tenancy start date that is after the void date" - after_major_repair_date: "Enter a tenancy start date that is after the major repair date" - year_not_two_digits: Tenancy start year must be 2 digits - ten_years_after_void_date: "Enter a tenancy start date that is no more than 10 years after the void date" - ten_years_after_mrc_date: "Enter a tenancy start date that is no more than 10 years after the major repairs completion date" + "Enter a date within the %{previous_start_year_short}/%{previous_end_year_short} or %{previous_end_year_short}/%{current_end_year_short} collection years, which is between %{previous_start_year_long} and %{current_end_year_long}." + later_than_14_days_after: "The tenancy start date must not be later than 14 days from today’s date." + before_scheme_end_date: "The tenancy start date must be before the end date for this supported housing scheme." + after_void_date: "Enter a tenancy start date that is after the void date." + after_major_repair_date: "Enter a tenancy start date that is after the major repair date." + year_not_two_digits: "Tenancy start year must be 2 digits." + ten_years_after_void_date: "Enter a tenancy start date that is no more than 10 years after the void date." + ten_years_after_mrc_date: "Enter a tenancy start date that is no more than 10 years after the major repairs completion date." invalid_merged_organisations_start_date: same_organisation: "Enter a date when the owning and managing organisation was active. %{owning_organisation} became inactive on %{owning_organisation_merge_date} and was replaced by %{owning_absorbing_organisation}." same_merge: "Enter a date when the owning and managing organisations were active. %{owning_organisation} and %{managing_organisation} became inactive on %{owning_organisation_merge_date} and were replaced by %{owning_absorbing_organisation}." @@ -301,110 +301,110 @@ en: startdate: "The location %{postcode} is inactive on this date. Enter another date or choose another location." location_id: "This location is not active on the tenancy start date. Choose another location or edit the tenancy start date." activating_soon: - startdate: "The location %{postcode} is not available until %{date}. Enter a tenancy start date after %{date}" - location_id: "The location %{postcode} is not available until %{date}. Select another location or edit the tenancy start date" + startdate: "The location %{postcode} is not available until %{date}. Enter a tenancy start date after %{date}." + location_id: "The location %{postcode} is not available until %{date}. Select another location or edit the tenancy start date." reactivating_soon: - startdate: "The location %{postcode} is not available until %{date}. Enter a tenancy start date after %{date}" - location_id: "The location %{postcode} is not available until %{date}. Select another location or edit the tenancy start date" + startdate: "The location %{postcode} is not available until %{date}. Enter a tenancy start date after %{date}." + location_id: "The location %{postcode} is not available until %{date}. Select another location or edit the tenancy start date." scheme: deactivated: - startdate: "The scheme %{name} was deactivated on %{date} and was not available on the day you entered. Select another scheme or edit the tenancy start date" - scheme_id: "The scheme %{name} was deactivated on %{date} and was not available on the day you entered. Select another scheme or edit the tenancy start date" + startdate: "The scheme %{name} was deactivated on %{date} and was not available on the day you entered. Select another scheme or edit the tenancy start date." + scheme_id: "The scheme %{name} was deactivated on %{date} and was not available on the day you entered. Select another scheme or edit the tenancy start date." reactivating_soon: - startdate: "The scheme %{name} is not available until %{date}. Enter a tenancy start date after %{date}" - scheme_id: "The scheme %{name} is not available until %{date}. Select another scheme or edit the tenancy start date" + startdate: "The scheme %{name} is not available until %{date}. Enter a tenancy start date after %{date}." + scheme_id: "The scheme %{name} is not available until %{date}. Select another scheme or edit the tenancy start date." locations_inactive: startdate: "The scheme %{name} has no locations that are active on this date. Enter another date or choose another scheme." scheme_id: "The scheme %{name} has no locations that are active on this date. Enter another date or choose another scheme." owning_organisation: - invalid: "Please select the owning organisation or managing organisation that you belong to" + invalid: "Please select the owning organisation or managing organisation that you belong to." data_sharing_agreement_not_signed: "The organisation must accept the Data Sharing Agreement before it can be selected as the owning organisation." inactive_merged_organisation: "The owning organisation must be active on the tenancy start date. %{owning_organisation} became inactive on %{owning_organisation_merge_date} and was replaced by %{owning_absorbing_organisation}." inactive_absorbing_organisation: "The owning organisation must be active on the tenancy start date. %{owning_organisation} became active on %{owning_organisation_available_from}." inactive_merged_organisation_sales: "The owning organisation must be active on the sale completion date. %{owning_organisation} became inactive on %{owning_organisation_merge_date} and was replaced by %{owning_absorbing_organisation}." inactive_absorbing_organisation_sales: "The owning organisation must be active on the sale completion date. %{owning_organisation} became active on %{owning_organisation_available_from}." managing_organisation: - invalid: "Please select the owning organisation or managing organisation that you belong to" + invalid: "Please select the owning organisation or managing organisation that you belong to." data_sharing_agreement_not_signed: "The organisation must accept the Data Sharing Agreement before it can be selected as the managing organisation." inactive_merged_organisation: "The managing organisation must be active on the tenancy start date. %{managing_organisation} became inactive on %{managing_organisation_merge_date} and was replaced by %{managing_absorbing_organisation}." inactive_absorbing_organisation: "The managing organisation must be active on the tenancy start date. %{managing_organisation} became active on %{managing_organisation_available_from}." assigned_to: - invalid: "Please select the owning organisation or managing organisation that you belong to" + invalid: "Please select the owning organisation or managing organisation that you belong to." lettype: - general_needs_mismatch: Lettings type must be a general needs type because you selected general needs when uploading the file - supported_housing_mismatch: Lettings type must be a supported housing type because you selected supported housing when uploading the file - needstype_general_needs: This needs type is general needs, but the letting type is supported housing. Change either the needs type or the letting type. - needstype_supported_housing: This needs type is supported housing, but the letting type is general needs. Change either the needs type or the letting type. + general_needs_mismatch: "Lettings type must be a general needs type because you selected general needs when uploading the file." + supported_housing_mismatch: "Lettings type must be a supported housing type because you selected supported housing when uploading the file." + needstype_general_needs: "This needs type is general needs, but the letting type is supported housing. Change either the needs type or the letting type." + needstype_supported_housing: "This needs type is supported housing, but the letting type is general needs. Change either the needs type or the letting type." needstype: - lettype_not_general_needs: This letting type is supported housing, but the needs type is general needs. Change either the needs type or the letting type. - lettype_not_supported_housing: This letting type is general needs, but the needs type is supported housing. Change either the needs type or the letting type. + lettype_not_general_needs: "This letting type is supported housing, but the needs type is general needs. Change either the needs type or the letting type." + lettype_not_supported_housing: "This letting type is general needs, but the needs type is supported housing. Change either the needs type or the letting type." location: - incomplete: "This location is incomplete. Select another location or update this one" + incomplete: "This location is incomplete. Select another location or update this one." scheme: - incomplete: "This scheme is incomplete. Select another scheme or update this one" + incomplete: "This scheme is incomplete. Select another scheme or update this one." property: uprn: - invalid: "UPRN must be 12 digits or less" + invalid: "UPRN must be 12 digits or less." uprn_known: invalid: "You must answer UPRN known?" mrcdate: - before_tenancy_start: "Enter a major repairs date that is before the tenancy start date" - not_first_let: "Major repairs date must not be completed if the tenancy is a first let" - ten_years_before_tenancy_start: "Enter a major repairs completion date that is no more than 10 years before the tenancy start date" - before_void_date: "Major repairs date must be after the void date if provided" + before_tenancy_start: "Enter a major repairs date that is before the tenancy start date." + not_first_let: "Major repairs date must not be completed if the tenancy is a first let." + ten_years_before_tenancy_start: "Enter a major repairs completion date that is no more than 10 years before the tenancy start date." + before_void_date: "Major repairs date must be after the void date if provided." void_date: - ten_years_before_tenancy_start: "Enter a void date no more than 10 years before the tenancy start date" - before_tenancy_start: "Enter a void date that is before the tenancy start date" - after_mrcdate: "Void date must be before the major repairs date if provided" + ten_years_before_tenancy_start: "Enter a void date no more than 10 years before the tenancy start date." + before_tenancy_start: "Enter a void date that is before the tenancy start date." + after_mrcdate: "Void date must be before the major repairs date if provided." la: - la_invalid_for_org: "%{org_name} does not operate in %{la_name}" - postcode_invalid_for_org: "Enter a postcode in an area covered by %{org_name}" + la_invalid_for_org: "%{org_name} does not operate in %{la_name}." + postcode_invalid_for_org: "Enter a postcode in an area covered by %{org_name}." rsnvac: - first_let_not_social: "Enter a reason for vacancy that is not 'first let' if unit has been previously let as social housing" - first_let_social: "Reason for vacancy must be first let if unit has been previously let as social housing" - previous_let_social: "Property cannot have a previous let type if being let as social housing for the first time" - non_temp_accommodation: "Answer cannot be re-let to tenant who occupied the same property as temporary accommodation as this accommodation is not temporary" - referral_invalid: "Answer cannot be re-let to tenant who occupied the same property as temporary accommodation as a different source of referral for this letting" - not_a_renewal: "Reason for vacancy cannot be 'Renewal of fixed-term tenancy' if letting is not a renewal" + first_let_not_social: "Enter a reason for vacancy that is not 'first let' if unit has been previously let as social housing." + first_let_social: "Reason for vacancy must be first let if unit has been previously let as social housing." + previous_let_social: "Property cannot have a previous let type if being let as social housing for the first time." + non_temp_accommodation: "Answer cannot be re-let to tenant who occupied the same property as temporary accommodation as this accommodation is not temporary." + referral_invalid: "Answer cannot be re-let to tenant who occupied the same property as temporary accommodation as a different source of referral for this letting." + not_a_renewal: "Reason for vacancy cannot be 'Renewal of fixed-term tenancy' if letting is not a renewal." unittype_gn: - one_bedroom_bedsit: "A bedsit can only have one bedroom" - one_seven_bedroom_shared: "A shared house must have 1 to 7 bedrooms" - one_three_bedroom_single_tenant_shared: "A shared house with fewer than two tenants must have 1 to 3 bedrooms" + one_bedroom_bedsit: "A bedsit can only have one bedroom." + one_seven_bedroom_shared: "A shared house must have 1 to 7 bedrooms." + one_three_bedroom_single_tenant_shared: "A shared house with fewer than two tenants must have 1 to 3 bedrooms." beds: - bedsits_have_max_one_bedroom: "Number of bedrooms must be 1 if the property is a bedsit" + bedsits_have_max_one_bedroom: "Number of bedrooms must be 1 if the property is a bedsit." proptype: - bedsits_have_max_one_bedroom: "Answer cannot be 'Bedsit' if the property has 2 or more bedrooms" + bedsits_have_max_one_bedroom: "Answer cannot be 'Bedsit' if the property has 2 or more bedrooms." postcode: - must_match_previous: "%{buyer_possessive} last accommodation and discounted ownership postcodes must match" + must_match_previous: "%{buyer_possessive} last accommodation and discounted ownership postcodes must match." financial: tshortfall: - outstanding_amount_not_expected: "You cannot answer the outstanding amount question if you don’t have outstanding rent or charges" - more_than_total_charge: "Enter a value less than the total charge" - must_be_positive: "Enter a value over £0.01 as you told us there is an outstanding amount" + outstanding_amount_not_expected: "You cannot answer the outstanding amount question if you don’t have outstanding rent or charges." + more_than_total_charge: "Enter a value less than the total charge." + must_be_positive: "Enter a value over £0.01 as you told us there is an outstanding amount." hbrentshortfall: - outstanding_amount_not_expected: "Answer must be ‘yes’ as you have answered the outstanding amount question" - outstanding_no_benefits: "Answer cannot be ‘yes’ to outstanding amount for basic rent or charges if tenant does not receive housing benefit or Universal Credit or you‘re not sure" + outstanding_amount_not_expected: "Answer must be ‘yes’ as you have answered the outstanding amount question." + outstanding_no_benefits: "Answer cannot be ‘yes’ to outstanding amount for basic rent or charges if tenant does not receive housing benefit or Universal Credit or you‘re not sure." benefits: - part_or_full_time: "Answer cannot be ‘all’ for income from Universal Credit, state pensions or benefits if the tenant or their partner works part-time or full-time" + part_or_full_time: "Answer cannot be ‘all’ for income from Universal Credit, state pensions or benefits if the tenant or their partner works part-time or full-time." earnings: - over_hard_max: "The household’s income cannot be greater than %{hard_max} per week given the household’s working situation" - under_hard_min: "The household’s income cannot be less than %{hard_min} per week given the household’s working situation" - freq_missing: "Select how often the household receives income" - earnings_missing: "Enter how much income the household has in total" + over_hard_max: "The household’s income cannot be greater than %{hard_max} per week given the household’s working situation." + under_hard_min: "The household’s income cannot be less than %{hard_min} per week given the household’s working situation." + freq_missing: "Select how often the household receives income." + earnings_missing: "Enter how much income the household has in total." income: - outside_london_income_range: "Income must be between £0 and £90,000 for properties within a London local authority" - outside_non_london_income_range: "Income must be between £0 and £80,000 for properties in a non-London local authority" - combined_over_hard_max_for_london: "Combined income must be £90,000 or lower for properties within a London local authority" - combined_over_hard_max_for_outside_london: "Combined income must be £80,000 or lower for properties outside London local authorities" - child_has_income: "Child's income must be £0" - negative_currency: "Enter an amount above 0" + outside_london_income_range: "Income must be between £0 and £90,000 for properties within a London local authority." + outside_non_london_income_range: "Income must be between £0 and £80,000 for properties in a non-London local authority." + combined_over_hard_max_for_london: "Combined income must be £90,000 or lower for properties within a London local authority." + combined_over_hard_max_for_outside_london: "Combined income must be £80,000 or lower for properties outside London local authorities." + child_has_income: "Child's income must be £0." + negative_currency: "Enter an amount above 0." rent: out_of_range: "Enter a value for the %{charge_name} between £0 and %{maximum_per_period} paid %{frequency}. %{maximum_per_period} is the max limit for rent and charges paid %{frequency} for %{letting_type} lettings owned by a %{provider_type}." ecstat: - over_hard_max: "The household’s income of %{earnings} %{frequency} is too high given the household’s working situation" - under_hard_min: "The household’s income of %{earnings} %{frequency} is too low given the household’s working situation" + over_hard_max: "The household’s income of %{earnings} %{frequency} is too high given the household’s working situation." + under_hard_min: "The household’s income of %{earnings} %{frequency} is too low given the household’s working situation." age: earnings_over_hard_max: "The household’s income of %{earnings} %{frequency} is too high for the number of adults. Change either the household income or the age of the tenants." hhmemb: @@ -412,159 +412,158 @@ en: over_hard_max: "The household’s income of %{earnings} %{frequency} is too high for this number of tenants. Change either the household income or number of tenants." under_hard_min: "The household’s income of %{earnings} %{frequency} is too low for this number of tenants. Change either the household income or number of tenants." brent: - below_hard_min: "Rent is below the absolute minimum expected for a property of this type. Please check the rent, rent period, local authority and (if general needs) number of bedrooms" - above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type. Please check the rent, rent period, local authority and (if general needs) number of bedrooms" + below_hard_min: "Rent is below the absolute minimum expected for a property of this type. Please check the rent, rent period, local authority and (if general needs) number of bedrooms." + above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type. Please check the rent, rent period, local authority and (if general needs) number of bedrooms." scheme_id: - below_hard_min: "Rent is below the absolute minimum expected for a property of this type. Please check the rent, rent period and local authority" - above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type. Please check the rent, rent period and local authority" + below_hard_min: "Rent is below the absolute minimum expected for a property of this type. Please check the rent, rent period and local authority." + above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type. Please check the rent, rent period and local authority." location_id: - below_hard_min: "Rent is below the absolute minimum expected for a property of this type. Please check the rent, rent period and local authority" - above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type. Please check the rent, rent period and local authority" + below_hard_min: "Rent is below the absolute minimum expected for a property of this type. Please check the rent, rent period and local authority." + above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type. Please check the rent, rent period and local authority." postcode_known: - below_hard_min: "Rent is below the absolute minimum expected for a property of this type. Please check the rent, rent period, local authority and number of bedrooms" - above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type. Please check the rent, rent period, local authority and number of bedrooms" + below_hard_min: "Rent is below the absolute minimum expected for a property of this type. Please check the rent, rent period, local authority and number of bedrooms." + above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type. Please check the rent, rent period, local authority and number of bedrooms." uprn: - below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this UPRN" - above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this UPRN" + below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this UPRN." + above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this UPRN." la: - below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this local authority" - above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this local authority" + below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this local authority." + above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this local authority." beds: - below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this number of bedrooms" - above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this number of bedrooms" + below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this number of bedrooms." + above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this number of bedrooms." needstype: - below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this lettings type" - above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this lettings type" + below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this lettings type." + above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this lettings type." rent_type: - below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this lettings type" - above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this lettings type" + below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this lettings type." + above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this lettings type." period: - below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this period" - above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this period" + below_hard_min: "Rent is below the absolute minimum expected for a property of this type based on this period." + above_hard_max: "Rent is higher than the absolute maximum expected for a property of this type based on this period." charges: complete_1_of_3: "Answer either the ‘household rent and charges’ question or ‘is this accommodation a care home‘, or select ‘no’ for ‘does the household pay rent or charges for the accommodation?’" missing_charges: "Please enter the %{question}. If there is no %{question}, please enter '0'." tcharge: - under_10: "Enter a total charge that is at least £10.00 per week" - less_than_shortfall: "The total charge must be more than the outstanding amount" + under_10: "Enter a total charge that is at least £10.00 per week." + less_than_shortfall: "The total charge must be more than the outstanding amount." rent_period: invalid_for_org: - period: "%{org_name} does not use %{rent_period} as a rent period. Choose another rent period, or a data coordinator can add rent periods to your organisation" - managing_org: "%{org_name} does not use %{rent_period} as a rent period. Set another rent period on this log, or a data coordinator can add rent periods to this organisation" + period: "%{org_name} does not use %{rent_period} as a rent period. Choose another rent period, or a data coordinator can add rent periods to your organisation." + managing_org: "%{org_name} does not use %{rent_period} as a rent period. Set another rent period on this log, or a data coordinator can add rent periods to this organisation." carehome: - out_of_range: "Household rent and other charges must be between %{min_chcharge} and %{max_chcharge} if paying %{period}" - not_provided: "Enter how much rent and other charges the household pays %{period}" - cash_discount_invalid: "Cash discount must be £0 - £999,999" + out_of_range: "Household rent and other charges must be between %{min_chcharge} and %{max_chcharge} if paying %{period}." + not_provided: "Enter how much rent and other charges the household pays %{period}." + cash_discount_invalid: "Cash discount must be £0 - £999,999." staircasing: - percentage_bought_must_be_greater_than_percentage_owned: "Total percentage %{buyer_now_owns} must be more than percentage bought in this transaction" - percentage_bought_must_be_at_least_threshold: "The minimum increase in equity while staircasing is %{threshold}%" + percentage_bought_must_be_greater_than_percentage_owned: "Total percentage %{buyer_now_owns} must be more than percentage bought in this transaction." + percentage_bought_must_be_at_least_threshold: "The minimum increase in equity while staircasing is %{threshold}%." percentage_bought_equal_percentage_owned: "The percentage bought is %{stairbought}% and the percentage owned in total is %{stairowned}%. These figures cannot be the same." monthly_leasehold_charges: - not_zero: "Monthly leasehold charges cannot be £0 if the property has monthly charges" + not_zero: "Monthly leasehold charges cannot be £0 if the property has monthly charges." equity: - under_min: "The minimum initial equity stake for this type of shared ownership sale is %{min_equity}%" - over_max: "The maximum initial equity stake is %{max_equity}%" + under_min: "The minimum initial equity stake for this type of shared ownership sale is %{min_equity}%." + over_max: "The maximum initial equity stake is %{max_equity}%." over_stairowned_minus_stairbought: "The initial equity stake is %{equity}% and the percentage owned in total minus the percentage bought is %{staircase_difference}%. In a staircasing transaction, the equity stake purchased cannot be larger than the percentage the %{buyer_owns} minus the percentage bought." - mortgage: "Mortgage value cannot be £0 if a mortgage was used for the purchase of this property" + mortgage: "Mortgage value cannot be £0 if a mortgage was used for the purchase of this property." mortgage_used: year: "You must answer either ‘yes’ or ‘no’ to the question ‘was a mortgage used’ for the selected year." staircasing: "You must answer either ‘yes’ or ‘no’ to the question ‘was a mortgage used’ for staircasing transactions." - shared_ownership_deposit: "The %{mortgage_deposit_and_discount_error_fields} added together is %{mortgage_deposit_and_discount_total}. The value times the equity percentage is %{value_times_equity}. These figures should be the same" + shared_ownership_deposit: "The %{mortgage_deposit_and_discount_error_fields} added together is %{mortgage_deposit_and_discount_total}. The value times the equity percentage is %{value_times_equity}. These figures should be the same." household: reasonable_preference_reason: - reason_required: "Enter a reason if you've answered 'yes' to reasonable preference" - reason_not_required: "Do not enter a reason if you've answered 'no' to reasonable preference" + reason_required: "Enter a reason if you've answered 'yes' to reasonable preference." + reason_not_required: "Do not enter a reason if you've answered 'no' to reasonable preference." underoccupation_benefitcap: - dont_know_required: "Answer must be ‘don’t know’ as you told us you don’t know the tenant’s main reason for leaving" + dont_know_required: "Answer must be ‘don’t know’ as you told us you don’t know the tenant’s main reason for leaving." reservist: - injury_required: "Tell us whether the person was seriously injured or ill as a result of serving in the UK armed forces" - injury_not_required: "You cannot answer this question as you told us the person has not served in the UK armed forces or prefers not to say" + injury_required: "Tell us whether the person was seriously injured or ill as a result of serving in the UK armed forces." + injury_not_required: "You cannot answer this question as you told us the person has not served in the UK armed forces or prefers not to say." leftreg: - question_required: "Tell us whether the person is still serving in the UK armed forces as you told us they’re a current or former regular" - question_not_required: "You cannot answer whether the person is still serving in the UK armed forces as you told us they’re not a current or former regular" + question_required: "Tell us whether the person is still serving in the UK armed forces as you told us they’re a current or former regular." + question_not_required: "You cannot answer whether the person is still serving in the UK armed forces as you told us they’re not a current or former regular." age: - retired_male: "A male tenant who is retired must be 65 or over" - retired_female: "A female tenant who is retired must be 60 or over" - retired_over_70: "Answer cannot be over 70 as person %{person_num} has economic status that is not ‘retired’" - child_under_16_relat_lettings: "Answer cannot be under 16 as person %{person_num}'s relationship to the lead tenant is ‘partner’" - child_under_16_relat_sales: "Answer cannot be under 16 as person %{person_num}'s relationship to buyer 1 is ‘partner’" - child_under_16_ecstat: "Answer cannot be under 16 as person %{person_num}’s working situation is not ‘child under 16’, ‘other’ or ‘prefers not to say’" - child_over_16: "Answer cannot be over 16 as person’s %{person_num} working situation is ‘child under 16‘" - child_over_20: "Answer cannot be 20 or over as the relationship is ‘child’" - child_12_years_younger: "A child must be at least 12 years younger than their parent" - not_student_16_19: "Answer cannot be between 16 and 19 as person %{person_num} is a child of the lead tenant but is not a full-time student" + retired_male: "A male tenant who is retired must be 65 or over." + retired_female: "A female tenant who is retired must be 60 or over." + retired_over_70: "Answer cannot be over 70 as person %{person_num} has economic status that is not ‘retired’." + child_under_16_relat_lettings: "Answer cannot be under 16 as person %{person_num}'s relationship to the lead tenant is ‘partner’." + child_under_16_relat_sales: "Answer cannot be under 16 as person %{person_num}'s relationship to buyer 1 is ‘partner’." + child_under_16_ecstat: "Answer cannot be under 16 as person %{person_num}’s working situation is not ‘child under 16’, ‘other’ or ‘prefers not to say’." + child_over_16: "Answer cannot be over 16 as person’s %{person_num} working situation is ‘child under 16‘." + child_over_20: "Answer cannot be 20 or over as the relationship is ‘child’." + child_12_years_younger: "A child must be at least 12 years younger than their parent." + not_student_16_19: "Answer cannot be between 16 and 19 as person %{person_num} is a child of the lead tenant but is not a full-time student." student_16_19: cannot_be_16_19: - child_not_student: "Person cannot be aged 16-19 if they have relationship ‘child’ but are not a student" - must_be_16_19: "Person must be aged 16-19 if they are a student and have relationship ‘child’" + child_not_student: "Person cannot be aged 16-19 if they have relationship ‘child’ but are not a student." + must_be_16_19: "Person must be aged 16-19 if they are a student and have relationship ‘child’." lead: - over_25: "The lead tenant must be under 26 as you told us their housing situation immediately before this letting was a children’s home or foster care" + over_25: "The lead tenant must be under 26 as you told us their housing situation immediately before this letting was a children’s home or foster care." student_not_child: - cannot_be_16_19: "Person cannot be aged 16-19 if they are a student but not a child" + cannot_be_16_19: "Person cannot be aged 16-19 if they are a student but not a child." ecstat: - retired_over_70: "Person %{person_num} must be retired if over 70" - child_under_16: "Person %{person_num}’s working situation must be ‘child under 16’, ‘other’ or ‘prefers not to say’ as you told us they’re under 16" - child_over_16: "Answer cannot be ‘child under 16’ as you told us the person %{person_num} is older than 16" + retired_over_70: "Person %{person_num} must be retired if over 70." + child_under_16: "Person %{person_num}’s working situation must be ‘child under 16’, ‘other’ or ‘prefers not to say’ as you told us they’re under 16." + child_over_16: "Answer cannot be ‘child under 16’ as you told us the person %{person_num} is older than 16." not_student_16_19: "Person’s %{person_num} working situation must be full-time student or prefers not to say as you told us they’re between 16 and 19." student_16_19: cannot_be_student: - child_not_16_19: "Person cannot be a student if they are not aged 16-19 but have relationship ‘child’" - must_be_student: "Person must be a student if they are aged 16-19 and have relationship ‘child’" - retired_male: "Answer cannot be ‘retired’ as the male tenant is under 65" - retired_female: "Answer cannot be ‘retired’ as the female tenant is under 60" + child_not_16_19: "Person cannot be a student if they are not aged 16-19 but have relationship ‘child’." + must_be_student: "Person must be a student if they are aged 16-19 and have relationship ‘child’." + retired_male: "Answer cannot be ‘retired’ as the male tenant is under 65." + retired_female: "Answer cannot be ‘retired’ as the female tenant is under 60." not_child_16_19: - cannot_be_student: "Person cannot be a student if they are aged 16-19 but are not a child" - buyer_cannot_be_child: "Buyer %{buyer_index} cannot have a working situation of child under 16" - buyer_cannot_be_over_16_and_child: "Buyer %{buyer_index}'s age cannot be 16 or over if their working situation is child under 16" + cannot_be_student: "Person cannot be a student if they are aged 16-19 but are not a child." + buyer_cannot_be_child: "Buyer %{buyer_index} cannot have a working situation of child under 16." + buyer_cannot_be_over_16_and_child: "Buyer %{buyer_index}'s age cannot be 16 or over if their working situation is child under 16." relat: - child_under_16_sales: "Answer cannot be ‘partner’ as you told us person %{person_num}'s age is under 16" - child_under_16_lettings: "Answer cannot be ‘partner’ as you told us person %{person_num}'s age is under 16" - child_over_20: "Answer cannot be ‘child’ if the person's age is 20 or over" - one_partner: "Number of partners cannot be greater than 1" - not_student_16_19: "Answer cannot be ‘child’ as you told us the person %{person_num} is between 16 and 19 and is not a full-time student" + child_under_16_sales: "Answer cannot be ‘partner’ as you told us person %{person_num}'s age is under 16." + child_under_16_lettings: "Answer cannot be ‘partner’ as you told us person %{person_num}'s age is under 16." + child_over_20: "Answer cannot be ‘child’ if the person's age is 20 or over." + one_partner: "Number of partners cannot be greater than 1." + not_student_16_19: "Answer cannot be ‘child’ as you told us the person %{person_num} is between 16 and 19 and is not a full-time student." student_16_19: cannot_be_child: - student_not_16_19: "Answer cannot be ‘child’ if the person is a student but not aged 16-19" - 16_19_not_student: "Answer cannot be ‘child’ if the person is aged 16-19 but not a student" - child_over_19: "Answer cannot be child as you told us person %{person_num} is over 19" + student_not_16_19: "Answer cannot be ‘child’ if the person is a student but not aged 16-19." + 16_19_not_student: "Answer cannot be ‘child’ if the person is aged 16-19 but not a student." + child_over_19: "Answer cannot be child as you told us person %{person_num} is over 19." housingneeds_a: - one_or_two_choices: "You can only select one option or ‘other disabled access needs’ plus ‘wheelchair-accessible housing’, ‘wheelchair access to essential rooms’ or ‘level access housing’" + one_or_two_choices: "You can only select one option or ‘other disabled access needs’ plus ‘wheelchair-accessible housing’, ‘wheelchair access to essential rooms’ or ‘level access housing’." housingneeds_type: - only_one_option_permitted: "Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected" + only_one_option_permitted: "Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected." housingneeds: - invalid: - If somebody in the household has disabled access needs, they must have the access needs listed, or other access needs - no_disabled_needs_conjunction: "No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs" - dont_know_disabled_needs_conjunction: "Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs" - no_and_dont_know_disabled_needs_conjunction: "No disabled access needs and don’t know disabled access needs cannot be selected together" + invalid: "If somebody in the household has disabled access needs, they must have the access needs listed, or other access needs." + no_disabled_needs_conjunction: "No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs." + dont_know_disabled_needs_conjunction: "Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs." + no_and_dont_know_disabled_needs_conjunction: "No disabled access needs and don’t know disabled access needs cannot be selected together." prevten: - non_temp_accommodation: "Answer cannot be non-temporary accommodation as this is a re-let to a tenant who occupied the same property as temporary accommodation" - over_25_foster_care: "Answer cannot be a children’s home or foster care as the lead tenant is 26 or older" - internal_transfer: "Answer cannot be %{prevten} as this tenancy is an internal transfer" + non_temp_accommodation: "Answer cannot be non-temporary accommodation as this is a re-let to a tenant who occupied the same property as temporary accommodation." + over_25_foster_care: "Answer cannot be a children’s home or foster care as the lead tenant is 26 or older." + internal_transfer: "Answer cannot be %{prevten} as this tenancy is an internal transfer." la_general_needs: - internal_transfer: "Answer cannot be a fixed-term or lifetime local authority general needs tenancy as it’s an internal transfer and a private registered provider is on the tenancy agreement" - invalid_for_discounted_sale: "Buyer 1’s previous tenure should be “local authority tenant” or “private registered provider or housing association tenant” for discounted sales" + internal_transfer: "Answer cannot be a fixed-term or lifetime local authority general needs tenancy as it’s an internal transfer and a private registered provider is on the tenancy agreement." + invalid_for_discounted_sale: "Buyer 1’s previous tenure should be “local authority tenant” or “private registered provider or housing association tenant” for discounted sales." referral: - secure_tenancy: "Answer must be internal transfer as this is a secure tenancy" - rsnvac_non_temp: "Answer cannot be this source of referral as this is a re-let to tenant who occupied the same property as temporary accommodation" - cannot_be_secure_tenancy: "Answer cannot be secure tenancy as this is not an internal transfer" - assessed_homeless: "Answer cannot be internal transfer as the tenant was assessed as homeless" - other_homeless: "Answer cannot be internal transfer as the tenant was considered homeless by their landlord" - prevten_invalid: "Answer cannot be internal transfer as the household situation immediately before this letting was %{prevten}" - reason_permanently_decanted: "Answer must be internal transfer as the tenant was permanently decanted from another property owned by this landlord" - nominated_by_local_ha_but_la: The source of the referral cannot be Nominated by local housing authority as your organisation is a local authority + secure_tenancy: "Answer must be internal transfer as this is a secure tenancy." + rsnvac_non_temp: "Answer cannot be this source of referral as this is a re-let to tenant who occupied the same property as temporary accommodation." + cannot_be_secure_tenancy: "Answer cannot be secure tenancy as this is not an internal transfer." + assessed_homeless: "Answer cannot be internal transfer as the tenant was assessed as homeless." + other_homeless: "Answer cannot be internal transfer as the tenant was considered homeless by their landlord." + prevten_invalid: "Answer cannot be internal transfer as the household situation immediately before this letting was %{prevten}." + reason_permanently_decanted: "Answer must be internal transfer as the tenant was permanently decanted from another property owned by this landlord." + nominated_by_local_ha_but_la: "The source of the referral cannot be Nominated by local housing authority as your organisation is a local authority." la_general_needs: - internal_transfer: "Answer cannot be internal transfer as it’s the same landlord on the tenancy agreement and the household had either a fixed-term or lifetime local authority general needs tenancy immediately before this letting" - prp_referred_by_la: "The source of the referral cannot be referred by local authority housing department for a general needs log" + internal_transfer: "Answer cannot be internal transfer as it’s the same landlord on the tenancy agreement and the household had either a fixed-term or lifetime local authority general needs tenancy immediately before this letting." + prp_referred_by_la: "The source of the referral cannot be referred by local authority housing department for a general needs log." homeless: assessed: - internal_transfer: "Answer cannot be 'assessed as homeless' as this tenancy is an internal transfer" + internal_transfer: "Answer cannot be 'assessed as homeless' as this tenancy is an internal transfer." other: - internal_transfer: "Answer cannot be 'other homelessness' as this tenancy was an internal transfer" + internal_transfer: "Answer cannot be 'other homelessness' as this tenancy was an internal transfer." reasonpref: - not_homeless: "Answer cannot be ‘no’ as the tenant was homeless or about to lose their home" - previous_la_known: "Enter name of local authority" + not_homeless: "Answer cannot be ‘no’ as the tenant was homeless or about to lose their home." + previous_la_known: "Enter name of local authority." renewal_just_moved_to_area: layear: 'The household cannot have just moved to the local authority area if this letting is a renewal' renewal: 'This letting cannot be a renewal if the household has just moved to the local authority area' @@ -573,95 +572,95 @@ en: current_la: 'You told us the tenant had just moved into the local authority, but this location is in the same local authority. Check your answers are correct' previous_la: 'The local authority of the previous property should not be the same as the current local authority, as you told us they had just moved to the local authority area. Check your answers are correct.' gender: - retired_male: "Answer cannot be ‘male’ as tenant is under 65 and retired" - retired_female: "Answer cannot be ‘female’ as tenant is under 60 and retired" + retired_male: "Answer cannot be ‘male’ as tenant is under 65 and retired." + retired_female: "Answer cannot be ‘female’ as tenant is under 60 and retired." reason: - not_internal_transfer: "Answer cannot be ‘permanently decanted from another property owned by this landlord’ as you told us the source of referral for this tenancy was not an internal transfer" - renewal_reason_needed: 'The reason for leaving must be "End of assured shorthold tenancy - no fault" or "End of fixed term tenancy - no fault" if the letting is a renewal' - renewal_reason_needed_2024: 'The reason for leaving must be "End of social or private sector tenancy - no fault", "End of social or private sector tenancy - evicted due to anti-social behaviour (ASB)", "End of social or private sector tenancy - evicted due to rent arrears" or "End of social or private sector tenancy - evicted for any other reason"' - other_not_settled: "Please give the reason for the tenant leaving their last settled home. This is where they were living before they became homeless, were living in temporary accommodation or sleeping rough" + not_internal_transfer: "Answer cannot be ‘permanently decanted from another property owned by this landlord’ as you told us the source of referral for this tenancy was not an internal transfer." + renewal_reason_needed: 'The reason for leaving must be "End of assured shorthold tenancy - no fault" or "End of fixed term tenancy - no fault" if the letting is a renewal.' + renewal_reason_needed_2024: 'The reason for leaving must be "End of social or private sector tenancy - no fault", "End of social or private sector tenancy - evicted due to anti-social behaviour (ASB)", "End of social or private sector tenancy - evicted due to rent arrears" or "End of social or private sector tenancy - evicted for any other reason".' + other_not_settled: "Please give the reason for the tenant leaving their last settled home. This is where they were living before they became homeless, were living in temporary accommodation or sleeping rough." condition_effects: - no_choices: "You cannot answer this question as you told us nobody in the household has a physical or mental health condition (or other illness) expected to last 12 months or more" + no_choices: "You cannot answer this question as you told us nobody in the household has a physical or mental health condition (or other illness) expected to last 12 months or more." postcode: - discounted_ownership: "Last settled accommodation and discounted ownership property postcodes must match" + discounted_ownership: "Last settled accommodation and discounted ownership property postcodes must match." buylivein: - buyers_will_live_in_property_values_inconsistent_setup: "You have already told us that both buyer 1 and buyer 2 will not live in the property" - buyers_will_live_in_property_values_inconsistent: "You have already told us that the buyers will live in the property. Either buyer 1 or buyer 2 must live in the property" - nationality: "Select a valid nationality" + buyers_will_live_in_property_values_inconsistent_setup: "You have already told us that both buyer 1 and buyer 2 will not live in the property." + buyers_will_live_in_property_values_inconsistent: "You have already told us that the buyers will live in the property. Either buyer 1 or buyer 2 must live in the property." + nationality: "Select a valid nationality." tenancy: length: - fixed_term_not_required: "You must only answer the length of the tenancy if it's fixed-term" - invalid_fixed: "Enter a tenancy length between %{min_tenancy_length} and 99 years for a tenancy of this type" - invalid_periodic: "Enter a tenancy length between %{min_tenancy_length} and 99 years (or don't specify the length) for a tenancy of this type" - internal_transfer: "Answer must be secure tenancy as this tenancy is an internal transfer" - cannot_be_internal_transfer: "Answer cannot be internal transfer as this is not a secure tenancy" - not_joint: "This cannot be a joint tenancy as you've told us there's only one person in the household" - joint_more_than_one_member: "There must be more than one person in the household as you've told us this is a joint tenancy" + fixed_term_not_required: "You must only answer the length of the tenancy if it's fixed-term." + invalid_fixed: "Enter a tenancy length between %{min_tenancy_length} and 99 years for a tenancy of this type." + invalid_periodic: "Enter a tenancy length between %{min_tenancy_length} and 99 years (or don't specify the length) for a tenancy of this type." + internal_transfer: "Answer must be secure tenancy as this tenancy is an internal transfer." + cannot_be_internal_transfer: "Answer cannot be internal transfer as this is not a secure tenancy." + not_joint: "This cannot be a joint tenancy as you've told us there's only one person in the household." + joint_more_than_one_member: "There must be more than one person in the household as you've told us this is a joint tenancy." declaration: missing: - pre_2024: "You must show the MHCLG privacy notice to the tenant before you can submit this log" - post_2024: "You must show or give the tenant access to the MHCLG privacy notice before you can submit this log" + pre_2024: "You must show the MHCLG privacy notice to the tenant before you can submit this log." + post_2024: "You must show or give the tenant access to the MHCLG privacy notice before you can submit this log." privacynotice: missing: - pre_2024: "You must show the MHCLG privacy notice to the %{buyer_or_buyers} before you can submit this log" - post_2024: "You must show or give the %{buyer_or_buyers} access to the MHCLG privacy notice before you can submit this log" + pre_2024: "You must show the MHCLG privacy notice to the %{buyer_or_buyers} before you can submit this log." + post_2024: "You must show or give the %{buyer_or_buyers} access to the MHCLG privacy notice before you can submit this log." scheme: toggle_date: - not_selected: "Select one of the options" - invalid: "Enter a valid day, month and year" - before_creation: "The scheme cannot be deactivated before %{date}, the start of the collection year when it was created" - out_of_range: "The date must be on or after the %{date}" + not_selected: "Select one of the options." + invalid: "Enter a valid day, month and year." + before_creation: "The scheme cannot be deactivated before %{date}, the start of the collection year when it was created." + out_of_range: "The date must be on or after the %{date}." reactivation: - before_deactivation: "This scheme was deactivated on %{date}. The reactivation date must be on or after deactivation date" + before_deactivation: "This scheme was deactivated on %{date}. The reactivation date must be on or after deactivation date." deactivation: - during_deactivated_period: "The scheme is already deactivated during this date, please enter a different date" + during_deactivated_period: "The scheme is already deactivated during this date, please enter a different date." owning_organisation: - does_not_own_stock: "Enter an organisation that owns housing stock" - no_completed_locations: "This scheme cannot be chosen as it has no completed locations" + does_not_own_stock: "Enter an organisation that owns housing stock." + no_completed_locations: "This scheme cannot be chosen as it has no completed locations." location: - postcode_blank: "Enter a postcode" - units: "The units at this location must be a number" - 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" - startdate_out_of_range: "Availability date must be on or after the %{date}" + postcode_blank: "Enter a postcode." + units: "The units at this location must be a number." + 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." + startdate_out_of_range: "Availability date must be on or after the %{date}." toggle_date: - not_selected: "Select one of the options" - invalid: "Enter a valid day, month and year" - before_creation: "The location cannot be deactivated before %{date}, the date when it was first available" - out_of_range: "The date must be on or after the %{date}" + not_selected: "Select one of the options." + invalid: "Enter a valid day, month and year." + before_creation: "The location cannot be deactivated before %{date}, the date when it was first available." + out_of_range: "The date must be on or after the %{date}." reactivation: - before_deactivation: "This location was deactivated on %{date}. The reactivation date must be on or after deactivation date" + before_deactivation: "This location was deactivated on %{date}. The reactivation date must be on or after deactivation date." deactivation: - during_deactivated_period: "The location is already deactivated during this date, please enter a different date" + during_deactivated_period: "The location is already deactivated during this date, please enter a different date." sale_information: proplen: - social_homebuy: "Social HomeBuy buyers should not have lived here before" - rent_to_buy: "Rent to Buy buyers should not have lived here before" + social_homebuy: "Social HomeBuy buyers should not have lived here before." + rent_to_buy: "Rent to Buy buyers should not have lived here before." hodate: - must_be_before_saledate: "Practical completion or handover date must be before sale completion date" - must_be_less_than_3_years_from_saledate: "Practical completion or handover date must be less than 3 years before sale completion date" + must_be_before_saledate: "Practical completion or handover date must be before sale completion date." + must_be_less_than_3_years_from_saledate: "Practical completion or handover date must be less than 3 years before sale completion date." 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" + 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." saledate: - must_be_after_exdate: "Sale completion date must be after contract exchange date" - must_be_less_than_1_year_from_exdate: "Sale completion date must be less than 1 year after contract exchange date" - must_be_less_than_3_years_from_hodate: "Sale completion date must be less than 3 years after practical completion or handover date" - must_be_after_hodate: "Sale completion date must be after practical completion or handover date" + must_be_after_exdate: "Sale completion date must be after contract exchange date." + must_be_less_than_1_year_from_exdate: "Sale completion date must be less than 1 year after contract exchange date." + must_be_less_than_3_years_from_hodate: "Sale completion date must be less than 3 years after practical completion or handover date." + must_be_after_hodate: "Sale completion date must be after practical completion or handover date." previous_property_type: - property_type_bedsit: "A bedsit cannot have more than 1 bedroom" + property_type_bedsit: "A bedsit cannot have more than 1 bedroom." discounted_ownership_value: "The mortgage%{mortgage}%{deposit_and_grant_sentence} added together is %{mortgage_deposit_and_grant_total}.

The full purchase price%{discount_sentence} is %{value_with_discount}.

These two amounts should be the same." outright_sale_value: "The mortgage%{mortgage} and cash deposit (%{deposit}) when added together is %{mortgage_and_deposit_total}.

The full purchase price is %{value}.

These two amounts should be the same." monthly_rent: - higher_than_expected: "Basic monthly rent must be between £0.00 and £9,999.00" + higher_than_expected: "Basic monthly rent must be between £0.00 and £9,999.00." grant: - out_of_range: "Loan, grants or subsidies must be between £9,000 and £16,000" + out_of_range: "Loan, grants or subsidies must be between £9,000 and £16,000." stairbought: over_max: "The percentage bought in this staircasing transaction cannot be higher than %{max_stairbought}% for %{type} sales." value: @@ -680,12 +679,12 @@ en: stairowned: mortgageused_dont_know: "The percentage owned has to be 100% if the mortgage used is 'Don’t know'" merge_request: - organisation_part_of_another_merge: "This organisation is part of another merge - select a different one" - organisation_not_selected: "Select an organisation from the search list" + organisation_part_of_another_merge: "This organisation is part of another merge - select a different one." + organisation_not_selected: "Select an organisation from the search list." soft_validations: net_income: - title_text: "You told us that the household’s income is %{earnings} %{incfreq}" + title_text: "You told us that the household’s income is %{earnings} %{incfreq}." hint_text: "This is %{net_income_higher_or_lower_text} than we would expect for the household’s working situation." in_soft_min_range: message: "Net income is lower than expected based on the household’s working situation. Are you sure this is correct?" @@ -699,16 +698,16 @@ en: over_soft_max_for_la_buyer_2: "You told us the income of buyer 2 is %{income}. This seems high. Are you sure this is correct?" over_soft_max_for_la_combined: "You told us the combined income of this household is %{combined_income}. This seems high. Are you sure this is correct?" rent: - outside_range_title: "You told us the rent is %{brent}" + outside_range_title: "You told us the rent is %{brent}." informative_text: "This is %{higher_or_lower} than we would expect." hint_text: "Check the following:
  • the decimal point
  • the frequency, for example every week or every calendar month
  • the rent type is correct, for example affordable or social rent
" purchase_price: - title_text: "You told us the purchase price is %{value}" - hint_text: "This is %{higher_or_lower} than we would expect" + title_text: "You told us the purchase price is %{value}." + hint_text: "This is %{higher_or_lower} than we would expect." staircase_owned: title_text: - one: "You told us that the buyer now owns %{stairowned} of the property" - two: "You told us that the buyers now own %{stairowned} of the property" + one: "You told us that the buyer now owns %{stairowned} of the property." + two: "You told us that the buyers now own %{stairowned} of the property." hint_text: "The maximum percentage that can be owned under the Older Persons Shared Ownership scheme is 75%, unless the property was funded outside the Affordable Homes Programme. Make sure these answers are correct." retirement: @@ -716,13 +715,13 @@ Make sure these answers are correct." title: "You told us this person is aged %{age} years and retired." hint_text: "The minimum expected retirement age in England is 66." max: - title: "You told us this person is over 66 and not retired" + title: "You told us this person is over 66 and not retired." hint_text: "The minimum expected retirement age in England is 66." extra_borrowing: - title_text: "You told us that the mortgage and deposit total is %{mortgage_and_deposit_total}" + title_text: "You told us that the mortgage and deposit total is %{mortgage_and_deposit_total}." hint_text: "This is higher than the purchase price minus the discount." pregnancy: - title: "You told us somebody in the household is pregnant" + title: "You told us somebody in the household is pregnant." all_male_tenants: "You also told us that all the tenants living at the property are male." females_not_in_soft_age_range: "You also told us that any female tenants living at the property are in the following age ranges:
  • under 16 years old
  • over 50 years old
" major_repairs_date: @@ -732,7 +731,7 @@ Make sure these answers are correct." title_text: "You told us that the property has been vacant for more than 2 years." hint_text: "This is higher than we would expect." shared_ownership_deposit: - title_text: "You told us that the %{mortgage_deposit_and_discount_error_fields} add up to %{mortgage_deposit_and_discount_total}" + title_text: "You told us that the %{mortgage_deposit_and_discount_error_fields} add up to %{mortgage_deposit_and_discount_total}." old_persons_shared_ownership: title_text: one: "You told us the buyer is using the Older Persons Shared Ownership scheme." @@ -745,12 +744,12 @@ Make sure these answers are correct." title_text: "You told us that the monthly charges were %{mscharge}." hint_text: "This is higher than we would expect." student_not_child: - title_text: "You told us this person is a student aged beween 16 and 19" + title_text: "You told us this person is a student aged between 16 and 19." discounted_sale_value: - title_text: "Mortgage, deposit, and grant total must equal %{value_with_discount}" - informative_text: "Your given mortgage, deposit and grant total is %{mortgage_deposit_and_grant_total}" + title_text: "Mortgage, deposit, and grant total must equal %{value_with_discount}." + informative_text: "Your given mortgage, deposit and grant total is %{mortgage_deposit_and_grant_total}." care_home_charges: - title_text: "Care home charges should be provided if this is a care home accommodation" + title_text: "Care home charges should be provided if this is a care home accommodation." buyer1_livein_wrong_for_ownership_type: title_text: "You told us that buyer 1 will not live in the property." hint_text: " For %{ownership_scheme} types, the buyer usually lives in the property." @@ -771,22 +770,22 @@ Make sure these answers are correct." two: "You told us the buyers’ deposit was %{deposit} and their savings were %{savings}." hint_text: "The deposit amount is higher than we would expect for the amount of savings they have." grant: - title_text: "You told us that the grant amount is %{grant}" + title_text: "You told us that the grant amount is %{grant}." hint_text: "Loans, grants and subsidies are usually between £9,000 and £16,000." wheelchair: title_text: "You told us that someone in the household uses a wheelchair." mortgage: - title_text: "You told us that the mortgage amount is %{mortgage}" + title_text: "You told us that the mortgage amount is %{mortgage}." hint_text: "This is more than 5 times the income, which is higher than we would expect." referral: title_text: "Are you sure?" hint_text: "This is a general needs log, and this referral type is for supported housing." scharge: - over_soft_max_title: "You told us the service charge is %{scharge}" + over_soft_max_title: "You told us the service charge is %{scharge}." pscharge: - over_soft_max_title: "You told us the personal service charge is %{pscharge}" + over_soft_max_title: "You told us the personal service charge is %{pscharge}." supcharg: - over_soft_max_title: "You told us the support charge is %{supcharg}" + over_soft_max_title: "You told us the support charge is %{supcharg}." charges: informative_text: "This is higher than we would expect." hint_text: "Check the following:
  • the decimal point
  • the frequency, for example every week or every calendar month
  • the needs type
" @@ -794,16 +793,16 @@ Make sure these answers are correct." title_text: "You told us the mortgage amount was %{mortgage}, the cash deposit was %{deposit} and the discount was %{discount}." hint_text: "We would expect the mortgage amount and the deposit added together to be the same as the purchase price minus the discount." reasonother: - title_text: "You told us that the tenant’s main reason for leaving their last settled home was %{reasonother}" + title_text: "You told us that the tenant’s main reason for leaving their last settled home was %{reasonother}." informative_text: "The reason you have entered looks very similar to one of the existing response categories. Please check the categories and select the appropriate one. If the existing categories are not suitable, please confirm here to move onto the next question." hodate: - must_be_less_than_3_years_from_saledate: "You told us practical completion or handover date is more than 3 years before sale completion date" + must_be_less_than_3_years_from_saledate: "You told us practical completion or handover date is more than 3 years before sale completion date." saledate: - must_be_less_than_3_years_from_hodate: "You told us sale completion date is more than 3 years after practical completion or handover date" + must_be_less_than_3_years_from_hodate: "You told us sale completion date is more than 3 years after practical completion or handover date." no_address_found: - title_text: "No address found" + title_text: "No address found." informative_text: "We could not find an address that matches your search. You can search again or continue to enter the address manually." partner_under_16_lettings: title: "You told us this person is aged %{age} years and has 'Partner' relationship to the lead tenant." @@ -816,16 +815,16 @@ Make sure these answers are correct." devise: email: - updated: An email has been sent to %{email} to confirm this change. + updated: "An email has been sent to %{email} to confirm this change." two_factor_authentication: - success: "Two-factor authentication successful" - attempt_failed: "Attempt failed" - max_login_attempts_reached: "Too many incorrect log in attempts" - account_locked: "Your account has been locked for security reasons" - contact_administrator: "Contact another helpdesk administrator for access" - code_has_been_sent: "Your security code has been sent" - code_required: "Security code is required" - code_incorrect: "Security code is incorrect" + success: "Two-factor authentication successful." + attempt_failed: "Attempt failed." + max_login_attempts_reached: "Too many incorrect log in attempts." + account_locked: "Your account has been locked for security reasons." + contact_administrator: "Contact another helpdesk administrator for access." + code_has_been_sent: "Your security code has been sent." + code_required: "Security code is required." + code_incorrect: "Security code is incorrect." questions: location: @@ -860,10 +859,10 @@ Make sure these answers are correct." hints: location: postcode: "For example, SW1P 4DF." - name: "This is how you refer to this location within your organisation" + name: "This is how you refer to this location within your organisation." units: "A unit is the space being let. For example, the property might be a block of flats and the unit would be the specific flat being let. A unit can also be a bedroom in a shared house or flat. Do not include spaces used for staff." toggle_active: "If the date is before %{date}, select ‘From the start of the open collection period’ because the previous period has now closed." - startdate: "For example, 27 3 2021" + startdate: "For example, 27 3 2021." scheme: toggle_active: "If the date is before %{date}, select ‘From the start of the open collection period’ because the previous period has now closed." bulk_upload: @@ -875,12 +874,12 @@ Make sure these answers are correct." one: "Buyer was a registered provider, housing association or local authority tenant immediately before this sale?" other: "Any buyers were registered providers, housing association or local authority tenants immediately before this sale?" prevown: - one: "Buyer previously owned a property" - other: "Buyers previously owned a property" + one: "Buyer previously owned a property." + other: "Buyers previously owned a property." stairowned: - one: "Percentage the buyer now owns in total" - other: "Percentage the buyers now own in total" - offered: "Times previously offered since becoming available" + one: "Percentage the buyer now owns in total." + other: "Percentage the buyers now own in total." + offered: "Times previously offered since becoming available." warnings: organisation: @@ -900,7 +899,7 @@ Make sure these answers are correct." existing_logs: "You’ll be able to add logs with this scheme if their tenancy start date is on or after the date you enter." test: - one_argument: "This is based on the tenant’s work situation: %{ecstat1}" + one_argument: "This is based on the tenant’s work situation: %{ecstat1}." title_text: - no_argument: "Some test text" - one_argument: "You said this: %{argument}" + no_argument: "Some test text." + one_argument: "You said this: %{argument}." diff --git a/spec/helpers/interruption_screen_helper_spec.rb b/spec/helpers/interruption_screen_helper_spec.rb index cd044e61a..60cd71131 100644 --- a/spec/helpers/interruption_screen_helper_spec.rb +++ b/spec/helpers/interruption_screen_helper_spec.rb @@ -151,7 +151,7 @@ RSpec.describe InterruptionScreenHelper do }, ], } - expect(display_informative_text(informative_text_hash, lettings_log)).to eq("You said this: £12,345.00") + expect(display_informative_text(informative_text_hash, lettings_log)).to eq("You said this: £12,345.00.") end end @@ -216,7 +216,7 @@ RSpec.describe InterruptionScreenHelper do }, ], } - expect(display_title_text(title_text, lettings_log)).to eq("You said this: £12,345.00") + expect(display_title_text(title_text, lettings_log)).to eq("You said this: £12,345.00.") end end end diff --git a/spec/lib/tasks/update_schemes_and_locations_from_csv_spec.rb b/spec/lib/tasks/update_schemes_and_locations_from_csv_spec.rb index 818a2b589..c397b456a 100644 --- a/spec/lib/tasks/update_schemes_and_locations_from_csv_spec.rb +++ b/spec/lib/tasks/update_schemes_and_locations_from_csv_spec.rb @@ -445,7 +445,7 @@ RSpec.describe "bulk_update" do expect(Rails.logger).to receive(:info).with("Will not export log #{lettings_log_5.id} as it is before the exportable date") expect(Rails.logger).to receive(:info).with("No changes to location #{locations[1].id}.") - expect(Rails.logger).to receive(:info).with("Cannot update location #{locations[2].id} with postcode: SWAAA. Enter a postcode in the correct format, for example AA1 1AA") + expect(Rails.logger).to receive(:info).with("Cannot update location #{locations[2].id} with postcode: SWAAA. Enter a postcode in the correct format, for example AA1 1AA.") expect(Rails.logger).to receive(:info).with("Cannot update location #{locations[2].id} with scheme_code: S. Scheme with id S is not in the database") expect(Rails.logger).to receive(:info).with("Cannot update location #{locations[2].id} with location_admin_district: Westminst. Location admin distrint Westminst is not a valid option") expect(Rails.logger).to receive(:info).with("Cannot update location #{locations[2].id} with type_of_unit: elf-contained house. 'elf-contained house' is not a valid type_of_unit") diff --git a/spec/models/form/lettings/questions/declaration_spec.rb b/spec/models/form/lettings/questions/declaration_spec.rb index c64e7f3c8..ea1e10402 100644 --- a/spec/models/form/lettings/questions/declaration_spec.rb +++ b/spec/models/form/lettings/questions/declaration_spec.rb @@ -63,7 +63,7 @@ RSpec.describe Form::Lettings::Questions::Declaration, type: :model do end it "returns correct unanswered_error_message" do - expect(question.unanswered_error_message).to eq("You must show the MHCLG privacy notice to the tenant before you can submit this log") + expect(question.unanswered_error_message).to eq("You must show the MHCLG privacy notice to the tenant before you can submit this log.") end end @@ -87,7 +87,7 @@ RSpec.describe Form::Lettings::Questions::Declaration, type: :model do end it "returns correct unanswered_error_message" do - expect(question.unanswered_error_message).to eq("You must show or give the tenant access to the MHCLG privacy notice before you can submit this log") + expect(question.unanswered_error_message).to eq("You must show or give the tenant access to the MHCLG privacy notice before you can submit this log.") end end end diff --git a/spec/models/form/lettings/questions/offered_spec.rb b/spec/models/form/lettings/questions/offered_spec.rb index bda69490e..753ab459e 100644 --- a/spec/models/form/lettings/questions/offered_spec.rb +++ b/spec/models/form/lettings/questions/offered_spec.rb @@ -18,7 +18,7 @@ RSpec.describe Form::Lettings::Questions::Offered, type: :model do end it "has the correct check_answer_label" do - expect(question.check_answer_label).to eq "Times previously offered since becoming available" + expect(question.check_answer_label).to eq "Times previously offered since becoming available." end it "has the correct type" do diff --git a/spec/models/form/lettings/questions/uprn_spec.rb b/spec/models/form/lettings/questions/uprn_spec.rb index 966b9b970..7d4822c16 100644 --- a/spec/models/form/lettings/questions/uprn_spec.rb +++ b/spec/models/form/lettings/questions/uprn_spec.rb @@ -40,7 +40,7 @@ RSpec.describe Form::Lettings::Questions::Uprn, type: :model do end it "has the correct unanswered_error_message" do - expect(question.unanswered_error_message).to eq("UPRN must be 12 digits or less") + expect(question.unanswered_error_message).to eq("UPRN must be 12 digits or less.") end describe "get_extra_check_answer_value" do diff --git a/spec/models/form/sales/questions/prevown_spec.rb b/spec/models/form/sales/questions/prevown_spec.rb index b086534d7..4e5d006d9 100644 --- a/spec/models/form/sales/questions/prevown_spec.rb +++ b/spec/models/form/sales/questions/prevown_spec.rb @@ -22,7 +22,7 @@ RSpec.describe Form::Sales::Questions::Prevown, type: :model do end it "has the correct check_answer_label" do - expect(question.check_answer_label).to eq("Buyer previously owned a property") + expect(question.check_answer_label).to eq("Buyer previously owned a property.") end end @@ -34,7 +34,7 @@ RSpec.describe Form::Sales::Questions::Prevown, type: :model do end it "has the correct check_answer_label" do - expect(question.check_answer_label).to eq("Buyers previously owned a property") + expect(question.check_answer_label).to eq("Buyers previously owned a property.") end end diff --git a/spec/models/form/sales/questions/privacy_notice_spec.rb b/spec/models/form/sales/questions/privacy_notice_spec.rb index 9fb81057a..9242804ae 100644 --- a/spec/models/form/sales/questions/privacy_notice_spec.rb +++ b/spec/models/form/sales/questions/privacy_notice_spec.rb @@ -60,7 +60,7 @@ RSpec.describe Form::Sales::Questions::PrivacyNotice, type: :model do end it "returns correct unanswered_error_message" do - expect(question.unanswered_error_message).to eq("You must show the MHCLG privacy notice to the buyer before you can submit this log") + expect(question.unanswered_error_message).to eq("You must show the MHCLG privacy notice to the buyer before you can submit this log.") end end @@ -78,7 +78,7 @@ RSpec.describe Form::Sales::Questions::PrivacyNotice, type: :model do end it "returns correct unanswered_error_message" do - expect(question.unanswered_error_message).to eq("You must show the MHCLG privacy notice to the buyers before you can submit this log") + expect(question.unanswered_error_message).to eq("You must show the MHCLG privacy notice to the buyers before you can submit this log.") end end end @@ -100,7 +100,7 @@ RSpec.describe Form::Sales::Questions::PrivacyNotice, type: :model do end it "returns correct unanswered_error_message" do - expect(question.unanswered_error_message).to eq("You must show or give the buyer access to the MHCLG privacy notice before you can submit this log") + expect(question.unanswered_error_message).to eq("You must show or give the buyer access to the MHCLG privacy notice before you can submit this log.") end end @@ -118,7 +118,7 @@ RSpec.describe Form::Sales::Questions::PrivacyNotice, type: :model do end it "returns correct unanswered_error_message" do - expect(question.unanswered_error_message).to eq("You must show or give the buyers access to the MHCLG privacy notice before you can submit this log") + expect(question.unanswered_error_message).to eq("You must show or give the buyers access to the MHCLG privacy notice before you can submit this log.") end end end diff --git a/spec/models/form/sales/questions/staircase_owned_spec.rb b/spec/models/form/sales/questions/staircase_owned_spec.rb index a9b252f05..80cd30543 100644 --- a/spec/models/form/sales/questions/staircase_owned_spec.rb +++ b/spec/models/form/sales/questions/staircase_owned_spec.rb @@ -24,7 +24,7 @@ RSpec.describe Form::Sales::Questions::StaircaseOwned, type: :model do end it "has the correct check_answer_label" do - expect(question.check_answer_label).to eq("Percentage the buyers now own in total") + expect(question.check_answer_label).to eq("Percentage the buyers now own in total.") end end @@ -34,7 +34,7 @@ RSpec.describe Form::Sales::Questions::StaircaseOwned, type: :model do end it "has the correct check_answer_label" do - expect(question.check_answer_label).to eq("Percentage the buyer now owns in total") + expect(question.check_answer_label).to eq("Percentage the buyer now owns in total.") end end diff --git a/spec/models/form/sales/questions/uprn_spec.rb b/spec/models/form/sales/questions/uprn_spec.rb index afc4e4a24..845358bce 100644 --- a/spec/models/form/sales/questions/uprn_spec.rb +++ b/spec/models/form/sales/questions/uprn_spec.rb @@ -40,7 +40,7 @@ RSpec.describe Form::Sales::Questions::Uprn, type: :model do end it "has the correct unanswered_error_message" do - expect(question.unanswered_error_message).to eq("UPRN must be 12 digits or less") + expect(question.unanswered_error_message).to eq("UPRN must be 12 digits or less.") end describe "get_extra_check_answer_value" do diff --git a/spec/models/location_deactivation_period_spec.rb b/spec/models/location_deactivation_period_spec.rb index e2cfd5758..6157b893b 100644 --- a/spec/models/location_deactivation_period_spec.rb +++ b/spec/models/location_deactivation_period_spec.rb @@ -23,7 +23,7 @@ RSpec.describe LocationDeactivationPeriod do record.deactivation_date = current_collection_start_date - 1.year location.location_deactivation_periods.clear validator.validate(record) - expect(record.errors[:deactivation_date]).to include "The date must be on or after the 1 April 2023" + expect(record.errors[:deactivation_date]).to include "The date must be on or after the 1 April 2023." end end @@ -47,7 +47,7 @@ RSpec.describe LocationDeactivationPeriod do record.deactivation_date = previous_collection_start_date - 2.years location.location_deactivation_periods.clear validator.validate(record) - expect(record.errors[:deactivation_date]).to include "The date must be on or after the 1 April 2022" + expect(record.errors[:deactivation_date]).to include "The date must be on or after the 1 April 2022." end end @@ -80,7 +80,7 @@ RSpec.describe LocationDeactivationPeriod do location.location_deactivation_periods.clear validator.validate(record) start_date = startdate.to_formatted_s(:govuk_date) - expect(record.errors[:deactivation_date]).to include "The location cannot be deactivated before #{start_date}, the date when it was first available" + expect(record.errors[:deactivation_date]).to include "The location cannot be deactivated before #{start_date}, the date when it was first available." end end end diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb index 86c02476f..b873affbd 100644 --- a/spec/models/notification_spec.rb +++ b/spec/models/notification_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Notification, type: :model do it "adds an error to page_content" do notification.valid? - expect(notification.errors[:page_content]).to include("Enter the page content") + expect(notification.errors[:page_content]).to include("Enter the page content.") end end @@ -19,7 +19,7 @@ RSpec.describe Notification, type: :model do it "adds an error to link_text" do notification.valid? - expect(notification.errors[:link_text]).to include("Enter the link text") + expect(notification.errors[:link_text]).to include("Enter the link text.") end end end diff --git a/spec/models/scheme_deactivation_period_spec.rb b/spec/models/scheme_deactivation_period_spec.rb index 9e59399e9..5d4e6f6e2 100644 --- a/spec/models/scheme_deactivation_period_spec.rb +++ b/spec/models/scheme_deactivation_period_spec.rb @@ -23,7 +23,7 @@ RSpec.describe SchemeDeactivationPeriod do record.deactivation_date = current_collection_start_date - 1.year scheme.scheme_deactivation_periods.clear validator.validate(record) - expect(record.errors[:deactivation_date]).to include("The date must be on or after the 1 April 2023") + expect(record.errors[:deactivation_date]).to include("The date must be on or after the 1 April 2023.") end end @@ -47,7 +47,7 @@ RSpec.describe SchemeDeactivationPeriod do record.deactivation_date = previous_collection_start_date - 2.years scheme.scheme_deactivation_periods.clear validator.validate(record) - expect(record.errors[:deactivation_date]).to include("The date must be on or after the 1 April 2022") + expect(record.errors[:deactivation_date]).to include("The date must be on or after the 1 April 2022.") end end diff --git a/spec/models/validations/financial_validations_spec.rb b/spec/models/validations/financial_validations_spec.rb index a6eefa708..4e1aa400f 100644 --- a/spec/models/validations/financial_validations_spec.rb +++ b/spec/models/validations/financial_validations_spec.rb @@ -237,9 +237,9 @@ RSpec.describe Validations::FinancialValidations do record.ecstat1 = 1 financial_validator.validate_net_income(record) expect(record.errors["earnings"]) - .to eq(["The household’s income cannot be greater than £1,230.00 per week given the household’s working situation"]) + .to eq(["The household’s income cannot be greater than £1,230.00 per week given the household’s working situation."]) expect(record.errors["ecstat1"]) - .to eq(["The household’s income of £5,000.00 weekly is too high given the household’s working situation"]) + .to eq(["The household’s income of £5,000.00 weekly is too high given the household’s working situation."]) expect(record.errors["hhmemb"]) .to eq(["The household’s income of £5,000.00 weekly is too high for this number of tenants. Change either the household income or number of tenants."]) end @@ -254,9 +254,9 @@ RSpec.describe Validations::FinancialValidations do record.ecstat1 = 1 financial_validator.validate_net_income(record) expect(record.errors["earnings"]) - .to eq(["The household’s income cannot be less than £90.00 per week given the household’s working situation"]) + .to eq(["The household’s income cannot be less than £90.00 per week given the household’s working situation."]) expect(record.errors["ecstat1"]) - .to eq(["The household’s income of £50.00 weekly is too low given the household’s working situation"]) + .to eq(["The household’s income of £50.00 weekly is too low given the household’s working situation."]) expect(record.errors["hhmemb"]) .to eq(["The household’s income of £50.00 weekly is too low for this number of tenants. Change either the household income or number of tenants."]) end @@ -286,7 +286,7 @@ RSpec.describe Validations::FinancialValidations do record.ecstat3 = 9 financial_validator.validate_net_income(record) expect(record.errors["earnings"]) - .to eq(["The household’s income cannot be less than £150.00 per week given the household’s working situation"]) + .to eq(["The household’s income cannot be less than £150.00 per week given the household’s working situation."]) end it "adds errors to relevant fields for each tenant when income is too high" do @@ -301,7 +301,7 @@ RSpec.describe Validations::FinancialValidations do financial_validator.validate_net_income(record) (1..record.hhmemb).each do |n| expect(record.errors["ecstat#{n}"]) - .to eq(["The household’s income of £5,000.00 weekly is too high given the household’s working situation"]) + .to eq(["The household’s income of £5,000.00 weekly is too high given the household’s working situation."]) end expect(record.errors["age1"]).to be_empty expect(record.errors["age2"]).to be_empty @@ -325,7 +325,7 @@ RSpec.describe Validations::FinancialValidations do financial_validator.validate_net_income(record) (1..record.hhmemb).each do |n| expect(record.errors["ecstat#{n}"]) - .to eq(["The household’s income of £50.00 weekly is too low given the household’s working situation"]) + .to eq(["The household’s income of £50.00 weekly is too low given the household’s working situation."]) end (record.hhmemb + 1..8).each do |n| expect(record.errors["ecstat#{n}"]).to be_empty @@ -1121,9 +1121,9 @@ RSpec.describe Validations::FinancialValidations do record.chcharge = 5001 financial_validator.validate_care_home_charges(record) expect(record.errors["chcharge"]) - .to include("Household rent and other charges must be between £10.00 and £5,000.00 if paying weekly for 52 weeks") + .to include("Household rent and other charges must be between £10.00 and £5,000.00 if paying weekly for 52 weeks.") expect(record.errors["period"]) - .to include("Household rent and other charges must be between £10.00 and £5,000.00 if paying weekly for 52 weeks") + .to include("Household rent and other charges must be between £10.00 and £5,000.00 if paying weekly for 52 weeks.") end it "validates charge when period is monthly" do @@ -1131,9 +1131,9 @@ RSpec.describe Validations::FinancialValidations do record.chcharge = 21_667 financial_validator.validate_care_home_charges(record) expect(record.errors["chcharge"]) - .to include("Household rent and other charges must be between £43.00 and £21,666.00 if paying every calendar month") + .to include("Household rent and other charges must be between £43.00 and £21,666.00 if paying every calendar month.") expect(record.errors["period"]) - .to include("Household rent and other charges must be between £43.00 and £21,666.00 if paying every calendar month") + .to include("Household rent and other charges must be between £43.00 and £21,666.00 if paying every calendar month.") end it "validates charge when period is every 2 weeks" do @@ -1141,9 +1141,9 @@ RSpec.describe Validations::FinancialValidations do record.chcharge = 12_001 financial_validator.validate_care_home_charges(record) expect(record.errors["chcharge"]) - .to include("Household rent and other charges must be between £20.00 and £10,000.00 if paying every 2 weeks") + .to include("Household rent and other charges must be between £20.00 and £10,000.00 if paying every 2 weeks.") expect(record.errors["period"]) - .to include("Household rent and other charges must be between £20.00 and £10,000.00 if paying every 2 weeks") + .to include("Household rent and other charges must be between £20.00 and £10,000.00 if paying every 2 weeks.") end it "validates charge when period is every 4 weeks" do @@ -1151,9 +1151,9 @@ RSpec.describe Validations::FinancialValidations do record.chcharge = 24_001 financial_validator.validate_care_home_charges(record) expect(record.errors["chcharge"]) - .to include("Household rent and other charges must be between £40.00 and £20,000.00 if paying every 4 weeks") + .to include("Household rent and other charges must be between £40.00 and £20,000.00 if paying every 4 weeks.") expect(record.errors["period"]) - .to include("Household rent and other charges must be between £40.00 and £20,000.00 if paying every 4 weeks") + .to include("Household rent and other charges must be between £40.00 and £20,000.00 if paying every 4 weeks.") end end @@ -1209,9 +1209,9 @@ RSpec.describe Validations::FinancialValidations do record.chcharge = 9 financial_validator.validate_care_home_charges(record) expect(record.errors["chcharge"]) - .to include("Household rent and other charges must be between £10.00 and £5,000.00 if paying weekly for 52 weeks") + .to include("Household rent and other charges must be between £10.00 and £5,000.00 if paying weekly for 52 weeks.") expect(record.errors["period"]) - .to include("Household rent and other charges must be between £10.00 and £5,000.00 if paying weekly for 52 weeks") + .to include("Household rent and other charges must be between £10.00 and £5,000.00 if paying weekly for 52 weeks.") end it "validates charge when period is monthly" do @@ -1219,9 +1219,9 @@ RSpec.describe Validations::FinancialValidations do record.chcharge = 42 financial_validator.validate_care_home_charges(record) expect(record.errors["chcharge"]) - .to include("Household rent and other charges must be between £43.00 and £21,666.00 if paying every calendar month") + .to include("Household rent and other charges must be between £43.00 and £21,666.00 if paying every calendar month.") expect(record.errors["period"]) - .to include("Household rent and other charges must be between £43.00 and £21,666.00 if paying every calendar month") + .to include("Household rent and other charges must be between £43.00 and £21,666.00 if paying every calendar month.") end it "validates charge when period is every 2 weeks" do @@ -1229,9 +1229,9 @@ RSpec.describe Validations::FinancialValidations do record.chcharge = 19 financial_validator.validate_care_home_charges(record) expect(record.errors["chcharge"]) - .to include("Household rent and other charges must be between £20.00 and £10,000.00 if paying every 2 weeks") + .to include("Household rent and other charges must be between £20.00 and £10,000.00 if paying every 2 weeks.") expect(record.errors["period"]) - .to include("Household rent and other charges must be between £20.00 and £10,000.00 if paying every 2 weeks") + .to include("Household rent and other charges must be between £20.00 and £10,000.00 if paying every 2 weeks.") end it "validates charge when period is every 4 weeks" do @@ -1239,9 +1239,9 @@ RSpec.describe Validations::FinancialValidations do record.chcharge = 39 financial_validator.validate_care_home_charges(record) expect(record.errors["chcharge"]) - .to include("Household rent and other charges must be between £40.00 and £20,000.00 if paying every 4 weeks") + .to include("Household rent and other charges must be between £40.00 and £20,000.00 if paying every 4 weeks.") expect(record.errors["period"]) - .to include("Household rent and other charges must be between £40.00 and £20,000.00 if paying every 4 weeks") + .to include("Household rent and other charges must be between £40.00 and £20,000.00 if paying every 4 weeks.") end end end diff --git a/spec/models/validations/household_validations_spec.rb b/spec/models/validations/household_validations_spec.rb index 7c8e023d0..b240b7b2a 100644 --- a/spec/models/validations/household_validations_spec.rb +++ b/spec/models/validations/household_validations_spec.rb @@ -763,7 +763,7 @@ RSpec.describe Validations::HouseholdValidations do it "is invalid" do household_validator.validate_combination_of_housing_needs_responses(record) - error_message = ["If somebody in the household has disabled access needs, they must have the access needs listed, or other access needs"] + error_message = ["If somebody in the household has disabled access needs, they must have the access needs listed, or other access needs."] expect(record.errors["housingneeds"]).to eq(error_message) expect(record.errors["housingneeds_type"]).to eq(error_message) @@ -779,7 +779,7 @@ RSpec.describe Validations::HouseholdValidations do it "is invalid" do household_validator.validate_combination_of_housing_needs_responses(record) - error_message = ["If somebody in the household has disabled access needs, they must have the access needs listed, or other access needs"] + error_message = ["If somebody in the household has disabled access needs, they must have the access needs listed, or other access needs."] expect(record.errors["housingneeds"]).to eq(error_message) expect(record.errors["housingneeds_type"]).to eq(error_message) diff --git a/spec/models/validations/property_validations_spec.rb b/spec/models/validations/property_validations_spec.rb index dcbcae337..3f6f9877c 100644 --- a/spec/models/validations/property_validations_spec.rb +++ b/spec/models/validations/property_validations_spec.rb @@ -276,7 +276,7 @@ RSpec.describe Validations::PropertyValidations do it "adds an error" do property_validator.validate_uprn(log) - expect(log.errors.added?(:uprn, "UPRN must be 12 digits or less")).to be true + expect(log.errors.added?(:uprn, "UPRN must be 12 digits or less.")).to be true end end @@ -285,7 +285,7 @@ RSpec.describe Validations::PropertyValidations do it "adds an error" do property_validator.validate_uprn(log) - expect(log.errors.added?(:uprn, "UPRN must be 12 digits or less")).to be true + expect(log.errors.added?(:uprn, "UPRN must be 12 digits or less.")).to be true end end diff --git a/spec/models/validations/sales/financial_validations_spec.rb b/spec/models/validations/sales/financial_validations_spec.rb index d9f47d39a..b0e80303a 100644 --- a/spec/models/validations/sales/financial_validations_spec.rb +++ b/spec/models/validations/sales/financial_validations_spec.rb @@ -187,7 +187,7 @@ RSpec.describe Validations::Sales::FinancialValidations do record.stairowned = 40 record.jointpur = 1 financial_validator.validate_percentage_bought_not_greater_than_percentage_owned(record) - expect(record.errors["stairowned"]).to include("Total percentage buyers now own must be more than percentage bought in this transaction") + expect(record.errors["stairowned"]).to include("Total percentage buyers now own must be more than percentage bought in this transaction.") end it "adds an error to stairowned and not stairbought if the percentage bought is more than the percentage owned for non joint purchase" do @@ -195,7 +195,7 @@ RSpec.describe Validations::Sales::FinancialValidations do record.stairowned = 40 record.jointpur = 2 financial_validator.validate_percentage_bought_not_greater_than_percentage_owned(record) - expect(record.errors["stairowned"]).to include("Total percentage buyer now owns must be more than percentage bought in this transaction") + expect(record.errors["stairowned"]).to include("Total percentage buyer now owns must be more than percentage bought in this transaction.") end end @@ -270,8 +270,8 @@ RSpec.describe Validations::Sales::FinancialValidations do [2, 16, 18, 24].each do |type| record.type = type financial_validator.validate_percentage_bought_at_least_threshold(record) - expect(record.errors["stairbought"]).to eq(["The minimum increase in equity while staircasing is 10%"]) - expect(record.errors["type"]).to eq(["The minimum increase in equity while staircasing is 10% for this shared ownership type"]) + expect(record.errors["stairbought"]).to eq(["The minimum increase in equity while staircasing is 10%."]) + expect(record.errors["type"]).to eq(["The minimum increase in equity while staircasing is 10% for this shared ownership type."]) record.errors.clear end @@ -279,8 +279,8 @@ RSpec.describe Validations::Sales::FinancialValidations do [28, 30, 31, 32].each do |type| record.type = type financial_validator.validate_percentage_bought_at_least_threshold(record) - expect(record.errors["stairbought"]).to eq(["The minimum increase in equity while staircasing is 1%"]) - expect(record.errors["type"]).to eq(["The minimum increase in equity while staircasing is 1% for this shared ownership type"]) + expect(record.errors["stairbought"]).to eq(["The minimum increase in equity while staircasing is 1%."]) + expect(record.errors["type"]).to eq(["The minimum increase in equity while staircasing is 1% for this shared ownership type."]) record.errors.clear end end diff --git a/spec/models/validations/sales/household_validations_spec.rb b/spec/models/validations/sales/household_validations_spec.rb index 12135c585..510681420 100644 --- a/spec/models/validations/sales/household_validations_spec.rb +++ b/spec/models/validations/sales/household_validations_spec.rb @@ -345,8 +345,8 @@ RSpec.describe Validations::Sales::HouseholdValidations do [3, 4, 5, 6, 7, 9, 0].each do |prevten| record.prevten = prevten household_validator.validate_buyer1_previous_tenure(record) - expect(record.errors["prevten"]).to include("Buyer 1’s previous tenure should be “local authority tenant” or “private registered provider or housing association tenant” for discounted sales") - expect(record.errors["ownershipsch"]).to include("Buyer 1’s previous tenure should be “local authority tenant” or “private registered provider or housing association tenant” for discounted sales") + expect(record.errors["prevten"]).to include("Buyer 1’s previous tenure should be “local authority tenant” or “private registered provider or housing association tenant” for discounted sales.") + expect(record.errors["ownershipsch"]).to include("Buyer 1’s previous tenure should be “local authority tenant” or “private registered provider or housing association tenant” for discounted sales.") end end @@ -420,7 +420,7 @@ RSpec.describe Validations::Sales::HouseholdValidations do record.ecstat1 = 9 household_validator.validate_buyer_not_child(record) expect(record.errors["ecstat1"]) - .to include("Buyer 1 cannot have a working situation of child under 16") + .to include("Buyer 1 cannot have a working situation of child under 16.") end it "validates buyer 2 isn't a child" do @@ -428,7 +428,7 @@ RSpec.describe Validations::Sales::HouseholdValidations do record.ecstat2 = 9 household_validator.validate_buyer_not_child(record) expect(record.errors["ecstat2"]) - .to include("Buyer 2 cannot have a working situation of child under 16") + .to include("Buyer 2 cannot have a working situation of child under 16.") end it "allows person 2 to be a child" do diff --git a/spec/models/validations/sales/property_validations_spec.rb b/spec/models/validations/sales/property_validations_spec.rb index 6d4c0c9ae..df0396845 100644 --- a/spec/models/validations/sales/property_validations_spec.rb +++ b/spec/models/validations/sales/property_validations_spec.rb @@ -51,9 +51,9 @@ RSpec.describe Validations::Sales::PropertyValidations do record.ppostcode_full = "SW1A 0AA" record.jointpur = 1 property_validator.validate_postcodes_match_if_discounted_ownership(record) - expect(record.errors["postcode_full"]).to include("Buyers’ last accommodation and discounted ownership postcodes must match") - expect(record.errors["ppostcode_full"]).to include("Buyers’ last accommodation and discounted ownership postcodes must match") - expect(record.errors["ownershipsch"]).to include("Buyers’ last accommodation and discounted ownership postcodes must match") + expect(record.errors["postcode_full"]).to include("Buyers’ last accommodation and discounted ownership postcodes must match.") + expect(record.errors["ppostcode_full"]).to include("Buyers’ last accommodation and discounted ownership postcodes must match.") + expect(record.errors["ownershipsch"]).to include("Buyers’ last accommodation and discounted ownership postcodes must match.") end it "when postcodes do not match an error is added for non joint purchase" do @@ -61,9 +61,9 @@ RSpec.describe Validations::Sales::PropertyValidations do record.ppostcode_full = "SW1A 0AA" record.jointpur = 2 property_validator.validate_postcodes_match_if_discounted_ownership(record) - expect(record.errors["postcode_full"]).to include("Buyer’s last accommodation and discounted ownership postcodes must match") - expect(record.errors["ppostcode_full"]).to include("Buyer’s last accommodation and discounted ownership postcodes must match") - expect(record.errors["ownershipsch"]).to include("Buyer’s last accommodation and discounted ownership postcodes must match") + expect(record.errors["postcode_full"]).to include("Buyer’s last accommodation and discounted ownership postcodes must match.") + expect(record.errors["ppostcode_full"]).to include("Buyer’s last accommodation and discounted ownership postcodes must match.") + expect(record.errors["ownershipsch"]).to include("Buyer’s last accommodation and discounted ownership postcodes must match.") end it "does not add error for 2024 log" do @@ -93,8 +93,8 @@ RSpec.describe Validations::Sales::PropertyValidations do it "does add an error if it's a bedsit" do property_validator.validate_bedsit_number_of_beds(record) - expect(record.errors.added?(:proptype, "Answer cannot be 'Bedsit' if the property has 2 or more bedrooms")).to be true - expect(record.errors.added?(:beds, "Number of bedrooms must be 1 if the property is a bedsit")).to be true + expect(record.errors.added?(:proptype, "Answer cannot be 'Bedsit' if the property has 2 or more bedrooms.")).to be true + expect(record.errors.added?(:beds, "Number of bedrooms must be 1 if the property is a bedsit.")).to be true end it "does not add an error if proptype is undefined" do @@ -120,7 +120,7 @@ RSpec.describe Validations::Sales::PropertyValidations do it "adds an error" do property_validator.validate_uprn(record) - expect(record.errors.added?(:uprn, "UPRN must be 12 digits or less")).to be true + expect(record.errors.added?(:uprn, "UPRN must be 12 digits or less.")).to be true end end @@ -129,7 +129,7 @@ RSpec.describe Validations::Sales::PropertyValidations do it "adds an error" do property_validator.validate_uprn(record) - expect(record.errors.added?(:uprn, "UPRN must be 12 digits or less")).to be true + expect(record.errors.added?(:uprn, "UPRN must be 12 digits or less.")).to be true end end diff --git a/spec/models/validations/sales/sale_information_validations_spec.rb b/spec/models/validations/sales/sale_information_validations_spec.rb index 6cfa71d21..945d1b25b 100644 --- a/spec/models/validations/sales/sale_information_validations_spec.rb +++ b/spec/models/validations/sales/sale_information_validations_spec.rb @@ -147,10 +147,10 @@ RSpec.describe Validations::Sales::SaleInformationValidations do sale_information_validator.validate_exchange_date(record) expect(record.errors[:exdate]).to eq( - ["Contract exchange date must be less than 1 year before sale completion date"], + ["Contract exchange date must be less than 1 year before sale completion date."], ) expect(record.errors[:saledate]).to eq( - ["Sale completion date must be less than 1 year after contract exchange date"], + ["Sale completion date must be less than 1 year after contract exchange date."], ) end end @@ -162,10 +162,10 @@ RSpec.describe Validations::Sales::SaleInformationValidations do sale_information_validator.validate_exchange_date(record) expect(record.errors[:exdate]).to eq( - ["Contract exchange date must be before sale completion date"], + ["Contract exchange date must be before sale completion date."], ) expect(record.errors[:saledate]).to eq( - ["Sale completion date must be after contract exchange date"], + ["Sale completion date must be after contract exchange date."], ) end end @@ -671,7 +671,7 @@ RSpec.describe Validations::Sales::SaleInformationValidations do it "adds an error" do sale_information_validator.validate_grant_amount(record) - expect(record.errors[:grant]).to include("Loan, grants or subsidies must be between £9,000 and £16,000") + expect(record.errors[:grant]).to include("Loan, grants or subsidies must be between £9,000 and £16,000.") end end @@ -681,7 +681,7 @@ RSpec.describe Validations::Sales::SaleInformationValidations do it "adds an error" do sale_information_validator.validate_grant_amount(record) - expect(record.errors[:grant]).to include("Loan, grants or subsidies must be between £9,000 and £16,000") + expect(record.errors[:grant]).to include("Loan, grants or subsidies must be between £9,000 and £16,000.") end end diff --git a/spec/models/validations/sales/setup_validations_spec.rb b/spec/models/validations/sales/setup_validations_spec.rb index a9b3563e6..797b0c22c 100644 --- a/spec/models/validations/sales/setup_validations_spec.rb +++ b/spec/models/validations/sales/setup_validations_spec.rb @@ -91,7 +91,7 @@ RSpec.describe Validations::Sales::SetupValidations do it "adds error" do setup_validator.validate_saledate_collection_year(record) - expect(record.errors[:saledate]).to include("Enter a date within the 23/24 or 24/25 collection years, which is between 1st April 2023 and 31st March 2025") + expect(record.errors[:saledate]).to include("Enter a date within the 23/24 or 24/25 collection years, which is between 1st April 2023 and 31st March 2025.") end end @@ -105,7 +105,7 @@ RSpec.describe Validations::Sales::SetupValidations do it "adds error" do setup_validator.validate_saledate_collection_year(record) - expect(record.errors[:saledate]).to include("Enter a date within the 23/24 or 24/25 collection years, which is between 1st April 2023 and 31st March 2025") + expect(record.errors[:saledate]).to include("Enter a date within the 23/24 or 24/25 collection years, which is between 1st April 2023 and 31st March 2025.") end end @@ -119,7 +119,7 @@ RSpec.describe Validations::Sales::SetupValidations do it "cannot create new logs for the archived collection year" do record.saledate = Time.zone.local(2023, 1, 1) setup_validator.validate_saledate_collection_year(record) - expect(record.errors["saledate"]).to include(match "Enter a date within the 23/24 or 24/25 collection years, which is between 1st April 2023 and 31st March 2025") + expect(record.errors["saledate"]).to include(match "Enter a date within the 23/24 or 24/25 collection years, which is between 1st April 2023 and 31st March 2025.") end it "can edit already created logs for the previous collection year" do @@ -127,7 +127,7 @@ RSpec.describe Validations::Sales::SetupValidations do record.save!(validate: false) record.saledate = Time.zone.local(2024, 1, 1) setup_validator.validate_saledate_collection_year(record) - expect(record.errors["saledate"]).not_to include(match "Enter a date within the 24/25 collection year, which is between 1st April 2024 and 31st March 2025") + expect(record.errors["saledate"]).not_to include(match "Enter a date within the 24/25 collection year, which is between 1st April 2024 and 31st March 2025.") end end @@ -142,7 +142,7 @@ RSpec.describe Validations::Sales::SetupValidations do record.update!(saledate: nil) record.saledate = Time.zone.local(2023, 1, 1) setup_validator.validate_saledate_collection_year(record) - expect(record.errors["saledate"]).to include(match "Enter a date within the 23/24 or 24/25 collection years, which is between 1st April 2023 and 31st March 2025") + expect(record.errors["saledate"]).to include(match "Enter a date within the 23/24 or 24/25 collection years, which is between 1st April 2023 and 31st March 2025.") end it "cannot edit already created logs for the archived collection year" do @@ -150,7 +150,7 @@ RSpec.describe Validations::Sales::SetupValidations do record.save!(validate: false) record.saledate = Time.zone.local(2023, 1, 1) setup_validator.validate_saledate_collection_year(record) - expect(record.errors["saledate"]).to include(match "Enter a date within the 23/24 or 24/25 collection years, which is between 1st April 2023 and 31st March 2025") + expect(record.errors["saledate"]).to include(match "Enter a date within the 23/24 or 24/25 collection years, which is between 1st April 2023 and 31st March 2025.") end end end @@ -183,7 +183,7 @@ RSpec.describe Validations::Sales::SetupValidations do it "adds an error" do setup_validator.validate_saledate_two_weeks(record) - expect(record.errors[:saledate]).to include("Sale completion date must not be later than 14 days from today’s date") + expect(record.errors[:saledate]).to include("Sale completion date must not be later than 14 days from today’s date.") end end end diff --git a/spec/models/validations/setup_validations_spec.rb b/spec/models/validations/setup_validations_spec.rb index 884c4e22e..f39b71328 100644 --- a/spec/models/validations/setup_validations_spec.rb +++ b/spec/models/validations/setup_validations_spec.rb @@ -157,7 +157,7 @@ RSpec.describe Validations::SetupValidations do record.startdate = Time.zone.local(2024, 4, 1) setup_validator.validate_startdate_setup(record) expect(record.errors["startdate"].length).to be >= 2 - expect(record.errors["startdate"][0]).to eq("Enter a date within the 23/24 collection year, which is between 1st April 2023 and 31st March 2024") + expect(record.errors["startdate"][0]).to eq("Enter a date within the 23/24 collection year, which is between 1st April 2023 and 31st March 2024.") expect(record.errors["startdate"][1]).to eq(I18n.t("validations.setup.startdate.later_than_14_days_after")) end end @@ -700,7 +700,7 @@ RSpec.describe Validations::SetupValidations do record.location = location setup_validator.validate_location(record) expect(record.errors["location_id"]) - .to include("This location is incomplete. Select another location or update this one") + .to include("This location is incomplete. Select another location or update this one.") end it "produces no error when location is completes" do diff --git a/spec/models/validations/shared_validations_spec.rb b/spec/models/validations/shared_validations_spec.rb index fb389d175..b4d7fb0b8 100644 --- a/spec/models/validations/shared_validations_spec.rb +++ b/spec/models/validations/shared_validations_spec.rb @@ -75,14 +75,14 @@ RSpec.describe Validations::SharedValidations do sales_log.details_known_2 = 1 sales_log.age2 = 130 shared_validator.validate_numeric_min_max(sales_log) - expect(sales_log.errors["age2"].first).to eq("Person 2’s age must be between 0 and 110") + expect(sales_log.errors["age2"].first).to eq("Person 2’s age must be between 0 and 110.") end it "validates that buyer 2's age is between 0 and 110 for joint purchase" do sales_log.jointpur = 1 sales_log.age2 = 130 shared_validator.validate_numeric_min_max(sales_log) - expect(sales_log.errors["age2"].first).to eq("Buyer 2’s age must be between 16 and 110") + expect(sales_log.errors["age2"].first).to eq("Buyer 2’s age must be between 16 and 110.") end end end diff --git a/spec/requests/lettings_logs_controller_spec.rb b/spec/requests/lettings_logs_controller_spec.rb index 4cd0a0609..f4fa6f1fb 100644 --- a/spec/requests/lettings_logs_controller_spec.rb +++ b/spec/requests/lettings_logs_controller_spec.rb @@ -1570,7 +1570,7 @@ RSpec.describe LettingsLogsController, type: :request do it "returns an error message" do json_response = JSON.parse(response.body) - expect(json_response["errors"]).to eq({ "age1" => ["Lead tenant’s age must be between 16 and 120"] }) + expect(json_response["errors"]).to eq({ "age1" => ["Lead tenant’s age must be between 16 and 120."] }) end end diff --git a/spec/requests/sales_logs_controller_spec.rb b/spec/requests/sales_logs_controller_spec.rb index 1083c7f58..607349a68 100644 --- a/spec/requests/sales_logs_controller_spec.rb +++ b/spec/requests/sales_logs_controller_spec.rb @@ -93,7 +93,7 @@ RSpec.describe SalesLogsController, type: :request do it "validates sales log parameters" do json_response = JSON.parse(response.body) expect(response).to have_http_status(:unprocessable_entity) - expect(json_response["errors"]).to match_array([["beds", ["Number of bedrooms must be 1 if the property is a bedsit"]], ["proptype", ["Answer cannot be 'Bedsit' if the property has 2 or more bedrooms"]]]) + expect(json_response["errors"]).to match_array([["beds", ["Number of bedrooms must be 1 if the property is a bedsit."]], ["proptype", ["Answer cannot be 'Bedsit' if the property has 2 or more bedrooms."]]]) end end end diff --git a/spec/services/bulk_upload/lettings/validator_spec.rb b/spec/services/bulk_upload/lettings/validator_spec.rb index 88f4801f3..a8773b63d 100644 --- a/spec/services/bulk_upload/lettings/validator_spec.rb +++ b/spec/services/bulk_upload/lettings/validator_spec.rb @@ -51,7 +51,7 @@ RSpec.describe BulkUpload::Lettings::Validator do it "is not valid" do expect(validator).not_to be_valid - expect(validator.errors["base"]).to eql(["Incorrect number of fields, please ensure you have used the correct template"]) + expect(validator.errors["base"]).to eql(["Incorrect number of fields, please ensure you have used the correct template."]) end end @@ -68,7 +68,7 @@ RSpec.describe BulkUpload::Lettings::Validator do it "is not valid" do expect(validator).not_to be_valid - expect(validator.errors["base"]).to eql(["Incorrect number of fields, please ensure you have used the correct template"]) + expect(validator.errors["base"]).to eql(["Incorrect number of fields, please ensure you have used the correct template."]) end end end @@ -90,7 +90,7 @@ RSpec.describe BulkUpload::Lettings::Validator do it "is not valid" do expect(validator).not_to be_valid - expect(validator.errors["base"]).to eql(["Incorrect start dates, please ensure you have used the correct template"]) + expect(validator.errors["base"]).to eql(["Incorrect start dates, please ensure you have used the correct template."]) end end end @@ -179,7 +179,7 @@ RSpec.describe BulkUpload::Lettings::Validator do end it "creates errors" do - expect { validator.call }.to change(BulkUploadError.where(category: :setup, error: "This is a duplicate of a log in your file"), :count) + expect { validator.call }.to change(BulkUploadError.where(category: :setup, error: "This is a duplicate of a log in your file."), :count) end end diff --git a/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb index e38396328..18c28189a 100644 --- a/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb @@ -263,7 +263,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do it "adds an error to all (and only) the fields used to determine duplicates" do parser.valid? - error_message = "This is a duplicate log" + error_message = "This is a duplicate log." [ :field_1, # owning_organisation @@ -300,7 +300,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do it "adds an error to all the fields used to determine duplicates" do parser.valid? - error_message = "This is a duplicate log" + error_message = "This is a duplicate log." [ :field_1, # owning_organisation @@ -338,7 +338,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do it "adds an error to all the fields used to determine duplicates" do parser.valid? - error_message = "This is a duplicate log" + error_message = "This is a duplicate log." [ :field_1, # owning_organisation @@ -385,7 +385,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do it "adds an error to all the fields used to determine duplicates" do parser.valid? - error_message = "This is a duplicate log" + error_message = "This is a duplicate log." [ :field_1, # owning_organisation @@ -444,7 +444,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do it "does not add an error to all the fields used to determine duplicates" do parser_too.valid? - error_message = "This is a duplicate log" + error_message = "This is a duplicate log." [ :field_1, # owning_organisation @@ -479,7 +479,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do it "adds an error to all the fields used to determine duplicates" do parser.valid? - error_message = "This is a duplicate log" + error_message = "This is a duplicate log." [ :field_1, # owning_organisation @@ -526,7 +526,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do it "adds an error to all the fields used to determine duplicates" do parser.valid? - error_message = "This is a duplicate log" + error_message = "This is a duplicate log." [ :field_1, # owning_organisation @@ -585,7 +585,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do it "does not add an error to all the fields used to determine duplicates" do parser_too.valid? - error_message = "This is a duplicate log" + error_message = "This is a duplicate log." [ :field_1, # owning_organisation @@ -686,7 +686,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do it "cannot be nulled" do parser.valid? - expect(parser.errors[:field_45]).to eq(["You must show the MHCLG privacy notice to the tenant before you can submit this log"]) + expect(parser.errors[:field_45]).to eq(["You must show the MHCLG privacy notice to the tenant before you can submit this log."]) end end end @@ -979,7 +979,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do parser.valid? expect(parser.errors[:field_15]).to be_blank - expect(parser.errors.where(:field_16, category: :setup).map(&:message)).to eq(["This scheme code does not belong to the owning organisation or managing organisation"]) + expect(parser.errors.where(:field_16, category: :setup).map(&:message)).to eq(["This scheme code does not belong to the owning organisation or managing organisation."]) expect(parser.errors[:field_17]).to be_blank end end @@ -1018,7 +1018,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do expect(parser.errors[:field_15]).to be_blank expect(parser.errors[:field_16]).to be_blank - expect(parser.errors.where(:field_17, category: :setup).map(&:message)).to eq(["Location code must relate to a location that is owned by the owning organisation or managing organisation"]) + expect(parser.errors.where(:field_17, category: :setup).map(&:message)).to eq(["Location code must relate to a location that is owned by the owning organisation or managing organisation."]) end end @@ -1054,7 +1054,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do expect(parser.errors[:field_15]).to be_blank expect(parser.errors[:field_16]).to be_blank - expect(parser.errors.where(:field_17, category: :setup).map(&:message)).to eq(["Location code must relate to a location that is owned by the owning organisation or managing organisation"]) + expect(parser.errors.where(:field_17, category: :setup).map(&:message)).to eq(["Location code must relate to a location that is owned by the owning organisation or managing organisation."]) end end @@ -1067,7 +1067,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do parser.valid? expect(parser.errors[:field_15]).to be_blank - expect(parser.errors.where(:field_16, category: :setup).map(&:message)).to eq(["This scheme code does not belong to the owning organisation or managing organisation"]) + expect(parser.errors.where(:field_16, category: :setup).map(&:message)).to eq(["This scheme code does not belong to the owning organisation or managing organisation."]) expect(parser.errors[:field_17]).to be_blank end end @@ -1106,8 +1106,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do parser.valid? expect(parser.errors[:field_15]).to be_blank - expect(parser.errors.where(:field_16).map(&:message)).to eq(["This location is incomplete. Select another location or update this one"]) - expect(parser.errors.where(:field_17).map(&:message)).to eq(["This location is incomplete. Select another location or update this one"]) + expect(parser.errors.where(:field_16).map(&:message)).to eq(["This location is incomplete. Select another location or update this one."]) + expect(parser.errors.where(:field_17).map(&:message)).to eq(["This location is incomplete. Select another location or update this one."]) end end end @@ -1122,7 +1122,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do it "returns a setup error" do parser.valid? - expect(parser.errors.where(:field_15, category: :setup).map(&:message)).to eq(["This management group code does not belong to the owning organisation or managing organisation"]) + expect(parser.errors.where(:field_15, category: :setup).map(&:message)).to eq(["This management group code does not belong to the owning organisation or managing organisation."]) expect(parser.errors[:field_16]).to be_blank expect(parser.errors[:field_17]).to be_blank end @@ -1147,7 +1147,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do parser.valid? expect(parser.errors[:field_15]).to be_blank - expect(parser.errors.where(:field_16, category: :setup).map(&:message)).to eq(["Scheme code must relate to a scheme that is owned by the owning organisation or managing organisation"]) + expect(parser.errors.where(:field_16, category: :setup).map(&:message)).to eq(["Scheme code must relate to a scheme that is owned by the owning organisation or managing organisation."]) expect(parser.errors[:field_17]).to be_blank end end @@ -1173,7 +1173,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do parser.valid? expect(parser.errors[:field_15]).to be_blank - expect(parser.errors.where(:field_16, category: :setup).map(&:message)).to eq(["Scheme code must relate to a scheme that is owned by the owning organisation or managing organisation"]) + expect(parser.errors.where(:field_16, category: :setup).map(&:message)).to eq(["Scheme code must relate to a scheme that is owned by the owning organisation or managing organisation."]) expect(parser.errors[:field_17]).to be_blank end end @@ -1186,7 +1186,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do it "returns a setup error" do parser.valid? - expect(parser.errors.where(:field_15, category: :setup).map(&:message)).to eq(["This management group code does not belong to the owning organisation or managing organisation"]) + expect(parser.errors.where(:field_15, category: :setup).map(&:message)).to eq(["This management group code does not belong to the owning organisation or managing organisation."]) expect(parser.errors[:field_16]).to be_blank expect(parser.errors[:field_17]).to be_blank end @@ -1480,7 +1480,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do it "returns an error" do parser.valid? - expect(parser.errors[:field_9]).to include("Tenancy start year must be 2 digits") + expect(parser.errors[:field_9]).to include("Tenancy start year must be 2 digits.") end end @@ -1543,7 +1543,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do parser.valid? setup_errors = parser.errors.select { |e| e.options[:category] == :setup } - expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code is incorrect") + expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code is incorrect.") end it "blocks log creation" do @@ -1561,7 +1561,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do parser.valid? setup_errors = parser.errors.select { |e| e.options[:category] == :setup } - expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code provided is for an organisation that does not own stock") + expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code provided is for an organisation that does not own stock.") end it "blocks log creation" do @@ -1579,7 +1579,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do parser.valid? setup_errors = parser.errors.select { |e| e.options[:category] == :setup } - expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("You do not have permission to add logs for this owning organisation") + expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("You do not have permission to add logs for this owning organisation.") end it "blocks log creation" do @@ -1642,7 +1642,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do parser.valid? setup_errors = parser.errors.select { |e| e.options[:category] == :setup } - expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("The managing organisation code is incorrect") + expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("The managing organisation code is incorrect.") end it "blocks log creation" do @@ -1658,7 +1658,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do parser.valid? setup_errors = parser.errors.select { |e| e.options[:category] == :setup } - expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("The managing organisation code is incorrect") + expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("The managing organisation code is incorrect.") end it "blocks log creation" do @@ -1676,7 +1676,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do parser.valid? setup_errors = parser.errors.select { |e| e.options[:category] == :setup } - expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("This managing organisation does not have a relationship with the owning organisation") + expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("This managing organisation does not have a relationship with the owning organisation.") end it "blocks log creation" do @@ -2710,8 +2710,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do it "sets error on housingneeds a and b" do parser.valid? - expect(parser.errors[:field_83]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected") - expect(parser.errors[:field_84]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected") + expect(parser.errors[:field_83]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.") + expect(parser.errors[:field_84]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.") expect(parser.errors[:field_85]).to be_blank end end @@ -2721,8 +2721,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do it "sets error on housingneeds a and c" do parser.valid? - expect(parser.errors[:field_83]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected") - expect(parser.errors[:field_85]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected") + expect(parser.errors[:field_83]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.") + expect(parser.errors[:field_85]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.") expect(parser.errors[:field_84]).to be_blank end end @@ -2732,8 +2732,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do it "sets error on housingneeds b and c" do parser.valid? - expect(parser.errors[:field_84]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected") - expect(parser.errors[:field_85]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected") + expect(parser.errors[:field_84]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.") + expect(parser.errors[:field_85]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.") expect(parser.errors[:field_83]).to be_blank end end @@ -2743,8 +2743,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do it "sets error on housingneeds a and g" do parser.valid? - expect(parser.errors[:field_87]).to include("No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs") - expect(parser.errors[:field_83]).to include("No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs") + expect(parser.errors[:field_87]).to include("No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs.") + expect(parser.errors[:field_83]).to include("No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs.") expect(parser.errors[:field_84]).to be_blank expect(parser.errors[:field_85]).to be_blank end @@ -2767,8 +2767,8 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do it "sets error on housingneeds a and h" do parser.valid? - expect(parser.errors[:field_88]).to include("Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs") - expect(parser.errors[:field_83]).to include("Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs") + expect(parser.errors[:field_88]).to include("Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs.") + expect(parser.errors[:field_83]).to include("Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs.") expect(parser.errors[:field_84]).to be_blank expect(parser.errors[:field_85]).to be_blank end 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 68de5d22b..3910e1281 100644 --- a/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2024/row_parser_spec.rb @@ -283,7 +283,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "adds an error to all (and only) the fields used to determine duplicates" do parser.valid? - error_message = "This is a duplicate log" + error_message = "This is a duplicate log." [ :field_1, # owning_organisation @@ -339,7 +339,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "adds an error to all the fields used to determine duplicates" do parser.valid? - error_message = "This is a duplicate log" + error_message = "This is a duplicate log." [ :field_1, # owning_organisation @@ -381,7 +381,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "adds an error to all the fields used to determine duplicates" do parser.valid? - error_message = "This is a duplicate log" + error_message = "This is a duplicate log." [ :field_1, # owning_organisation @@ -432,7 +432,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "adds an error to all the fields used to determine duplicates" do parser.valid? - error_message = "This is a duplicate log" + error_message = "This is a duplicate log." [ :field_1, # owning_organisation @@ -492,7 +492,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "does not add an error to all the fields used to determine duplicates" do parser_too.valid? - error_message = "This is a duplicate log" + error_message = "This is a duplicate log." [ :field_1, # owning_organisation @@ -527,7 +527,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "adds an error to all the fields used to determine duplicates" do parser.valid? - error_message = "This is a duplicate log" + error_message = "This is a duplicate log." [ :field_1, # owning_organisation @@ -574,7 +574,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "adds an error to all the fields used to determine duplicates" do parser.valid? - error_message = "This is a duplicate log" + error_message = "This is a duplicate log." [ :field_1, # owning_organisation @@ -634,7 +634,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "does not add an error to all the fields used to determine duplicates" do parser_too.valid? - error_message = "This is a duplicate log" + error_message = "This is a duplicate log." [ :field_1, # owning_organisation @@ -747,7 +747,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "cannot be nulled" do parser.valid? - expect(parser.errors[:field_15]).to eq(["You must show or give the tenant access to the MHCLG privacy notice before you can submit this log"]) + expect(parser.errors[:field_15]).to eq(["You must show or give the tenant access to the MHCLG privacy notice before you can submit this log."]) end end @@ -932,7 +932,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do let(:attributes) { { bulk_upload:, field_1: owning_org.old_visible_id, field_2: owning_org.old_visible_id, field_4: "2", field_11: "2", field_5: "S123", field_6: location.id } } it "returns a setup error" do - expect(parser.errors.where(:field_5, category: :setup).map(&:message)).to eq(["This scheme code does not belong to the owning organisation or managing organisation"]) + expect(parser.errors.where(:field_5, category: :setup).map(&:message)).to eq(["This scheme code does not belong to the owning organisation or managing organisation."]) expect(parser.errors[:field_6]).to be_blank end end @@ -952,7 +952,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "returns a setup error" do expect(parser.errors[:field_5]).to be_blank - expect(parser.errors.where(:field_6, category: :setup).map(&:message)).to eq(["Location code must relate to a location that is owned by the owning organisation or managing organisation"]) + expect(parser.errors.where(:field_6, category: :setup).map(&:message)).to eq(["Location code must relate to a location that is owned by the owning organisation or managing organisation."]) end end @@ -981,7 +981,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "returns a setup error" do expect(parser.errors[:field_5]).to be_blank - expect(parser.errors.where(:field_6, category: :setup).map(&:message)).to eq(["Location code must relate to a location that is owned by the owning organisation or managing organisation"]) + expect(parser.errors.where(:field_6, category: :setup).map(&:message)).to eq(["Location code must relate to a location that is owned by the owning organisation or managing organisation."]) end end @@ -991,7 +991,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do let(:attributes) { { bulk_upload:, field_4: "2", field_11: "2", field_5: "S#{other_scheme.id}", field_6: other_location.id, field_1: owning_org.old_visible_id, field_2: owning_org.old_visible_id } } it "returns a setup error" do - expect(parser.errors.where(:field_5, category: :setup).map(&:message)).to eq(["This scheme code does not belong to the owning organisation or managing organisation"]) + expect(parser.errors.where(:field_5, category: :setup).map(&:message)).to eq(["This scheme code does not belong to the owning organisation or managing organisation."]) expect(parser.errors[:field_6]).to be_blank end end @@ -1021,8 +1021,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do let(:attributes) { { bulk_upload:, field_1: owning_org.old_visible_id, field_2: owning_org.old_visible_id, field_4: "2", field_11: "2", field_5: "S#{scheme.id}", field_6: incomplete_location.id } } it "returns a setup error for scheme" do - expect(parser.errors.where(:field_5).map(&:message)).to eq(["This location is incomplete. Select another location or update this one"]) - expect(parser.errors.where(:field_6).map(&:message)).to eq(["This location is incomplete. Select another location or update this one"]) + expect(parser.errors.where(:field_5).map(&:message)).to eq(["This location is incomplete. Select another location or update this one."]) + expect(parser.errors.where(:field_6).map(&:message)).to eq(["This location is incomplete. Select another location or update this one."]) end end end @@ -1071,7 +1071,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "is not permitted" do parser.valid? - expect(parser.errors[:field_98]).to include('The reason for leaving must be "End of social or private sector tenancy - no fault", "End of social or private sector tenancy - evicted due to anti-social behaviour (ASB)", "End of social or private sector tenancy - evicted due to rent arrears" or "End of social or private sector tenancy - evicted for any other reason"') + expect(parser.errors[:field_98]).to include('The reason for leaving must be "End of social or private sector tenancy - no fault", "End of social or private sector tenancy - evicted due to anti-social behaviour (ASB)", "End of social or private sector tenancy - evicted due to rent arrears" or "End of social or private sector tenancy - evicted for any other reason".') end end end @@ -1297,7 +1297,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "returns an error" do parser.valid? - expect(parser.errors[:field_10]).to include("Tenancy start year must be 2 digits") + expect(parser.errors[:field_10]).to include("Tenancy start year must be 2 digits.") end end @@ -1365,7 +1365,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do setup_errors = parser.errors.select { |e| e.options[:category] == :setup } - expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code is incorrect") + expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code is incorrect.") end it "blocks log creation" do @@ -1384,7 +1384,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do setup_errors = parser.errors.select { |e| e.options[:category] == :setup } - expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code provided is for an organisation that does not own stock") + expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code provided is for an organisation that does not own stock.") end it "blocks log creation" do @@ -1403,7 +1403,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do setup_errors = parser.errors.select { |e| e.options[:category] == :setup } - expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("You do not have permission to add logs for this owning organisation") + expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("You do not have permission to add logs for this owning organisation.") end it "blocks log creation" do @@ -1472,7 +1472,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do parser = described_class.new(attributes) parser.valid? expect(parser).to be_block_log_creation - expect(parser.errors[:field_1]).to include("You do not have permission to add logs for this owning organisation") + expect(parser.errors[:field_1]).to include("You do not have permission to add logs for this owning organisation.") end end @@ -1490,7 +1490,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "does not block log creation and does not add an error to field_1" do parser = described_class.new(attributes) parser.valid? - expect(parser.errors[:field_1]).not_to include("You do not have permission to add logs for this owning organisation") + expect(parser.errors[:field_1]).not_to include("You do not have permission to add logs for this owning organisation.") end end end @@ -1521,7 +1521,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do setup_errors = parser.errors.select { |e| e.options[:category] == :setup } - expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("The managing organisation code is incorrect") + expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("The managing organisation code is incorrect.") end it "blocks log creation" do @@ -1540,7 +1540,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do setup_errors = parser.errors.select { |e| e.options[:category] == :setup } - expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("This managing organisation does not have a relationship with the owning organisation") + expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("This managing organisation does not have a relationship with the owning organisation.") end it "blocks log creation" do @@ -1602,7 +1602,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "adds an appropriate error to the UPRN field" do parser.valid? - expect(parser.errors[:field_16]).to eql(["UPRN must be 12 digits or less"]) + expect(parser.errors[:field_16]).to eql(["UPRN must be 12 digits or less."]) end it "adds errors to missing key address fields" do @@ -1619,7 +1619,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "adds an error to the UPRN field only" do parser.valid? - expect(parser.errors[:field_16]).to eql(["UPRN must be 12 digits or less"]) + expect(parser.errors[:field_16]).to eql(["UPRN must be 12 digits or less."]) %i[field_17 field_19 field_21 field_22].each do |field| expect(parser.errors[field]).to be_empty end @@ -1844,7 +1844,7 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "adds an error to field_45" do parser.valid? - expect(parser.errors["field_45"]).to include("Select a valid nationality") + expect(parser.errors["field_45"]).to include("Select a valid nationality.") end end end @@ -2813,8 +2813,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "sets error on housingneeds a and b" do parser.valid? - expect(parser.errors[:field_79]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected") - expect(parser.errors[:field_80]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected") + expect(parser.errors[:field_79]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.") + expect(parser.errors[:field_80]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.") expect(parser.errors[:field_81]).to be_blank end end @@ -2824,8 +2824,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "sets error on housingneeds a and c" do parser.valid? - expect(parser.errors[:field_79]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected") - expect(parser.errors[:field_81]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected") + expect(parser.errors[:field_79]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.") + expect(parser.errors[:field_81]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.") expect(parser.errors[:field_80]).to be_blank end end @@ -2835,8 +2835,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "sets error on housingneeds b and c" do parser.valid? - expect(parser.errors[:field_80]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected") - expect(parser.errors[:field_81]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected") + expect(parser.errors[:field_80]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.") + expect(parser.errors[:field_81]).to include("Only one disabled access need: fully wheelchair-accessible housing, wheelchair access to essential rooms or level access housing, can be selected.") expect(parser.errors[:field_79]).to be_blank end end @@ -2846,8 +2846,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "sets error on housingneeds a and g" do parser.valid? - expect(parser.errors[:field_83]).to include("No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs") - expect(parser.errors[:field_79]).to include("No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs") + expect(parser.errors[:field_83]).to include("No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs.") + expect(parser.errors[:field_79]).to include("No disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs.") expect(parser.errors[:field_80]).to be_blank expect(parser.errors[:field_81]).to be_blank end @@ -2870,8 +2870,8 @@ RSpec.describe BulkUpload::Lettings::Year2024::RowParser do it "sets error on housingneeds a and h" do parser.valid? - expect(parser.errors[:field_84]).to include("Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs") - expect(parser.errors[:field_79]).to include("Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs") + expect(parser.errors[:field_84]).to include("Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs.") + expect(parser.errors[:field_79]).to include("Don’t know disabled access needs can’t be selected if you have selected fully wheelchair-accessible housing, wheelchair access to essential rooms, level access housing or other disabled access needs.") expect(parser.errors[:field_80]).to be_blank expect(parser.errors[:field_81]).to be_blank end diff --git a/spec/services/bulk_upload/sales/validator_spec.rb b/spec/services/bulk_upload/sales/validator_spec.rb index c5a35bd56..f968f5661 100644 --- a/spec/services/bulk_upload/sales/validator_spec.rb +++ b/spec/services/bulk_upload/sales/validator_spec.rb @@ -54,7 +54,7 @@ RSpec.describe BulkUpload::Sales::Validator do it "is not valid" do expect(validator).not_to be_valid - expect(validator.errors["base"]).to eql(["Incorrect sale dates, please ensure you have used the correct template"]) + expect(validator.errors["base"]).to eql(["Incorrect sale dates, please ensure you have used the correct template."]) end end @@ -199,7 +199,7 @@ RSpec.describe BulkUpload::Sales::Validator do end it "creates errors" do - expect { validator.call }.to change(BulkUploadError.where(category: :setup, error: "This is a duplicate of a log in your file"), :count).by(20) + expect { validator.call }.to change(BulkUploadError.where(category: :setup, error: "This is a duplicate of a log in your file."), :count).by(20) end end end diff --git a/spec/services/bulk_upload/sales/year2023/row_parser_spec.rb b/spec/services/bulk_upload/sales/year2023/row_parser_spec.rb index 6e193978e..7d7c62885 100644 --- a/spec/services/bulk_upload/sales/year2023/row_parser_spec.rb +++ b/spec/services/bulk_upload/sales/year2023/row_parser_spec.rb @@ -432,7 +432,7 @@ RSpec.describe BulkUpload::Sales::Year2023::RowParser do let(:attributes) { { bulk_upload:, field_1: unaffiliated_org.old_visible_id } } it "is not permitted as setup error" do - expect(parser.errors.where(:field_1, category: :setup).map(&:message)).to eql(["You do not have permission to add logs for this owning organisation"]) + expect(parser.errors.where(:field_1, category: :setup).map(&:message)).to eql(["You do not have permission to add logs for this owning organisation."]) end it "blocks log creation" do @@ -597,7 +597,7 @@ RSpec.describe BulkUpload::Sales::Year2023::RowParser do let(:attributes) { setup_section_params.merge({ bulk_upload:, field_5: "2022" }) } it "returns a setup error" do - expect(parser.errors.where(:field_5, category: :setup).map(&:message)).to include("Sale completion year must be 2 digits") + expect(parser.errors.where(:field_5, category: :setup).map(&:message)).to include("Sale completion year must be 2 digits.") end end @@ -661,7 +661,7 @@ RSpec.describe BulkUpload::Sales::Year2023::RowParser do it "adds an error to all (and only) the fields used to determine duplicates" do parser.valid? - error_message = "This is a duplicate log" + error_message = "This is a duplicate log." [ :field_1, # Owning org @@ -969,7 +969,7 @@ RSpec.describe BulkUpload::Sales::Year2023::RowParser do let(:attributes) { valid_attributes.merge({ field_35: "9" }) } it "a custom validation is applied" do - validation_message = "Buyer 1 cannot be a child under 16" + validation_message = "Buyer 1 cannot be a child under 16." expect(parser.errors[:field_35]).to include validation_message end end @@ -1121,7 +1121,7 @@ RSpec.describe BulkUpload::Sales::Year2023::RowParser do it "only adds errors to the discounted ownership field" do expect(parser.errors[:field_105]).to be_empty - expect(parser.errors[:field_119]).to include("Mortgage, deposit, and grant total must equal £90.00. Your given mortgage, deposit and grant total is £100.00") + expect(parser.errors[:field_119]).to include("Mortgage, deposit, and grant total must equal £90.00. Your given mortgage, deposit and grant total is £100.00.") expect(parser.errors[:field_128]).to be_empty end end @@ -1409,7 +1409,7 @@ RSpec.describe BulkUpload::Sales::Year2023::RowParser do parser.valid? setup_errors = parser.errors.select { |e| e.options[:category] == :setup } - expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("This user belongs to an organisation that does not have a relationship with the owning organisation") + expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("This user belongs to an organisation that does not have a relationship with the owning organisation.") end it "blocks log creation" do @@ -1431,7 +1431,7 @@ RSpec.describe BulkUpload::Sales::Year2023::RowParser do parser.valid? setup_errors = parser.errors.select { |e| e.options[:category] == :setup } - expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code provided is for an organisation that does not own stock") + expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code provided is for an organisation that does not own stock.") end it "blocks log creation" do 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 ca8f29e92..b74545a9a 100644 --- a/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb +++ b/spec/services/bulk_upload/sales/year2024/row_parser_spec.rb @@ -467,7 +467,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do it "is not permitted as a setup error" do parser.valid? - expect(parser.errors.where(:field_1, category: :setup).map(&:message)).to eql(["The owning organisation code is incorrect"]) + expect(parser.errors.where(:field_1, category: :setup).map(&:message)).to eql(["The owning organisation code is incorrect."]) end it "blocks log creation" do @@ -483,7 +483,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do it "is not permitted as setup error" do parser.valid? - expect(parser.errors.where(:field_1, category: :setup).map(&:message)).to eql(["You do not have permission to add logs for this owning organisation"]) + expect(parser.errors.where(:field_1, category: :setup).map(&:message)).to eql(["You do not have permission to add logs for this owning organisation."]) end it "blocks log creation" do @@ -570,7 +570,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do parser = described_class.new(attributes) parser.valid? expect(parser).to be_block_log_creation - expect(parser.errors[:field_1]).to include("You do not have permission to add logs for this owning organisation") + expect(parser.errors[:field_1]).to include("You do not have permission to add logs for this owning organisation.") end end @@ -588,7 +588,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do it "does not block log creation and does not add an error to field_1" do parser = described_class.new(attributes) parser.valid? - expect(parser.errors[:field_1]).not_to include("You do not have permission to add logs for this owning organisation") + expect(parser.errors[:field_1]).not_to include("You do not have permission to add logs for this owning organisation.") end end end @@ -716,7 +716,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do it "returns a setup error" do parser.valid? - expect(parser.errors.where(:field_6, category: :setup).map(&:message)).to include("Sale completion year must be 2 digits") + expect(parser.errors.where(:field_6, category: :setup).map(&:message)).to include("Sale completion year must be 2 digits.") end end @@ -782,7 +782,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do it "adds an error to all (and only) the fields used to determine duplicates" do parser.valid? - error_message = "This is a duplicate log" + error_message = "This is a duplicate log." [ :field_1, # Owning org @@ -871,7 +871,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do it "returns correct error" do parser.valid? - expect(parser.errors.where(:field_116).map(&:message)).to include("Percentage discount must be between 0% and 70%") + expect(parser.errors.where(:field_116).map(&:message)).to include("Percentage discount must be between 0% and 70%.") end end @@ -889,7 +889,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do it "returns correct error" do parser.valid? - expect(parser.errors.where(:field_116).map(&:message)).to include("Percentage discount must be between 0% and 70%") + expect(parser.errors.where(:field_116).map(&:message)).to include("Percentage discount must be between 0% and 70%.") end end end @@ -986,7 +986,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do it "adds an appropriate error to the UPRN field" do parser.valid? - expect(parser.errors[:field_22]).to eql(["UPRN must be 12 digits or less"]) + expect(parser.errors[:field_22]).to eql(["UPRN must be 12 digits or less."]) end it "adds errors to missing key address fields" do @@ -1003,7 +1003,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do it "adds an error to the UPRN field only" do parser.valid? - expect(parser.errors[:field_22]).to eql(["UPRN must be 12 digits or less"]) + expect(parser.errors[:field_22]).to eql(["UPRN must be 12 digits or less."]) %i[field_23 field_25 field_27 field_28].each do |field| expect(parser.errors[field]).to be_empty end @@ -1108,7 +1108,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do context "when the privacy notice is not accepted" do it "cannot be nulled" do - expect(parser.errors[:field_18]).to eq(["You must show or give the buyer access to the MHCLG privacy notice before you can submit this log"]) + expect(parser.errors[:field_18]).to eq(["You must show or give the buyer access to the MHCLG privacy notice before you can submit this log."]) end end end @@ -1198,7 +1198,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do it "a custom validation is applied" do parser.valid? - validation_message = "Buyer 2 cannot have a working situation of child under 16" + validation_message = "Buyer 2 cannot have a working situation of child under 16." expect(parser.errors[:field_42]).to include validation_message end end @@ -1209,7 +1209,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do it "a custom validation is applied" do parser.valid? - validation_message = "Buyer 2’s age must be between 16 and 110" + validation_message = "Buyer 2’s age must be between 16 and 110." expect(parser.errors[:field_38]).to include validation_message end end @@ -1220,7 +1220,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do it "a custom validation is applied" do parser.valid? - validation_message = "Buyer 2's age cannot be 16 or over if their working situation is child under 16" + validation_message = "Buyer 2's age cannot be 16 or over if their working situation is child under 16." expect(parser.errors[:field_42]).to include validation_message expect(parser.errors[:field_38]).to include validation_message end @@ -1244,7 +1244,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do it "a custom validation is applied" do parser.valid? - validation_message = "Buyer 1 cannot have a working situation of child under 16" + validation_message = "Buyer 1 cannot have a working situation of child under 16." expect(parser.errors[:field_35]).to include validation_message end end @@ -1255,7 +1255,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do it "a custom validation is applied" do parser.valid? - validation_message = "Buyer 1’s age must be between 16 and 110" + validation_message = "Buyer 1’s age must be between 16 and 110." expect(parser.errors[:field_31]).to include validation_message end end @@ -1266,7 +1266,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do it "a custom validation is applied" do parser.valid? - validation_message = "Buyer 1's age cannot be 16 or over if their working situation is child under 16" + validation_message = "Buyer 1's age cannot be 16 or over if their working situation is child under 16." expect(parser.errors[:field_35]).to include validation_message expect(parser.errors[:field_31]).to include validation_message end @@ -1676,7 +1676,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do parser.valid? expect(parser.log.nationality_all).to be(nil) expect(parser.log.nationality_all_group).to be(nil) - expect(parser.errors["field_34"]).to include("Select a valid nationality") + expect(parser.errors["field_34"]).to include("Select a valid nationality.") end end end @@ -1761,7 +1761,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do parser.valid? expect(parser.log.nationality_all_buyer2).to be(nil) expect(parser.log.nationality_all_buyer2_group).to be(nil) - expect(parser.errors["field_41"]).to include("Select a valid nationality") + expect(parser.errors["field_41"]).to include("Select a valid nationality.") end end end @@ -1997,7 +1997,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do parser.valid? setup_errors = parser.errors.select { |e| e.options[:category] == :setup } - expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("This organisation does not have a relationship with the owning organisation") + expect(setup_errors.find { |e| e.attribute == :field_2 }.message).to eql("This organisation does not have a relationship with the owning organisation.") end it "blocks log creation" do @@ -2019,7 +2019,7 @@ RSpec.describe BulkUpload::Sales::Year2024::RowParser do parser.valid? setup_errors = parser.errors.select { |e| e.options[:category] == :setup } - expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code provided is for an organisation that does not own stock") + expect(setup_errors.find { |e| e.attribute == :field_1 }.message).to eql("The owning organisation code provided is for an organisation that does not own stock.") end it "blocks log creation" do From 961f1b7f93dfa3dd83d3d48a3b601ee696f4f362 Mon Sep 17 00:00:00 2001 From: Rachael Booth Date: Thu, 3 Oct 2024 10:24:40 +0100 Subject: [PATCH 14/14] CLDC-3423: Add sales soft validation for person over retirement age not retired (#2644) * CLDC-3423: Add sales soft validation for person over retirement age not retired * Add rake task to recalculate log status when new validation is triggered --- .../sales/pages/not_retired_value_check.rb | 25 +++++++++ .../questions/not_retired_value_check.rb | 24 ++++++++ .../subsections/household_characteristics.rb | 21 ++++--- ..._sales_over_retirement_age_validation.rake | 20 +++++++ ...les_over_retirement_age_validation_spec.rb | 55 +++++++++++++++++++ .../household_characteristics_spec.rb | 35 +++++------- 6 files changed, 152 insertions(+), 28 deletions(-) create mode 100644 app/models/form/sales/pages/not_retired_value_check.rb create mode 100644 app/models/form/sales/questions/not_retired_value_check.rb create mode 100644 lib/tasks/recalculate_status_after_sales_over_retirement_age_validation.rake create mode 100644 spec/lib/tasks/recalculate_status_after_sales_over_retirement_age_validation_spec.rb diff --git a/app/models/form/sales/pages/not_retired_value_check.rb b/app/models/form/sales/pages/not_retired_value_check.rb new file mode 100644 index 000000000..193aa6e7d --- /dev/null +++ b/app/models/form/sales/pages/not_retired_value_check.rb @@ -0,0 +1,25 @@ +class Form::Sales::Pages::NotRetiredValueCheck < Form::Sales::Pages::Person + def initialize(id, hsh, subsection, person_index:) + super + @depends_on = [ + { + "person_#{person_index}_not_retired_over_soft_max_age?" => true, + }, + ] + @person_index = person_index + @title_text = { + "translation" => "soft_validations.retirement.max.title", + } + @informative_text = { + "translation" => "soft_validations.retirement.max.hint_text", + } + end + + def questions + @questions ||= [Form::Sales::Questions::NotRetiredValueCheck.new(nil, nil, self, person_index: @person_index)] + end + + def interruption_screen_question_ids + %W[age#{@person_index} ecstat#{@person_index}] + end +end diff --git a/app/models/form/sales/questions/not_retired_value_check.rb b/app/models/form/sales/questions/not_retired_value_check.rb new file mode 100644 index 000000000..58c95c1bb --- /dev/null +++ b/app/models/form/sales/questions/not_retired_value_check.rb @@ -0,0 +1,24 @@ +class Form::Sales::Questions::NotRetiredValueCheck < ::Form::Question + def initialize(id, hsh, page, person_index:) + super(id, hsh, page) + @id = "retirement_value_check" + @check_answer_label = "Retirement confirmation" + @type = "interruption_screen" + @answer_options = { + "0" => { "value" => "Yes" }, + "1" => { "value" => "No" }, + } + @hidden_in_check_answers = { + "depends_on" => [ + { + "retirement_value_check" => 0, + }, + { + "retirement_value_check" => 1, + }, + ], + } + @check_answers_card_number = person_index + @header = "Are you sure this person isn't retired?" + end +end diff --git a/app/models/form/sales/subsections/household_characteristics.rb b/app/models/form/sales/subsections/household_characteristics.rb index 40e41201b..afbbe4278 100644 --- a/app/models/form/sales/subsections/household_characteristics.rb +++ b/app/models/form/sales/subsections/household_characteristics.rb @@ -14,10 +14,10 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection (Form::Sales::Pages::PrivacyNotice.new("privacy_notice", nil, self, joint_purchase: false) unless form.start_year_after_2024?), Form::Sales::Pages::Age1.new(nil, nil, self), Form::Sales::Pages::RetirementValueCheck.new("age_1_retirement_value_check", nil, self, person_index: 1), + (Form::Sales::Pages::NotRetiredValueCheck.new("age_1_not_retired_value_check", nil, self, person_index: 1) if form.start_year_after_2024?), Form::Sales::Pages::OldPersonsSharedOwnershipValueCheck.new("age_1_old_persons_shared_ownership_joint_purchase_value_check", nil, self, joint_purchase: true), Form::Sales::Pages::OldPersonsSharedOwnershipValueCheck.new("age_1_old_persons_shared_ownership_value_check", nil, self, joint_purchase: false), Form::Sales::Pages::GenderIdentity1.new(nil, nil, self), - Form::Sales::Pages::RetirementValueCheck.new("gender_1_retirement_value_check", nil, self, person_index: 1), Form::Sales::Pages::Buyer1EthnicGroup.new(nil, nil, self), Form::Sales::Pages::Buyer1EthnicBackgroundBlack.new(nil, nil, self), Form::Sales::Pages::Buyer1EthnicBackgroundAsian.new(nil, nil, self), @@ -27,6 +27,7 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection Form::Sales::Pages::Buyer1Nationality.new(nil, nil, self), 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_after_2024?), Form::Sales::Pages::Buyer1IncomeMinValueCheck.new("working_situation_buyer_1_income_min_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), @@ -36,12 +37,13 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection Form::Sales::Pages::OldPersonsSharedOwnershipValueCheck.new("age_2_old_persons_shared_ownership_joint_purchase_value_check", nil, self, joint_purchase: true), Form::Sales::Pages::OldPersonsSharedOwnershipValueCheck.new("age_2_old_persons_shared_ownership_value_check", nil, self, joint_purchase: false), Form::Sales::Pages::RetirementValueCheck.new("age_2_buyer_retirement_value_check", nil, self, person_index: 2), + (Form::Sales::Pages::NotRetiredValueCheck.new("age_2_buyer_not_retired_value_check", nil, self, person_index: 2) if form.start_year_after_2024?), Form::Sales::Pages::PersonStudentNotChildValueCheck.new("buyer_2_age_student_not_child_value_check", nil, self, person_index: 2), Form::Sales::Pages::GenderIdentity2.new(nil, nil, self), - Form::Sales::Pages::RetirementValueCheck.new("gender_2_buyer_retirement_value_check", nil, self, person_index: 2), buyer_2_ethnicity_nationality_pages, 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_after_2024?), Form::Sales::Pages::Buyer2IncomeMinValueCheck.new("working_situation_buyer_2_income_min_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), @@ -55,12 +57,13 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection Form::Sales::Pages::PersonStudentNotChildValueCheck.new("relationship_2_student_not_child_value_check", nil, self, person_index: 2), Form::Sales::Pages::PersonAge.new("person_2_age", nil, self, person_index: 2), Form::Sales::Pages::RetirementValueCheck.new("age_2_retirement_value_check", nil, self, person_index: 2), + (Form::Sales::Pages::NotRetiredValueCheck.new("age_2_not_retired_value_check", nil, self, person_index: 2) if form.start_year_after_2024?), Form::Sales::Pages::PersonStudentNotChildValueCheck.new("age_2_student_not_child_value_check", nil, self, person_index: 2), (Form::Sales::Pages::PartnerUnder16ValueCheck.new("age_2_partner_under_16_value_check", nil, self, person_index: 2) if form.start_year_after_2024?), Form::Sales::Pages::PersonGenderIdentity.new("person_2_gender_identity", nil, self, person_index: 2), - Form::Sales::Pages::RetirementValueCheck.new("gender_2_retirement_value_check", nil, self, person_index: 2), Form::Sales::Pages::PersonWorkingSituation.new("person_2_working_situation", nil, self, person_index: 2), Form::Sales::Pages::RetirementValueCheck.new("working_situation_2_retirement_value_check", nil, self, person_index: 2), + (Form::Sales::Pages::NotRetiredValueCheck.new("working_situation_2_not_retired_value_check", nil, self, person_index: 2) if form.start_year_after_2024?), Form::Sales::Pages::PersonStudentNotChildValueCheck.new("working_situation_2_student_not_child_value_check", nil, self, person_index: 2), Form::Sales::Pages::PersonKnown.new("person_3_known", nil, self, person_index: 3), Form::Sales::Pages::PersonRelationshipToBuyer1.new("person_3_relationship_to_buyer_1", nil, self, person_index: 3), @@ -69,12 +72,13 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection Form::Sales::Pages::PersonStudentNotChildValueCheck.new("relationship_3_student_not_child_value_check", nil, self, person_index: 3), Form::Sales::Pages::PersonAge.new("person_3_age", nil, self, person_index: 3), Form::Sales::Pages::RetirementValueCheck.new("age_3_retirement_value_check", nil, self, person_index: 3), + (Form::Sales::Pages::NotRetiredValueCheck.new("age_3_not_retired_value_check", nil, self, person_index: 3) if form.start_year_after_2024?), Form::Sales::Pages::PersonStudentNotChildValueCheck.new("age_3_student_not_child_value_check", nil, self, person_index: 3), (Form::Sales::Pages::PartnerUnder16ValueCheck.new("age_3_partner_under_16_value_check", nil, self, person_index: 3) if form.start_year_after_2024?), Form::Sales::Pages::PersonGenderIdentity.new("person_3_gender_identity", nil, self, person_index: 3), - Form::Sales::Pages::RetirementValueCheck.new("gender_3_retirement_value_check", nil, self, person_index: 3), Form::Sales::Pages::PersonWorkingSituation.new("person_3_working_situation", nil, self, person_index: 3), Form::Sales::Pages::RetirementValueCheck.new("working_situation_3_retirement_value_check", nil, self, person_index: 3), + (Form::Sales::Pages::NotRetiredValueCheck.new("working_situation_3_not_retired_value_check", nil, self, person_index: 3) if form.start_year_after_2024?), Form::Sales::Pages::PersonStudentNotChildValueCheck.new("working_situation_3_student_not_child_value_check", nil, self, person_index: 3), Form::Sales::Pages::PersonKnown.new("person_4_known", nil, self, person_index: 4), Form::Sales::Pages::PersonRelationshipToBuyer1.new("person_4_relationship_to_buyer_1", nil, self, person_index: 4), @@ -83,12 +87,13 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection Form::Sales::Pages::PersonStudentNotChildValueCheck.new("relationship_4_student_not_child_value_check", nil, self, person_index: 4), Form::Sales::Pages::PersonAge.new("person_4_age", nil, self, person_index: 4), Form::Sales::Pages::RetirementValueCheck.new("age_4_retirement_value_check", nil, self, person_index: 4), + (Form::Sales::Pages::NotRetiredValueCheck.new("age_4_not_retired_value_check", nil, self, person_index: 4) if form.start_year_after_2024?), Form::Sales::Pages::PersonStudentNotChildValueCheck.new("age_4_student_not_child_value_check", nil, self, person_index: 4), (Form::Sales::Pages::PartnerUnder16ValueCheck.new("age_4_partner_under_16_value_check", nil, self, person_index: 4) if form.start_year_after_2024?), Form::Sales::Pages::PersonGenderIdentity.new("person_4_gender_identity", nil, self, person_index: 4), - Form::Sales::Pages::RetirementValueCheck.new("gender_4_retirement_value_check", nil, self, person_index: 4), Form::Sales::Pages::PersonWorkingSituation.new("person_4_working_situation", nil, self, person_index: 4), Form::Sales::Pages::RetirementValueCheck.new("working_situation_4_retirement_value_check", nil, self, person_index: 4), + (Form::Sales::Pages::NotRetiredValueCheck.new("working_situation_4_not_retired_value_check", nil, self, person_index: 4) if form.start_year_after_2024?), Form::Sales::Pages::PersonStudentNotChildValueCheck.new("working_situation_4_student_not_child_value_check", nil, self, person_index: 4), Form::Sales::Pages::PersonKnown.new("person_5_known", nil, self, person_index: 5), Form::Sales::Pages::PersonRelationshipToBuyer1.new("person_5_relationship_to_buyer_1", nil, self, person_index: 5), @@ -97,12 +102,13 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection Form::Sales::Pages::PersonStudentNotChildValueCheck.new("relationship_5_student_not_child_value_check", nil, self, person_index: 5), Form::Sales::Pages::PersonAge.new("person_5_age", nil, self, person_index: 5), Form::Sales::Pages::RetirementValueCheck.new("age_5_retirement_value_check", nil, self, person_index: 5), + (Form::Sales::Pages::NotRetiredValueCheck.new("age_5_not_retired_value_check", nil, self, person_index: 5) if form.start_year_after_2024?), Form::Sales::Pages::PersonStudentNotChildValueCheck.new("age_5_student_not_child_value_check", nil, self, person_index: 5), (Form::Sales::Pages::PartnerUnder16ValueCheck.new("age_5_partner_under_16_value_check", nil, self, person_index: 5) if form.start_year_after_2024?), Form::Sales::Pages::PersonGenderIdentity.new("person_5_gender_identity", nil, self, person_index: 5), - Form::Sales::Pages::RetirementValueCheck.new("gender_5_retirement_value_check", nil, self, person_index: 5), Form::Sales::Pages::PersonWorkingSituation.new("person_5_working_situation", nil, self, person_index: 5), Form::Sales::Pages::RetirementValueCheck.new("working_situation_5_retirement_value_check", nil, self, person_index: 5), + (Form::Sales::Pages::NotRetiredValueCheck.new("working_situation_5_not_retired_value_check", nil, self, person_index: 5) if form.start_year_after_2024?), Form::Sales::Pages::PersonStudentNotChildValueCheck.new("working_situation_5_student_not_child_value_check", nil, self, person_index: 5), Form::Sales::Pages::PersonKnown.new("person_6_known", nil, self, person_index: 6), Form::Sales::Pages::PersonRelationshipToBuyer1.new("person_6_relationship_to_buyer_1", nil, self, person_index: 6), @@ -111,12 +117,13 @@ class Form::Sales::Subsections::HouseholdCharacteristics < ::Form::Subsection Form::Sales::Pages::PersonStudentNotChildValueCheck.new("relationship_6_student_not_child_value_check", nil, self, person_index: 6), Form::Sales::Pages::PersonAge.new("person_6_age", nil, self, person_index: 6), Form::Sales::Pages::RetirementValueCheck.new("age_6_retirement_value_check", nil, self, person_index: 6), + (Form::Sales::Pages::NotRetiredValueCheck.new("age_6_not_retired_value_check", nil, self, person_index: 6) if form.start_year_after_2024?), Form::Sales::Pages::PersonStudentNotChildValueCheck.new("age_6_student_not_child_value_check", nil, self, person_index: 6), (Form::Sales::Pages::PartnerUnder16ValueCheck.new("age_6_partner_under_16_value_check", nil, self, person_index: 6) if form.start_year_after_2024?), Form::Sales::Pages::PersonGenderIdentity.new("person_6_gender_identity", nil, self, person_index: 6), - Form::Sales::Pages::RetirementValueCheck.new("gender_6_retirement_value_check", nil, self, person_index: 6), Form::Sales::Pages::PersonWorkingSituation.new("person_6_working_situation", nil, self, person_index: 6), Form::Sales::Pages::RetirementValueCheck.new("working_situation_6_retirement_value_check", nil, self, person_index: 6), + (Form::Sales::Pages::NotRetiredValueCheck.new("working_situation_6_not_retired_value_check", nil, self, person_index: 6) if form.start_year_after_2024?), Form::Sales::Pages::PersonStudentNotChildValueCheck.new("working_situation_6_student_not_child_value_check", nil, self, person_index: 6), ].flatten.compact end diff --git a/lib/tasks/recalculate_status_after_sales_over_retirement_age_validation.rake b/lib/tasks/recalculate_status_after_sales_over_retirement_age_validation.rake new file mode 100644 index 000000000..e827e1c10 --- /dev/null +++ b/lib/tasks/recalculate_status_after_sales_over_retirement_age_validation.rake @@ -0,0 +1,20 @@ +desc "Recalculates status for 2024 logs that will trigger new sales over retirement age soft validation" +task recalculate_status_over_retirement: :environment do + validation_trigger_condition = "(ecstat1 != 5 AND age1 > 66) OR (ecstat2 != 5 AND age2 > 66) OR (ecstat3 != 5 AND age3 > 66) OR (ecstat4 != 5 AND age4 > 66) OR (ecstat5 != 5 AND age5 > 66) OR (ecstat6 != 5 AND age6 > 66)" + SalesLog.filter_by_year(2024).where(status: "pending", status_cache: "completed").where(validation_trigger_condition).find_each do |log| + log.status_cache = log.calculate_status + log.skip_update_status = true + + unless log.save + Rails.logger.info "Could not save changes to pending sales log #{log.id}" + end + end + + SalesLog.filter_by_year(2024).where(status: "completed").where(validation_trigger_condition).find_each do |log| + log.status = log.calculate_status + + unless log.save + Rails.logger.info "Could not save changes to sales log #{log.id}" + end + end +end diff --git a/spec/lib/tasks/recalculate_status_after_sales_over_retirement_age_validation_spec.rb b/spec/lib/tasks/recalculate_status_after_sales_over_retirement_age_validation_spec.rb new file mode 100644 index 000000000..b76380f90 --- /dev/null +++ b/spec/lib/tasks/recalculate_status_after_sales_over_retirement_age_validation_spec.rb @@ -0,0 +1,55 @@ +require "rails_helper" +require "rake" + +RSpec.describe "recalculate_status_after_sales_over_retirement_age_validation" do + describe ":recalculate_status_over_retirement", type: :task do + subject(:task) { Rake::Task["recalculate_status_over_retirement"] } + + before do + Rake.application.rake_require("tasks/recalculate_status_after_sales_over_retirement_age_validation") + Rake::Task.define_task(:environment) + task.reenable + end + + context "when the rake task is run" do + context "and there is a completed sales log that trips the validation" do + let(:log) { create(:sales_log, :completed, ecstat1: 1, age1: 67) } + + before do + log.status = "completed" + log.skip_update_status = true + log.save! + end + + it "sets the log to in progress" do + task.invoke + log.reload + expect(log.status).to eq("in_progress") + end + end + + context "and there is a pending sales log that trips the validation" do + let(:log) { create(:sales_log, :completed, ecstat2: 1, age2: 70) } + + before do + log.status = "pending" + log.status_cache = "completed" + log.skip_update_status = true + log.save! + end + + it "updates the status cache" do + task.invoke + log.reload + expect(log.status_cache).to eq("in_progress") + end + + it "does not change the log status" do + task.invoke + log.reload + expect(log.status).to eq("pending") + end + end + end + end +end diff --git a/spec/models/form/sales/subsections/household_characteristics_spec.rb b/spec/models/form/sales/subsections/household_characteristics_spec.rb index be78f91d3..7c6546d79 100644 --- a/spec/models/form/sales/subsections/household_characteristics_spec.rb +++ b/spec/models/form/sales/subsections/household_characteristics_spec.rb @@ -34,7 +34,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model age_1_old_persons_shared_ownership_joint_purchase_value_check age_1_old_persons_shared_ownership_value_check buyer_1_gender_identity - gender_1_retirement_value_check buyer_1_ethnic_group buyer_1_ethnic_background_black buyer_1_ethnic_background_asian @@ -55,7 +54,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model age_2_buyer_retirement_value_check buyer_2_age_student_not_child_value_check buyer_2_gender_identity - gender_2_buyer_retirement_value_check buyer_2_working_situation working_situation_2_retirement_value_check_joint_purchase working_situation_buyer_2_income_min_value_check @@ -71,7 +69,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model age_2_retirement_value_check age_2_student_not_child_value_check person_2_gender_identity - gender_2_retirement_value_check person_2_working_situation working_situation_2_retirement_value_check working_situation_2_student_not_child_value_check @@ -82,7 +79,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model age_3_retirement_value_check age_3_student_not_child_value_check person_3_gender_identity - gender_3_retirement_value_check person_3_working_situation working_situation_3_retirement_value_check working_situation_3_student_not_child_value_check @@ -93,7 +89,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model age_4_retirement_value_check age_4_student_not_child_value_check person_4_gender_identity - gender_4_retirement_value_check person_4_working_situation working_situation_4_retirement_value_check working_situation_4_student_not_child_value_check @@ -104,7 +99,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model age_5_retirement_value_check age_5_student_not_child_value_check person_5_gender_identity - gender_5_retirement_value_check person_5_working_situation working_situation_5_retirement_value_check working_situation_5_student_not_child_value_check @@ -115,7 +109,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model age_6_retirement_value_check age_6_student_not_child_value_check person_6_gender_identity - gender_6_retirement_value_check person_6_working_situation working_situation_6_retirement_value_check working_situation_6_student_not_child_value_check @@ -142,7 +135,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model age_1_old_persons_shared_ownership_joint_purchase_value_check age_1_old_persons_shared_ownership_value_check buyer_1_gender_identity - gender_1_retirement_value_check buyer_1_ethnic_group buyer_1_ethnic_background_black buyer_1_ethnic_background_asian @@ -163,7 +155,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model age_2_buyer_retirement_value_check buyer_2_age_student_not_child_value_check buyer_2_gender_identity - gender_2_buyer_retirement_value_check buyer_2_ethnic_group buyer_2_ethnic_background_black buyer_2_ethnic_background_asian @@ -186,7 +177,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model age_2_retirement_value_check age_2_student_not_child_value_check person_2_gender_identity - gender_2_retirement_value_check person_2_working_situation working_situation_2_retirement_value_check working_situation_2_student_not_child_value_check @@ -197,7 +187,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model age_3_retirement_value_check age_3_student_not_child_value_check person_3_gender_identity - gender_3_retirement_value_check person_3_working_situation working_situation_3_retirement_value_check working_situation_3_student_not_child_value_check @@ -208,7 +197,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model age_4_retirement_value_check age_4_student_not_child_value_check person_4_gender_identity - gender_4_retirement_value_check person_4_working_situation working_situation_4_retirement_value_check working_situation_4_student_not_child_value_check @@ -219,7 +207,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model age_5_retirement_value_check age_5_student_not_child_value_check person_5_gender_identity - gender_5_retirement_value_check person_5_working_situation working_situation_5_retirement_value_check working_situation_5_student_not_child_value_check @@ -230,7 +217,6 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model age_6_retirement_value_check age_6_student_not_child_value_check person_6_gender_identity - gender_6_retirement_value_check person_6_working_situation working_situation_6_retirement_value_check working_situation_6_student_not_child_value_check @@ -250,10 +236,10 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model %w[ buyer_1_age age_1_retirement_value_check + age_1_not_retired_value_check age_1_old_persons_shared_ownership_joint_purchase_value_check age_1_old_persons_shared_ownership_value_check buyer_1_gender_identity - gender_1_retirement_value_check buyer_1_ethnic_group buyer_1_ethnic_background_black buyer_1_ethnic_background_asian @@ -263,6 +249,7 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model buyer_1_nationality 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 buyer_1_live_in_property buyer_1_live_in_property_value_check @@ -272,9 +259,9 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model age_2_old_persons_shared_ownership_joint_purchase_value_check age_2_old_persons_shared_ownership_value_check age_2_buyer_retirement_value_check + age_2_buyer_not_retired_value_check buyer_2_age_student_not_child_value_check buyer_2_gender_identity - gender_2_buyer_retirement_value_check buyer_2_ethnic_group buyer_2_ethnic_background_black buyer_2_ethnic_background_asian @@ -284,6 +271,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_2_not_retired_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 @@ -297,12 +285,13 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model relationship_2_student_not_child_value_check person_2_age age_2_retirement_value_check + age_2_not_retired_value_check age_2_student_not_child_value_check age_2_partner_under_16_value_check person_2_gender_identity - gender_2_retirement_value_check person_2_working_situation working_situation_2_retirement_value_check + working_situation_2_not_retired_value_check working_situation_2_student_not_child_value_check person_3_known person_3_relationship_to_buyer_1 @@ -311,12 +300,13 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model relationship_3_student_not_child_value_check person_3_age age_3_retirement_value_check + age_3_not_retired_value_check age_3_student_not_child_value_check age_3_partner_under_16_value_check person_3_gender_identity - gender_3_retirement_value_check person_3_working_situation working_situation_3_retirement_value_check + working_situation_3_not_retired_value_check working_situation_3_student_not_child_value_check person_4_known person_4_relationship_to_buyer_1 @@ -325,12 +315,13 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model relationship_4_student_not_child_value_check person_4_age age_4_retirement_value_check + age_4_not_retired_value_check age_4_student_not_child_value_check age_4_partner_under_16_value_check person_4_gender_identity - gender_4_retirement_value_check person_4_working_situation working_situation_4_retirement_value_check + working_situation_4_not_retired_value_check working_situation_4_student_not_child_value_check person_5_known person_5_relationship_to_buyer_1 @@ -339,12 +330,13 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model relationship_5_student_not_child_value_check person_5_age age_5_retirement_value_check + age_5_not_retired_value_check age_5_student_not_child_value_check age_5_partner_under_16_value_check person_5_gender_identity - gender_5_retirement_value_check person_5_working_situation working_situation_5_retirement_value_check + working_situation_5_not_retired_value_check working_situation_5_student_not_child_value_check person_6_known person_6_relationship_to_buyer_1 @@ -353,12 +345,13 @@ RSpec.describe Form::Sales::Subsections::HouseholdCharacteristics, type: :model relationship_6_student_not_child_value_check person_6_age age_6_retirement_value_check + age_6_not_retired_value_check age_6_student_not_child_value_check age_6_partner_under_16_value_check person_6_gender_identity - gender_6_retirement_value_check person_6_working_situation working_situation_6_retirement_value_check + working_situation_6_not_retired_value_check working_situation_6_student_not_child_value_check ], )