From 5b98a2190709cf7c0bd3766184e50b0352b9dbe9 Mon Sep 17 00:00:00 2001 From: Arthur Campbell <51094020+arfacamble@users.noreply.github.com> Date: Thu, 15 Jun 2023 15:01:24 +0100 Subject: [PATCH 01/19] minor change in the reset confirmation method to avoid errors if email in params is nil (#1676) --- app/controllers/auth/passwords_controller.rb | 2 +- spec/requests/auth/passwords_controller_spec.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/controllers/auth/passwords_controller.rb b/app/controllers/auth/passwords_controller.rb index c76ebaf6a..a990325f9 100644 --- a/app/controllers/auth/passwords_controller.rb +++ b/app/controllers/auth/passwords_controller.rb @@ -4,7 +4,7 @@ class Auth::PasswordsController < Devise::PasswordsController def reset_confirmation self.resource = resource_class.new @email = params["email"] - if @email.empty? + if @email.blank? resource.errors.add :email, I18n.t("validations.email.blank") render "devise/passwords/new", status: :unprocessable_entity elsif !email_valid?(@email) diff --git a/spec/requests/auth/passwords_controller_spec.rb b/spec/requests/auth/passwords_controller_spec.rb index 70357062e..29a032395 100644 --- a/spec/requests/auth/passwords_controller_spec.rb +++ b/spec/requests/auth/passwords_controller_spec.rb @@ -145,4 +145,12 @@ RSpec.describe Auth::PasswordsController, type: :request do end end end + + context "when a password is reset" do + let(:email) { nil } + + it "does not error if the email is nil or not in the params" do + expect { get account_password_reset_confirmation_path(email:) }.not_to raise_error + end + end end From cec1208b35e7aa4e9f3dfbcfdce072cff2444f19 Mon Sep 17 00:00:00 2001 From: Arthur Campbell <51094020+arfacamble@users.noreply.github.com> Date: Thu, 15 Jun 2023 15:02:06 +0100 Subject: [PATCH 02/19] CLDC-2290 implement delete multiple logs story (#1657) * add a button to the logs list to delete multiple logs style and position of button helpers for displaying the button conditionally depending on user role and what filters and search are active * correct indentation from 4 spaces to 2 in view file * test appearance of delete logs button on index page for lettings logs * write a happy path feature test for the entire journey * create basic tests for the view component for listing logs to delete * create request tests for the GET delete-logs path * create request tests for the GET delete-logs-confirmation path * create request tests for the DELETE delete-logs path * comprehensive reworking after code review ensure that we are not passing lists of ids through params in the query string, risking overflowing the maximum URL length, adjust tests accordingly, do not attempt to reuse the same table for sales and lettings * alter config to allow creating controllers from the command line with associated spec files that matches how we test * extract controller methods and associated tests to do with the delete logs feature into their own controller, amend routes accordingly * implement same work for sales as for lettings * implement the story for lettings and sales logs under the organisation tab routing and controller methods testing for deleting sales logs, lettings or sales logs for an organisation move storage of relevant routes inside the form object as a comprehensive view model * merge the delete pages for lettings logs and sales logs, add to the tests for the lettings page to test sales specific content * minor refactor to delete logs controller: ensure session filters are only fetched from teh session when needed and extract discard logs method to private method * extract tables for lettings and sales to own partials * refactor delete logs controller after tech review improve the private method that builds the form object so that it has the flexibility to do so for all controller methods ensure that the search term is passed to the delete logs controller when navigating through the organisations tab ensure that noly logs for that organisation are displayed when navigating to delete logs through the organisations tab * remove unnecessary untested arguments * test new helper methods * implement dirty fiddle to get the checkboxes smaller and also not misaligned * ensure delete logs button is always visible on log lists when in the organisations tab * minor linting corrections * revert change, causing errors and outside the scope of this ticket * simplify tests for whether delete logs button appears on index page * replicate request specs from lettings for sales and organisations controllers * minor refactor of lettings log feature spec setup, replicate happy path for sales * minor refactors after rebasing onto Nat's work * temp * write tests for the delete logs form object * lint: add new line at end of file * respond to PO feedback the log id in the delte logs table should be a link to the log the delete logs button should be visible when the user is in a bulk upload journey updated associated tests --- app/controllers/delete_logs_controller.rb | 196 ++++ app/controllers/lettings_logs_controller.rb | 2 +- app/controllers/organisations_controller.rb | 4 +- app/controllers/sales_logs_controller.rb | 1 + app/frontend/styles/_delete-logs-table.scss | 5 + app/frontend/styles/application.scss | 3 +- app/helpers/filters_helper.rb | 12 + app/helpers/log_list_helper.rb | 13 + app/models/forms/delete_logs_form.rb | 44 + .../logs/_delete_logs_table_lettings.html.erb | 30 + .../logs/_delete_logs_table_sales.html.erb | 30 + app/views/logs/_log_list.html.erb | 12 +- app/views/logs/delete_logs.html.erb | 22 + .../logs/delete_logs_confirmation.html.erb | 25 + app/views/logs/index.html.erb | 6 +- app/views/logs/update_logs.html.erb | 65 +- app/views/organisations/logs.html.erb | 2 + config/application.rb | 9 + config/locales/en.yml | 5 + config/routes.rb | 18 + spec/features/lettings_log_spec.rb | 40 + spec/features/sales_log_spec.rb | 73 +- spec/helpers/filters_helper_spec.rb | 78 ++ spec/helpers/log_list_helper_spec.rb | 72 ++ spec/models/forms/delete_logs_form_spec.rb | 89 ++ spec/requests/delete_logs_controller_spec.rb | 946 ++++++++++++++++++ .../requests/lettings_logs_controller_spec.rb | 40 +- .../requests/organisations_controller_spec.rb | 344 ++++--- spec/requests/sales_logs_controller_spec.rb | 32 + spec/views/form/page_view_spec.rb | 4 +- spec/views/logs/delete_logs_spec.rb | 121 +++ 31 files changed, 2115 insertions(+), 228 deletions(-) create mode 100644 app/controllers/delete_logs_controller.rb create mode 100644 app/frontend/styles/_delete-logs-table.scss create mode 100644 app/helpers/log_list_helper.rb create mode 100644 app/models/forms/delete_logs_form.rb create mode 100644 app/views/logs/_delete_logs_table_lettings.html.erb create mode 100644 app/views/logs/_delete_logs_table_sales.html.erb create mode 100644 app/views/logs/delete_logs.html.erb create mode 100644 app/views/logs/delete_logs_confirmation.html.erb create mode 100644 spec/helpers/log_list_helper_spec.rb create mode 100644 spec/models/forms/delete_logs_form_spec.rb create mode 100644 spec/requests/delete_logs_controller_spec.rb create mode 100644 spec/views/logs/delete_logs_spec.rb diff --git a/app/controllers/delete_logs_controller.rb b/app/controllers/delete_logs_controller.rb new file mode 100644 index 000000000..0b4dc3ba5 --- /dev/null +++ b/app/controllers/delete_logs_controller.rb @@ -0,0 +1,196 @@ +class DeleteLogsController < ApplicationController + rescue_from ActiveRecord::RecordNotFound, with: :render_not_found + + before_action :session_filters, if: :current_user, except: %i[discard_lettings_logs discard_sales_logs discard_lettings_logs_for_organisation discard_sales_logs_for_organisation] + before_action :add_organisation_to_filters, only: %i[delete_lettings_logs_for_organisation delete_lettings_logs_for_organisation_with_selected_ids delete_lettings_logs_for_organisation_confirmation delete_sales_logs_for_organisation delete_sales_logs_for_organisation_with_selected_ids delete_sales_logs_for_organisation_confirmation] + + def delete_lettings_logs + @delete_logs_form = delete_logs_form(log_type: :lettings) + render "logs/delete_logs" + end + + def delete_lettings_logs_with_selected_ids + @delete_logs_form = delete_logs_form(log_type: :lettings, selected_ids:) + render "logs/delete_logs" + end + + def delete_lettings_logs_confirmation + @delete_logs_form = delete_logs_form(log_type: :lettings, form_params:) + if @delete_logs_form.valid? + render "logs/delete_logs_confirmation" + else + render "logs/delete_logs" + end + end + + def discard_lettings_logs + logs = LettingsLog.find(params.require(:ids)) + discard logs + + redirect_to lettings_logs_path, notice: I18n.t("notification.logs_deleted", count: logs.count) + end + + def delete_sales_logs + @delete_logs_form = delete_logs_form(log_type: :sales) + render "logs/delete_logs" + end + + def delete_sales_logs_with_selected_ids + @delete_logs_form = delete_logs_form(log_type: :sales, selected_ids:) + render "logs/delete_logs" + end + + def delete_sales_logs_confirmation + @delete_logs_form = delete_logs_form(log_type: :sales, form_params:) + if @delete_logs_form.valid? + render "logs/delete_logs_confirmation" + else + render "logs/delete_logs" + end + end + + def discard_sales_logs + logs = SalesLog.find(params.require(:ids)) + discard logs + + redirect_to sales_logs_path, notice: I18n.t("notification.logs_deleted", count: logs.count) + end + + def delete_lettings_logs_for_organisation + @delete_logs_form = delete_logs_form(log_type: :lettings, organisation: true) + render "logs/delete_logs" + end + + def delete_lettings_logs_for_organisation_with_selected_ids + @delete_logs_form = delete_logs_form(log_type: :lettings, organisation: true, selected_ids:) + render "logs/delete_logs" + end + + def delete_lettings_logs_for_organisation_confirmation + @delete_logs_form = delete_logs_form(log_type: :lettings, organisation: true, form_params:) + if @delete_logs_form.valid? + render "logs/delete_logs_confirmation" + else + render "logs/delete_logs" + end + end + + def discard_lettings_logs_for_organisation + logs = LettingsLog.where(owning_organisation: params[:id]).find(params.require(:ids)) + discard logs + + redirect_to lettings_logs_organisation_path, notice: I18n.t("notification.logs_deleted", count: logs.count) + end + + def delete_sales_logs_for_organisation + @delete_logs_form = delete_logs_form(log_type: :sales, organisation: true) + render "logs/delete_logs" + end + + def delete_sales_logs_for_organisation_with_selected_ids + @delete_logs_form = delete_logs_form(log_type: :sales, organisation: true, selected_ids:) + render "logs/delete_logs" + end + + def delete_sales_logs_for_organisation_confirmation + @delete_logs_form = delete_logs_form(log_type: :sales, organisation: true, form_params:) + if @delete_logs_form.valid? + render "logs/delete_logs_confirmation" + else + render "logs/delete_logs" + end + end + + def discard_sales_logs_for_organisation + logs = SalesLog.where(owning_organisation: params[:id]).find(params.require(:ids)) + discard logs + + redirect_to sales_logs_organisation_path, notice: I18n.t("notification.logs_deleted", count: logs.count) + end + +private + + def session_filters + @session_filters ||= filter_manager.session_filters + end + + def filter_manager + log_type = action_name.include?("lettings") ? "lettings_logs" : "sales_logs" + FilterManager.new(current_user:, session:, params:, filter_type: log_type) + end + + def delete_logs_form(log_type:, organisation: false, selected_ids: nil, form_params: {}) + paths = case log_type + when :lettings + organisation ? lettings_logs_for_organisation_paths : lettings_logs_paths + when :sales + organisation ? sales_logs_for_organisation_paths : sales_logs_paths + end + attributes = { + log_type:, + current_user:, + log_filters: @session_filters, + search_term:, + selected_ids:, + **paths, + }.merge(form_params).transform_keys(&:to_sym) + Forms::DeleteLogsForm.new(attributes) + end + + def form_params + form_attributes = params.require(:forms_delete_logs_form).permit(:search_term, selected_ids: []) + form_attributes[:selected_ids] = [] unless form_attributes.key? :selected_ids + form_attributes + end + + def lettings_logs_paths + { + delete_confirmation_path: delete_logs_confirmation_lettings_logs_path, + back_to_logs_path: lettings_logs_path(search: search_term), + delete_path: delete_logs_lettings_logs_path, + } + end + + def sales_logs_paths + { + delete_confirmation_path: delete_logs_confirmation_sales_logs_path, + back_to_logs_path: sales_logs_path(search: search_term), + delete_path: delete_logs_sales_logs_path, + } + end + + def lettings_logs_for_organisation_paths + { + delete_confirmation_path: delete_lettings_logs_confirmation_organisation_path, + back_to_logs_path: lettings_logs_organisation_path(search: search_term), + delete_path: delete_lettings_logs_organisation_path, + } + end + + def sales_logs_for_organisation_paths + { + delete_confirmation_path: delete_sales_logs_confirmation_organisation_path, + back_to_logs_path: sales_logs_organisation_path(search: search_term), + delete_path: delete_sales_logs_organisation_path, + } + end + + def add_organisation_to_filters + @session_filters[:organisation] = params[:id] + end + + def search_term + params["search"] + end + + def selected_ids + params.require(:selected_ids).split.map(&:to_i) + end + + def discard(logs) + logs.each do |log| + authorize log, :destroy? + log.discard! + end + end +end diff --git a/app/controllers/lettings_logs_controller.rb b/app/controllers/lettings_logs_controller.rb index eb1caa47c..f67e94acc 100644 --- a/app/controllers/lettings_logs_controller.rb +++ b/app/controllers/lettings_logs_controller.rb @@ -16,7 +16,7 @@ class LettingsLogsController < LogsController all_logs = current_user.lettings_logs.visible unpaginated_filtered_logs = filter_manager.filtered_logs(all_logs, search_term, session_filters) - @search_term = search_term + @delete_logs_path = delete_logs_lettings_logs_path(search: search_term) @pagy, @logs = pagy(unpaginated_filtered_logs) @searched = search_term.presence @total_count = all_logs.size diff --git a/app/controllers/organisations_controller.rb b/app/controllers/organisations_controller.rb index 7fdcd1d94..98082bea9 100644 --- a/app/controllers/organisations_controller.rb +++ b/app/controllers/organisations_controller.rb @@ -96,6 +96,7 @@ class OrganisationsController < ApplicationController format.html do @search_term = search_term @pagy, @logs = pagy(unpaginated_filtered_logs) + @delete_logs_path = delete_lettings_logs_organisation_path(search: @search_term) @searched = search_term.presence @total_count = organisation_logs.size @log_type = :lettings @@ -119,13 +120,14 @@ class OrganisationsController < ApplicationController end def sales_logs - organisation_logs = SalesLog.where(owning_organisation_id: @organisation.id) + organisation_logs = SalesLog.visible.where(owning_organisation_id: @organisation.id) unpaginated_filtered_logs = filter_manager.filtered_logs(organisation_logs, search_term, session_filters) respond_to do |format| format.html do @search_term = search_term @pagy, @logs = pagy(unpaginated_filtered_logs) + @delete_logs_path = delete_sales_logs_organisation_path(search: @search_term) @searched = search_term.presence @total_count = organisation_logs.size @log_type = :sales diff --git a/app/controllers/sales_logs_controller.rb b/app/controllers/sales_logs_controller.rb index 27f3d4f05..4a06fa6c4 100644 --- a/app/controllers/sales_logs_controller.rb +++ b/app/controllers/sales_logs_controller.rb @@ -18,6 +18,7 @@ class SalesLogsController < LogsController all_logs = current_user.sales_logs.visible unpaginated_filtered_logs = filter_manager.filtered_logs(all_logs, search_term, session_filters) + @delete_logs_path = delete_logs_sales_logs_path(search: search_term) @search_term = search_term @pagy, @logs = pagy(unpaginated_filtered_logs) @searched = search_term.presence diff --git a/app/frontend/styles/_delete-logs-table.scss b/app/frontend/styles/_delete-logs-table.scss new file mode 100644 index 000000000..00bb3ed28 --- /dev/null +++ b/app/frontend/styles/_delete-logs-table.scss @@ -0,0 +1,5 @@ +.checkbox-cell { + .govuk-checkboxes__item { + margin-top: -7px; + } +} diff --git a/app/frontend/styles/application.scss b/app/frontend/styles/application.scss index 03a6ab6f2..ddf368807 100644 --- a/app/frontend/styles/application.scss +++ b/app/frontend/styles/application.scss @@ -44,6 +44,7 @@ $govuk-breakpoints: ( @import "search"; @import "sub-navigation"; @import "errors"; +@import "delete-logs-table"; // App utilities .app-\!-colour-muted { @@ -52,7 +53,7 @@ $govuk-breakpoints: ( } .app-\!-colour-red { - color: govuk-colour("red"); + color: govuk-colour("red") !important; } .app-\!-font-tabular { diff --git a/app/helpers/filters_helper.rb b/app/helpers/filters_helper.rb index 51472b299..34e768603 100644 --- a/app/helpers/filters_helper.rb +++ b/app/helpers/filters_helper.rb @@ -11,6 +11,18 @@ module FiltersHelper selected_filters[filter].include?(value.to_s) end + def any_filter_selected?(filter_type) + filters_json = session[session_name_for(filter_type)] + return false unless filters_json + + filters = JSON.parse(filters_json) + filters["user"] == "yours" || + filters["organisation"].present? || + filters["status"]&.compact_blank&.any? || + filters["years"]&.compact_blank&.any? || + filters["bulk_upload_id"].present? + end + def status_filters { "not_started" => "Not started", diff --git a/app/helpers/log_list_helper.rb b/app/helpers/log_list_helper.rb new file mode 100644 index 000000000..b3e967358 --- /dev/null +++ b/app/helpers/log_list_helper.rb @@ -0,0 +1,13 @@ +module LogListHelper + def display_delete_logs?(current_user, search_term, filter_type) + if current_user.data_provider? + filter_selected?("user", "yours", filter_type) + else + any_filter_selected?(filter_type) || search_term.present? + end + end + + def in_organisations_tab? + controller.class.name.start_with? "Organisation" + end +end diff --git a/app/models/forms/delete_logs_form.rb b/app/models/forms/delete_logs_form.rb new file mode 100644 index 000000000..6c4d8eba6 --- /dev/null +++ b/app/models/forms/delete_logs_form.rb @@ -0,0 +1,44 @@ +module Forms + class DeleteLogsForm + include ActiveModel::Model + include ActiveModel::Validations + + attr_reader :logs, :log_type, :selected_ids, :search_term, :delete_confirmation_path, :back_to_logs_path, :delete_path + + validate :at_least_one_log_selected + + def initialize(attributes) + @log_type = attributes[:log_type] + @search_term = attributes[:search_term] + @current_user = attributes[:current_user] + @logs = FilterManager.filter_logs(visible_logs, @search_term, attributes[:log_filters], nil, @current_user) + @selected_ids = attributes[:selected_ids] || @logs.map(&:id) + @delete_confirmation_path = attributes[:delete_confirmation_path] + @back_to_logs_path = attributes[:back_to_logs_path] + @delete_path = attributes[:delete_path] + end + + def log_count + @logs.count + end + + def table_partial_name + "logs/delete_logs_table_#{@log_type}" + end + + private + + def at_least_one_log_selected + if selected_ids.blank? || selected_ids.reject(&:blank?).blank? + errors.add(:log_ids, "Select at least one log to delete or press cancel to return") + end + end + + def visible_logs + case @log_type + when :lettings then @current_user.lettings_logs.visible + when :sales then @current_user.sales_logs.visible + end + end + end +end diff --git a/app/views/logs/_delete_logs_table_lettings.html.erb b/app/views/logs/_delete_logs_table_lettings.html.erb new file mode 100644 index 000000000..bd5952170 --- /dev/null +++ b/app/views/logs/_delete_logs_table_lettings.html.erb @@ -0,0 +1,30 @@ +<%= govuk_table do |table| %> + <% table.head do |head| %> + <% head.row do |row| %> + <% row.cell header: true, text: "Log ID" %> + <% row.cell header: true, text: "Tenancy code" %> + <% row.cell header: true, text: "Property reference" %> + <% row.cell header: true, text: "Status" %> + <% row.cell header: true, text: "Delete?" %> + <% end %> + <% end %> + <% table.body do |body| %> + <% f.govuk_check_boxes_fieldset :selected_ids, small: true do %> + <% delete_logs_form.logs.each do |log| %> + <% body.row do |row| %> + <% row.cell do %> + <%= govuk_link_to log.id, url_for(log) %> + <% end %> + <% row.cell text: log.tenancycode %> + <% row.cell text: log.propcode %> + <% row.cell text: status_tag(log.status) %> + <% row.cell html_attributes: { class: "checkbox-cell" } do %> + <% f.govuk_check_box :selected_ids, log.id, + label: { text: log.id, hidden: true }, + checked: delete_logs_form.selected_ids.include?(log.id) %> + <% end %> + <% end %> + <% end %> + <% end %> + <% end %> +<% end %> diff --git a/app/views/logs/_delete_logs_table_sales.html.erb b/app/views/logs/_delete_logs_table_sales.html.erb new file mode 100644 index 000000000..8659f12bb --- /dev/null +++ b/app/views/logs/_delete_logs_table_sales.html.erb @@ -0,0 +1,30 @@ +<%= govuk_table do |table| %> + <% table.head do |head| %> + <% head.row do |row| %> + <% row.cell header: true, text: "Log ID" %> + <% row.cell header: true, text: "Purchaser code" %> + <% row.cell header: true, text: "Sale completion date" %> + <% row.cell header: true, text: "Status" %> + <% row.cell header: true, text: "Delete?" %> + <% end %> + <% end %> + <% table.body do |body| %> + <% f.govuk_check_boxes_fieldset :selected_ids, small: true do %> + <% delete_logs_form.logs.each do |log| %> + <% body.row do |row| %> + <% row.cell do %> + <%= govuk_link_to log.id, url_for(log) %> + <% end %> + <% row.cell text: log.purchid %> + <% row.cell text: log.saledate&.to_formatted_s(:govuk_date) %> + <% row.cell text: status_tag(log.status) %> + <% row.cell html_attributes: { class: "checkbox-cell" } do %> + <% f.govuk_check_box :selected_ids, log.id, + label: { text: log.id, hidden: true }, + checked: delete_logs_form.selected_ids.include?(log.id) %> + <% end %> + <% end %> + <% end %> + <% end %> + <% end %> +<% end %> diff --git a/app/views/logs/_log_list.html.erb b/app/views/logs/_log_list.html.erb index e92186d4d..d41bdc9a8 100644 --- a/app/views/logs/_log_list.html.erb +++ b/app/views/logs/_log_list.html.erb @@ -1,11 +1,21 @@

+
+
<%= render(SearchResultCaptionComponent.new(searched:, count: pagy.count, item_label:, total_count:, item: "logs", path: request.path)) %> <% if logs&.any? %> <%= govuk_link_to "Download (CSV)", csv_download_url, type: "text/csv", class: "govuk-!-margin-right-4" %> <% if @current_user.support? %> - <%= govuk_link_to "Download (CSV, codes only)", csv_codes_only_download_url, type: "text/csv" %> + <%= govuk_link_to "Download (CSV, codes only)", csv_codes_only_download_url, type: "text/csv", class: "govuk-!-margin-right-4" %> <% end %> <% end %> +
+
+ <% if logs&.any? && (display_delete_logs?(@current_user, searched, filter_type) || in_organisations_tab?) %> + <%# remove @ on current user %> + <%= govuk_link_to "Delete logs", delete_logs_path, class: "app-!-colour-red" %> + <% end %> +
+

<% logs.map do |log| %> <%= render(LogSummaryComponent.new(current_user:, log:)) %> diff --git a/app/views/logs/delete_logs.html.erb b/app/views/logs/delete_logs.html.erb new file mode 100644 index 000000000..8f9338ff2 --- /dev/null +++ b/app/views/logs/delete_logs.html.erb @@ -0,0 +1,22 @@ +<% title = "Delete logs" %> +<% content_for :title, title %> +<% content_for :before_content do %> + <%= govuk_back_link(href: :back) %> +<% end %> + +

+ <%= title %> + Review the logs you want to delete +

+

You've selected <%= @delete_logs_form.log_count %> <%= "log".pluralize(@delete_logs_form.log_count) %> to delete

+ +
+ <%= form_with model: @delete_logs_form, url: @delete_logs_form.delete_confirmation_path do |f| %> + <%= f.hidden_field :search_term, value: @delete_logs_form.search_term %> + <%= f.govuk_error_summary %> + <%= render partial: @delete_logs_form.table_partial_name, locals: { f:, delete_logs_form: @delete_logs_form } %> + <%= f.govuk_submit "Continue" do %> + <%= govuk_button_link_to "Cancel", @delete_logs_form.back_to_logs_path, secondary: true %> + <% end %> + <% end %> +
diff --git a/app/views/logs/delete_logs_confirmation.html.erb b/app/views/logs/delete_logs_confirmation.html.erb new file mode 100644 index 000000000..a9b4252d4 --- /dev/null +++ b/app/views/logs/delete_logs_confirmation.html.erb @@ -0,0 +1,25 @@ +<% title = "Delete logs" %> +<% content_for :title, title %> +<% content_for :before_content do %> + <%= govuk_back_link(href: :back) %> +<% end %> + +

+ <%= title %> + Are you sure you want to delete these logs? +

+<% log_count = @delete_logs_form.selected_ids.count %> +

You've selected <%= log_count %> <%= "log".pluralize(log_count) %> to delete

+ +<%= govuk_warning_text(icon_fallback_text: "Danger") do %> + You will not be able to undo this action +<% end %> + +
+ <%= govuk_button_to "Delete logs", @delete_logs_form.delete_path, method: "delete", params: { ids: @delete_logs_form.selected_ids } %> + <%= form_with url: @delete_logs_form.delete_path do |f| %> + <%= f.hidden_field :selected_ids, value: @delete_logs_form.selected_ids %> + <%= f.hidden_field :search, value: @delete_logs_form.search_term %> + <%= f.govuk_submit "Cancel", secondary: true %> + <% end %> +
diff --git a/app/views/logs/index.html.erb b/app/views/logs/index.html.erb index e2cc2e0d1..eac53ed7c 100644 --- a/app/views/logs/index.html.erb +++ b/app/views/logs/index.html.erb @@ -68,8 +68,10 @@ searched: @searched, item_label:, total_count: @total_count, - csv_download_url: csv_download_url_for_controller(controller:, search: @search_term, codes_only: false), - csv_codes_only_download_url: csv_download_url_for_controller(controller:, search: @search_term, codes_only: true), + csv_download_url: csv_download_url_for_controller(controller:, search: @searched, codes_only: false), + csv_codes_only_download_url: csv_download_url_for_controller(controller:, search: @searched, codes_only: true), + delete_logs_path: @delete_logs_path, + filter_type: @filter_type, } %> <%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "logs" } %> diff --git a/app/views/logs/update_logs.html.erb b/app/views/logs/update_logs.html.erb index 1b744beb6..d89ecdc4b 100644 --- a/app/views/logs/update_logs.html.erb +++ b/app/views/logs/update_logs.html.erb @@ -4,43 +4,42 @@ <% content_for :title, title %> <% if @total_count < 1 %> - <%= render partial: "organisations/headings", locals: { main: "There are no more logs that need updating", sub: "" } %> -

- You’ve completed all the logs that were affected by scheme changes. -

-
- <%= govuk_button_link_to "Back to all logs", lettings_logs_path %> -
+ <%= render partial: "organisations/headings", locals: { main: "There are no more logs that need updating", sub: "" } %> +

+ You’ve completed all the logs that were affected by scheme changes. +

+
+ <%= govuk_button_link_to "Back to all logs", lettings_logs_path %> +
<% else %> - <%= render partial: "organisations/headings", locals: { main: "You need to update #{@total_count} logs", sub: "" } %> - - <%= govuk_table do |table| %> - <% table.head do |head| %> - <% head.row do |row| %> - <% row.cell(header: true, text: "Log ID") %> - <% row.cell(header: true, text: "Tenancy code") %> - <% row.cell(header: true, text: "Property reference") %> - <% row.cell(header: true, text: "Status") %> - <% row.cell(header: true, text: "") %> - <% end %> - <% end %> + <%= render partial: "organisations/headings", locals: { main: "You need to update #{@total_count} logs", sub: "" } %> + <%= govuk_table do |table| %> + <% table.head do |head| %> + <% head.row do |row| %> + <% row.cell(header: true, text: "Log ID") %> + <% row.cell(header: true, text: "Tenancy code") %> + <% row.cell(header: true, text: "Property reference") %> + <% row.cell(header: true, text: "Status") %> + <% row.cell(header: true, text: "") %> + <% end %> + <% end %> <% @logs.each do |log| %> - <% table.body do |body| %> - <% body.row do |row| %> - <% row.cell(text: log.id) %> - <% row.cell(text: log.tenancycode) %> - <% row.cell(text: log.propcode) %> - <% row.cell(text: status_tag(log.status)) %> - <% row.cell(html_attributes: { - scope: "row", - class: "govuk-!-text-align-right", - }) do %> - <%= govuk_link_to("Update now", send(log.form.unresolved_log_path, log)) %> - <% end %> - <% end %> + <% table.body do |body| %> + <% body.row do |row| %> + <% row.cell(text: log.id) %> + <% row.cell(text: log.tenancycode) %> + <% row.cell(text: log.propcode) %> + <% row.cell(text: status_tag(log.status)) %> + <% row.cell(html_attributes: { + scope: "row", + class: "govuk-!-text-align-right", + }) do %> + <%= govuk_link_to "Update now", send(log.form.unresolved_log_path, log) %> + <% end %> <% end %> + <% end %> <% end %> - <% end %> + <% end %> <% end %> <%= render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "logs" } %> diff --git a/app/views/organisations/logs.html.erb b/app/views/organisations/logs.html.erb index 6b269590c..89f54fb1d 100644 --- a/app/views/organisations/logs.html.erb +++ b/app/views/organisations/logs.html.erb @@ -34,6 +34,8 @@ total_count: @total_count, csv_download_url: csv_download_url_by_log_type(@log_type, @organisation, search: @search_term, codes_only: false), csv_codes_only_download_url: csv_download_url_by_log_type(@log_type, @organisation, search: @search_term, codes_only: true), + delete_logs_path: @delete_logs_path, + filter_type: @filter_type, } %> <%= render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "logs" } %> diff --git a/config/application.rb b/config/application.rb index 49a236636..4fd65c695 100644 --- a/config/application.rb +++ b/config/application.rb @@ -41,5 +41,14 @@ module DataCollector key: "_data_collector_session", secure: (Rails.env.production? || Rails.env.staging? || Rails.env.review?) ) + + config.generators do |g| + g.test_framework :rspec, + request_specs: true, + view_specs: false, + routing_specs: false, + helper_specs: false, + controller_specs: false + end end end diff --git a/config/locales/en.yml b/config/locales/en.yml index f5d8a56b7..e1635e748 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -175,6 +175,11 @@ en: new_organisation_telephone_number: blank: "Enter a valid telephone number" + notification: + logs_deleted: + one: "%{count} log has been deleted" + other: "%{count} logs have been deleted" + validations: organisation: data_sharing_agreement_not_signed: Your organisation must accept the Data Sharing Agreement before you can create any logs. diff --git a/config/routes.rb b/config/routes.rb index 9dd9b7325..fa0cb598e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -122,7 +122,15 @@ Rails.application.routes.draw do get "users", to: "organisations#users" get "users/invite", to: "users/account#new" get "lettings-logs", to: "organisations#lettings_logs" + get "delete-lettings-logs", to: "delete_logs#delete_lettings_logs_for_organisation" + post "delete-lettings-logs", to: "delete_logs#delete_lettings_logs_for_organisation_with_selected_ids" + post "delete-lettings-logs-confirmation", to: "delete_logs#delete_lettings_logs_for_organisation_confirmation" + delete "delete-lettings-logs", to: "delete_logs#discard_lettings_logs_for_organisation" get "sales-logs", to: "organisations#sales_logs" + get "delete-sales-logs", to: "delete_logs#delete_sales_logs_for_organisation" + post "delete-sales-logs", to: "delete_logs#delete_sales_logs_for_organisation_with_selected_ids" + post "delete-sales-logs-confirmation", to: "delete_logs#delete_sales_logs_for_organisation_confirmation" + delete "delete-sales-logs", to: "delete_logs#discard_sales_logs_for_organisation" get "lettings-logs/csv-download", to: "organisations#download_lettings_csv" post "lettings-logs/email-csv", to: "organisations#email_lettings_csv" get "lettings-logs/csv-confirmation", to: "lettings_logs#csv_confirmation" @@ -170,6 +178,11 @@ Rails.application.routes.draw do post "email-csv", to: "lettings_logs#email_csv" get "csv-confirmation", to: "lettings_logs#csv_confirmation" + get "delete-logs", to: "delete_logs#delete_lettings_logs" + post "delete-logs", to: "delete_logs#delete_lettings_logs_with_selected_ids" + post "delete-logs-confirmation", to: "delete_logs#delete_lettings_logs_confirmation" + delete "delete-logs", to: "delete_logs#discard_lettings_logs" + resources :bulk_upload_lettings_logs, path: "bulk-upload-logs", only: %i[show update] do collection do get :start @@ -227,6 +240,11 @@ Rails.application.routes.draw do post "email-csv", to: "sales_logs#email_csv" get "csv-confirmation", to: "sales_logs#csv_confirmation" + get "delete-logs", to: "delete_logs#delete_sales_logs" + post "delete-logs", to: "delete_logs#delete_sales_logs_with_selected_ids" + post "delete-logs-confirmation", to: "delete_logs#delete_sales_logs_confirmation" + delete "delete-logs", to: "delete_logs#discard_sales_logs" + resources :bulk_upload_sales_logs, path: "bulk-upload-logs" do collection do get :start diff --git a/spec/features/lettings_log_spec.rb b/spec/features/lettings_log_spec.rb index 7f1e77d2a..2e83b669c 100644 --- a/spec/features/lettings_log_spec.rb +++ b/spec/features/lettings_log_spec.rb @@ -220,6 +220,46 @@ RSpec.describe "Lettings Log Features" do end end end + + it "is possible to delete multiple logs" do + postcode = "SW1A 1AA" + lettings_log_1 = create(:lettings_log, :setup_completed, created_by: support_user, postcode_full: postcode) + lettings_log_2 = create(:lettings_log, :in_progress, created_by: support_user, postcode_full: postcode) + create_list(:lettings_log, 5, :in_progress) + + visit lettings_logs_path + expect(page).to have_selector "article.app-log-summary", count: 7 + expect(page).not_to have_link "Delete logs" + within ".app-filter" do + check "status-in-progress-field" + choose "user-yours-field" + click_button + end + expect(page).to have_selector "article.app-log-summary", count: 2 + expect(page).to have_link "Delete logs" + click_link "Delete logs" + + expect(page).to have_current_path delete_logs_lettings_logs_path + rows = page.find_all "tbody tr" + expect(rows.count).to be 2 + id_to_delete, id_to_keep = rows.map { |row| row.first("td").text.to_i } + expect([id_to_delete, id_to_keep]).to match_array [lettings_log_1.id, lettings_log_2.id] + check "forms-delete-logs-form-selected-ids-#{id_to_delete}-field" + uncheck "forms-delete-logs-form-selected-ids-#{id_to_keep}-field" + click_button "Continue" + + expect(page).to have_current_path delete_logs_confirmation_lettings_logs_path + expect(page.text).to include "You've selected 1 log to delete" + expect(page.find("form.button_to")[:action]).to eq delete_logs_lettings_logs_path + click_button "Delete logs" + + expect(page).to have_current_path lettings_logs_path + expect(page).to have_selector "article.app-log-summary", count: 1 + expect(page.find("article.app-log-summary h2").text).to eq "Log #{id_to_keep}" + deleted_log = LettingsLog.find(id_to_delete) + expect(deleted_log.status).to eq "deleted" + expect(deleted_log.discarded_at).not_to be nil + end end context "when the signed is user is not a Support user" do diff --git a/spec/features/sales_log_spec.rb b/spec/features/sales_log_spec.rb index 8093f85dd..890c82786 100644 --- a/spec/features/sales_log_spec.rb +++ b/spec/features/sales_log_spec.rb @@ -3,16 +3,14 @@ require "rails_helper" RSpec.describe "Sales Log Features" do context "when searching for specific sales logs" do context "when I am signed in and there are sales logs in the database" do - let(:user) { FactoryBot.create(:user, last_sign_in_at: Time.zone.now) } + let(:user) { FactoryBot.create(:user, last_sign_in_at: Time.zone.now, name: "Jimbo") } let!(:log_to_search) { FactoryBot.create(:sales_log, owning_organisation: user.organisation) } let!(:same_organisation_log) { FactoryBot.create(:sales_log, owning_organisation: user.organisation) } let!(:another_organisation_log) { FactoryBot.create(:sales_log) } before do - visit("/sales-logs") - fill_in("user[email]", with: user.email) - fill_in("user[password]", with: user.password) - click_button("Sign in") + sign_in user + visit sales_logs_path end it "displays the logs belonging to the same organisation" do @@ -23,34 +21,75 @@ RSpec.describe "Sales Log Features" do context "when returning to the list of logs via breadcrumbs link" do before do - visit("/sales-logs") click_button("Create a new sales log") click_link("Logs") end it "navigates you to the sales logs page" do - expect(page).to have_current_path("/sales-logs") + expect(page).to have_current_path sales_logs_path end end context "when completing the setup sales log section" do it "includes the purchaser code and sale completion date questions" do - visit("/sales-logs") - click_button("Create a new sales log") - click_link("Set up this sales log") + click_button "Create a new sales log" + click_link "Set up this sales log" fill_in("sales_log[saledate(1i)]", with: Time.zone.today.year) fill_in("sales_log[saledate(2i)]", with: Time.zone.today.month) fill_in("sales_log[saledate(3i)]", with: Time.zone.today.day) - click_button("Save and continue") - fill_in("sales_log[purchid]", with: "PC123") - click_button("Save and continue") + click_button "Save and continue" + fill_in "sales_log[purchid]", with: "PC123" + click_button "Save and continue" log_id = page.current_path.scan(/\d/).join - visit("sales-logs/#{log_id}/setup/check-answers") - expect(page).to have_content("Sale completion date") + visit sales_log_setup_check_answers_path(log_id) + expect(page).to have_content "Sale completion date" expect(page).to have_content(Time.zone.today.year) - expect(page).to have_content("Purchaser code") - expect(page).to have_content("PC123") + expect(page).to have_content "Purchaser code" + expect(page).to have_content "PC123" + end + end + + it "is possible to delete multiple logs" do + log_card_selector = "article.app-log-summary" + logs_by_user = create_list(:sales_log, 2, created_by: user) + + visit sales_logs_path + expect(page).to have_selector log_card_selector, count: 4 + expect(page).not_to have_link "Delete logs" + + within ".app-filter" do + choose "user-yours-field" + click_button end + + expect(page).to have_selector log_card_selector, count: 2 + expect(page).to have_link "Delete logs" + + click_link "Delete logs" + + expect(page).to have_current_path delete_logs_sales_logs_path + + rows = page.find_all "tbody tr" + expect(rows.count).to be 2 + id_to_delete, id_to_keep = rows.map { |row| row.first("td").text.to_i } + expect([id_to_delete, id_to_keep]).to match_array logs_by_user.map(&:id) + check "forms-delete-logs-form-selected-ids-#{id_to_delete}-field" + uncheck "forms-delete-logs-form-selected-ids-#{id_to_keep}-field" + click_button "Continue" + + expect(page).to have_current_path delete_logs_confirmation_sales_logs_path + expect(page.text).to include "You've selected 1 log to delete" + button = page.find("form.button_to") + expect(button[:action]).to eq delete_logs_sales_logs_path + expect(button.text).to eq "Delete logs" + click_button "Delete logs" + + expect(page).to have_current_path sales_logs_path + expect(page).to have_selector "article.app-log-summary", count: 1 + expect(page.find("article.app-log-summary h2").text).to eq "Log #{id_to_keep}" + deleted_log = SalesLog.find(id_to_delete) + expect(deleted_log.status).to eq "deleted" + expect(deleted_log.discarded_at).not_to be nil end end end diff --git a/spec/helpers/filters_helper_spec.rb b/spec/helpers/filters_helper_spec.rb index dfc6b51fb..622fd71f8 100644 --- a/spec/helpers/filters_helper_spec.rb +++ b/spec/helpers/filters_helper_spec.rb @@ -70,6 +70,84 @@ RSpec.describe FiltersHelper do end end + describe "#any_filter_selected?" do + let(:filter_type) { "lettings_logs" } + let(:result) { any_filter_selected?(filter_type) } + let(:serialised_filters) { filters&.to_json } + let(:filters) { nil } + + before do + session[:lettings_logs_filters] = serialised_filters if serialised_filters + end + + it "returns false if the session contains no filters" do + expect(result).to be_falsey + end + + context "when organisation and user are set to all" do + let(:filters) { { "organisation_select" => "all", "user" => "all" } } + + it "returns false" do + expect(result).to be_falsey + end + end + + context "when user is set to 'yours'" do + let(:filters) { { "user" => "yours" } } + + it "returns true" do + expect(result).to be true + end + end + + context "when organisation is filtered" do + let(:filters) { { "organisation" => 2 } } + + it "returns true" do + expect(result).to be true + end + end + + context "when status is filtered" do + let(:filters) { { "status" => %w[in_progress] } } + + it "returns true" do + expect(result).to be true + end + end + + context "when collection year is filtered" do + let(:filters) { { "years" => %w[2023] } } + + it "returns true" do + expect(result).to be true + end + end + + context "when the user is currently in a bulk upload journey" do + let(:filters) { { "bulk_upload_id" => "3456" } } + + it "returns true" do + expect(result).to be true + end + end + + context "when a range of filters are applied" do + let(:filters) do + { + "user" => "all", + "status" => %w[in_progress completed], + "years" => [""], + "organisation" => 2, + } + end + + it "returns true" do + expect(result).to be true + end + end + end + describe "#selected_option" do before do session[:lettings_logs_filters] = {}.to_json diff --git a/spec/helpers/log_list_helper_spec.rb b/spec/helpers/log_list_helper_spec.rb new file mode 100644 index 000000000..22836ec45 --- /dev/null +++ b/spec/helpers/log_list_helper_spec.rb @@ -0,0 +1,72 @@ +require "rails_helper" + +RSpec.describe LogListHelper, type: :helper do + include FiltersHelper + + describe "#display_delete_logs?" do + let(:search_term) { nil } + let(:filter_type) { "lettings_logs" } + + context "when logged in as a data provider" do + let(:result) { display_delete_logs?(user, search_term, filter_type) } + let(:user) { create(:user) } + + it "returns false if no filters or search are set" do + allow(self).to receive(:filter_selected?).and_return false + expect(result).to be false + end + + it "returns true if the user filter is set to 'yours'" do + allow(self).to receive(:filter_selected?).with("user", "yours", filter_type).and_return true + expect(result).to be true + end + + it "returns false if any filters other than the user filter are set" do + allow(self).to receive(:filter_selected?).and_return true + allow(self).to receive(:filter_selected?).with("user", "yours", filter_type).and_return false + expect(result).to be false + end + + context "when there is a search term present" do + let(:search_term) { "word" } + + it "still returns false as long as the user filter is not set to yours" do + allow(self).to receive(:filter_selected?).with("user", "yours", filter_type).and_return false + expect(result).to be false + end + end + end + + context "when logged in as a support user or data coordinator" do + let(:support_user) { create(:user, :support) } + let(:data_coordinator) { create(:user, :data_coordinator) } + let(:support_result) { display_delete_logs?(support_user, search_term, filter_type) } + let(:coordinator_result) { display_delete_logs?(data_coordinator, search_term, filter_type) } + let(:results) { [support_result, coordinator_result] } + + it "returns false if no filters or search are set" do + allow(self).to receive(:any_filter_selected?).and_return false + expect(results).to all be false + end + + it "returns true if any filter is set" do + allow(self).to receive(:any_filter_selected?).and_return true + expect(results).to all be true + end + + context "when there is a search term present" do + let(:search_term) { "word" } + + it "returns true if no filter is selected" do + allow(self).to receive(:any_filter_selected?).and_return false + expect(results).to all be true + end + + it "returns true if any filter is selected" do + allow(self).to receive(:any_filter_selected?).and_return true + expect(results).to all be true + end + end + end + end +end diff --git a/spec/models/forms/delete_logs_form_spec.rb b/spec/models/forms/delete_logs_form_spec.rb new file mode 100644 index 000000000..adad36d90 --- /dev/null +++ b/spec/models/forms/delete_logs_form_spec.rb @@ -0,0 +1,89 @@ +require "rails_helper" + +RSpec.describe Forms::DeleteLogsForm do + let(:delete_logs_form) { described_class.new(attributes) } + + let(:attributes) do + { + log_type:, + search_term:, + current_user:, + log_filters:, + selected_ids:, + delete_confirmation_path:, + back_to_logs_path:, + delete_path:, + } + end + let(:log_type) { :lettings } + let(:search_term) { "meaning" } + let(:current_user) { create(:user) } + let(:log_filters) do + { + "years" => [""], + "status" => ["", "completed"], + "user" => "yours", + } + end + let(:selected_ids) { [visible_logs.first.id] } + let(:delete_confirmation_path) { "/lettings-logs/delete-logs-confirmation" } + let(:back_to_logs_path) { "/lettings-logs?search=meaning" } + let(:delete_path) { "/lettings-logs/delete-logs" } + + let(:visible_logs) { create_list(:lettings_log, 3, created_by: current_user) } + + before do + allow(FilterManager).to receive(:filter_logs).and_return visible_logs + end + + it "exposes the log type" do + expect(delete_logs_form.log_type).to be log_type + end + + it "exposes the search term" do + expect(delete_logs_form.search_term).to be search_term + end + + it "exposes the paths" do + expect(delete_logs_form.delete_confirmation_path).to be delete_confirmation_path + expect(delete_logs_form.back_to_logs_path).to be back_to_logs_path + expect(delete_logs_form.delete_path).to be delete_path + end + + it "exposes the logs returned by the filter manager" do + expect(delete_logs_form.logs).to be visible_logs + end + + it "exposes the selected ids" do + expect(delete_logs_form.selected_ids).to be selected_ids + end + + context "when selected ids are not provided to the initializer" do + let(:selected_ids) { nil } + + it "sets the selected ids to be all logs" do + expect(delete_logs_form.selected_ids).to match_array visible_logs.map(&:id) + end + end + + it "calls the filter manager with the correct arguments" do + create(:lettings_log) + + expect(FilterManager).to receive(:filter_logs) { |arg1, arg2, arg3, arg4, arg5| + expect(arg1).to contain_exactly(*visible_logs) + expect(arg2).to eq search_term + expect(arg3).to eq log_filters + expect(arg4).to be nil + expect(arg5).to be current_user + }.and_return visible_logs + delete_logs_form + end + + it "exposes the number of logs" do + expect(delete_logs_form.log_count).to be visible_logs.count + end + + it "provides the name of the table partial relevant to the log type" do + expect(delete_logs_form.table_partial_name).to eq "logs/delete_logs_table_lettings" + end +end diff --git a/spec/requests/delete_logs_controller_spec.rb b/spec/requests/delete_logs_controller_spec.rb new file mode 100644 index 000000000..fd509d699 --- /dev/null +++ b/spec/requests/delete_logs_controller_spec.rb @@ -0,0 +1,946 @@ +require "rails_helper" + +RSpec.describe "DeleteLogs", type: :request do + let(:page) { Capybara::Node::Simple.new(response.body) } + let(:user) { create(:user, name: "Richard MacDuff") } + + before do + allow(user).to receive(:need_two_factor_authentication?).and_return(false) + sign_in user + end + + describe "GET lettings-logs/delete-logs" do + let!(:log_1) { create(:lettings_log, :in_progress, created_by: user) } + let!(:log_2) { create(:lettings_log, :completed, created_by: user) } + + before do + allow(FilterManager).to receive(:filter_logs).and_return LettingsLog.all + end + + it "calls the filter service with the filters in the session and the search term from the query params" do + search = "Schrödinger's cat" + logs_filters = { + "years" => [""], + "status" => ["", "in_progress"], + "user" => "all", + } + get lettings_logs_path(logs_filters) # adds the filters to the session + + expect(FilterManager).to receive(:filter_logs) { |arg1, arg2, arg3| + expect(arg1).to contain_exactly(log_1, log_2) + expect(arg2).to eq search + expect(arg3).to eq logs_filters + }.and_return LettingsLog.all + + get delete_logs_lettings_logs_path(search:) + end + + it "displays the logs returned by the filter service" do + get delete_logs_lettings_logs_path + + table_body_rows = page.find_all("tbody tr") + expect(table_body_rows.count).to be 2 + ids_in_table = table_body_rows.map { |row| row.first("td").text.strip } + expect(ids_in_table).to match_array [log_1.id.to_s, log_2.id.to_s] + end + + it "checks all checkboxes by default" do + get delete_logs_lettings_logs_path + + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + expect(checkboxes.count).to be 2 + expect(checkboxes).to all be_checked + end + end + + describe "POST lettings-logs/delete-logs" do + let!(:log_1) { create(:lettings_log, :in_progress, created_by: user) } + let!(:log_2) { create(:lettings_log, :completed, created_by: user) } + let(:selected_ids) { log_1.id } + + before do + allow(FilterManager).to receive(:filter_logs).and_return LettingsLog.all + end + + it "throws an error if selected ids are not provided" do + expect { post delete_logs_lettings_logs_path }.to raise_error ActionController::ParameterMissing + end + + it "calls the filter service with the filters in the session and the search term from the query params" do + search = "Schrödinger's cat" + logs_filters = { + "years" => [""], + "status" => ["", "in_progress"], + "user" => "all", + } + get lettings_logs_path(logs_filters) # adds the filters to the session + + expect(FilterManager).to receive(:filter_logs) { |arg1, arg2, arg3| + expect(arg1).to contain_exactly(log_1, log_2) + expect(arg2).to eq search + expect(arg3).to eq logs_filters + }.and_return LettingsLog.all + + post delete_logs_lettings_logs_path(search:, selected_ids:) + end + + it "displays the logs returned by the filter service" do + post delete_logs_lettings_logs_path(selected_ids:) + + table_body_rows = page.find_all("tbody tr") + expect(table_body_rows.count).to be 2 + ids_in_table = table_body_rows.map { |row| row.first("td").text.strip } + expect(ids_in_table).to match_array [log_1.id.to_s, log_2.id.to_s] + end + + it "only checks the selected checkboxes when selected_ids provided" do + post delete_logs_lettings_logs_path(selected_ids:) + + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + checkbox_expected_checked = checkboxes.find { |cb| cb.value == log_1.id.to_s } + checkbox_expected_unchecked = checkboxes.find { |cb| cb.value == log_2.id.to_s } + expect(checkbox_expected_checked).to be_checked + expect(checkbox_expected_unchecked).not_to be_checked + end + end + + describe "POST lettings-logs/delete-logs-confirmation" do + let(:log_1) { create(:lettings_log, :in_progress) } + let(:log_2) { create(:lettings_log, :completed) } + let(:log_3) { create(:lettings_log, :in_progress) } + let(:params) do + { + forms_delete_logs_form: { + search_term: "milk", + selected_ids: [log_1, log_2].map(&:id), + }, + } + end + + before do + post delete_logs_confirmation_lettings_logs_path, params: params + end + + it "requires delete logs form data to be provided" do + expect { post delete_logs_confirmation_lettings_logs_path }.to raise_error(ActionController::ParameterMissing) + end + + it "shows the correct title" do + expect(page.find("h1").text).to include "Are you sure you want to delete these logs?" + end + + it "shows the correct information text to the user" do + expect(page).to have_selector("p", text: "You've selected 2 logs to delete") + end + + context "when only one log is selected" do + let(:params) do + { + forms_delete_logs_form: { + search_term: "milk", + selected_ids: [log_1].map(&:id), + }, + } + end + + it "shows the correct information text to the user in the singular" do + expect(page).to have_selector("p", text: "You've selected 1 log to delete") + end + end + + it "shows a warning to the user" do + expect(page).to have_selector(".govuk-warning-text", text: "You will not be able to undo this action") + end + + it "shows a button to delete the selected logs" do + expect(page).to have_selector("form.button_to button", text: "Delete logs") + end + + it "the delete logs button submits the correct data to the correct path" do + form_containing_button = page.find("form.button_to") + + expect(form_containing_button[:action]).to eq delete_logs_lettings_logs_path + expect(form_containing_button).to have_field "_method", type: :hidden, with: "delete" + expect(form_containing_button).to have_field "ids[]", type: :hidden, with: log_1.id + expect(form_containing_button).to have_field "ids[]", type: :hidden, with: log_2.id + end + + it "shows a cancel button with the correct style" do + expect(page).to have_selector("button.govuk-button--secondary", text: "Cancel") + end + + it "the cancel button submits the correct data to the correct path" do + form_containing_cancel = page.find_all("form").find { |form| form.has_selector?("button.govuk-button--secondary") } + expect(form_containing_cancel).to have_field("selected_ids", type: :hidden, with: [log_1, log_2].map(&:id).join(" ")) + expect(form_containing_cancel).to have_field("search", type: :hidden, with: "milk") + expect(form_containing_cancel[:method]).to eq "post" + expect(form_containing_cancel[:action]).to eq delete_logs_lettings_logs_path + end + + context "when no logs are selected" do + let(:params) do + { + forms_delete_logs_form: { + log_type: :lettings, + log_ids: [log_1, log_2, log_3].map(&:id).join(" "), + }, + } + end + + before do + post delete_logs_confirmation_lettings_logs_path, params: params + end + + it "renders the list of logs table again" do + expect(page.find("h1").text).to include "Review the logs you want to delete" + end + + it "displays an error message" do + expect(page).to have_selector(".govuk-error-summary", text: "Select at least one log to delete or press cancel to return") + end + + it "renders the table with all checkboxes unchecked" do + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + checkboxes.each do |checkbox| + expect(checkbox).not_to be_checked + end + end + end + end + + describe "DELETE lettings-logs/delete-logs" do + let(:log_1) { create(:lettings_log, :in_progress, created_by: user) } + let(:params) { { ids: [log_1.id, log_2.id] } } + + context "when the user is authorized to delete the logs provided" do + let(:log_2) { create(:lettings_log, :completed, created_by: user) } + + it "deletes the logs provided" do + delete delete_logs_lettings_logs_path, params: params + log_1.reload + expect(log_1.status).to eq "deleted" + expect(log_1.discarded_at).not_to be nil + log_2.reload + expect(log_2.status).to eq "deleted" + expect(log_2.discarded_at).not_to be nil + end + + it "redirects to the lettings log index and displays a notice that the logs have been deleted" do + delete delete_logs_lettings_logs_path, params: params + expect(response).to redirect_to lettings_logs_path + follow_redirect! + expect(page).to have_selector(".govuk-notification-banner--success") + expect(page).to have_selector(".govuk-notification-banner--success", text: "2 logs have been deleted") + end + end + + context "when the user is not authorized to delete all the logs provided" do + let(:log_2) { create(:lettings_log, :completed) } + + it "returns unauthorised and only deletes logs for which the user is authorised" do + delete delete_logs_lettings_logs_path, params: params + expect(response).to have_http_status(:unauthorized) + log_1.reload + expect(log_1.status).to eq "deleted" + expect(log_1.discarded_at).not_to be nil + log_2.reload + expect(log_2.discarded_at).to be nil + end + end + end + + describe "GET sales-logs/delete-logs" do + let!(:log_1) { create(:sales_log, :in_progress, created_by: user) } + let!(:log_2) { create(:sales_log, :completed, created_by: user) } + + before do + allow(FilterManager).to receive(:filter_logs).and_return SalesLog.all + end + + it "calls the filter service with the filters in the session and the search term from the query params" do + search = "Schrödinger's cat" + logs_filters = { + "years" => [""], + "status" => ["", "in_progress"], + "user" => "all", + } + get sales_logs_path(logs_filters) # adds the filters to the session + + expect(FilterManager).to receive(:filter_logs) { |arg1, arg2, arg3| + expect(arg1).to contain_exactly(log_1, log_2) + expect(arg2).to eq search + expect(arg3).to eq logs_filters + }.and_return SalesLog.all + + get delete_logs_sales_logs_path(search:) + end + + it "displays the logs returned by the filter service" do + get delete_logs_sales_logs_path + + table_body_rows = page.find_all("tbody tr") + expect(table_body_rows.count).to be 2 + ids_in_table = table_body_rows.map { |row| row.first("td").text.strip } + expect(ids_in_table).to match_array [log_1.id.to_s, log_2.id.to_s] + end + + it "checks all checkboxes by default" do + get delete_logs_sales_logs_path + + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + expect(checkboxes.count).to be 2 + expect(checkboxes).to all be_checked + end + end + + describe "POST sales-logs/delete-logs" do + let!(:log_1) { create(:sales_log, :in_progress, created_by: user) } + let!(:log_2) { create(:sales_log, :completed, created_by: user) } + let(:selected_ids) { log_1.id } + + before do + allow(FilterManager).to receive(:filter_logs).and_return SalesLog.all + end + + it "throws an error if selected ids are not provided" do + expect { post delete_logs_sales_logs_path }.to raise_error ActionController::ParameterMissing + end + + it "calls the filter service with the filters in the session and the search term from the query params" do + search = "Schrödinger's cat" + logs_filters = { + "years" => [""], + "status" => ["", "in_progress"], + "user" => "all", + } + get sales_logs_path(logs_filters) # adds the filters to the session + + expect(FilterManager).to receive(:filter_logs) { |arg1, arg2, arg3| + expect(arg1).to contain_exactly(log_1, log_2) + expect(arg2).to eq search + expect(arg3).to eq logs_filters + }.and_return SalesLog.all + + post delete_logs_sales_logs_path(search:, selected_ids:) + end + + it "displays the logs returned by the filter service" do + post delete_logs_sales_logs_path(selected_ids:) + + table_body_rows = page.find_all("tbody tr") + expect(table_body_rows.count).to be 2 + ids_in_table = table_body_rows.map { |row| row.first("td").text.strip } + expect(ids_in_table).to match_array [log_1.id.to_s, log_2.id.to_s] + end + + it "only checks the selected checkboxes when selected_ids provided" do + post delete_logs_sales_logs_path(selected_ids:) + + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + checkbox_expected_checked = checkboxes.find { |cb| cb.value == log_1.id.to_s } + checkbox_expected_unchecked = checkboxes.find { |cb| cb.value == log_2.id.to_s } + expect(checkbox_expected_checked).to be_checked + expect(checkbox_expected_unchecked).not_to be_checked + end + end + + describe "POST sales-logs/delete-logs-confirmation" do + let(:log_1) { create(:sales_log, :in_progress) } + let(:log_2) { create(:sales_log, :completed) } + let(:log_3) { create(:sales_log, :in_progress) } + let(:params) do + { + forms_delete_logs_form: { + search_term: "milk", + selected_ids: [log_1, log_2].map(&:id), + }, + } + end + + before do + post delete_logs_confirmation_sales_logs_path, params: params + end + + it "requires delete logs form data to be provided" do + expect { post delete_logs_confirmation_sales_logs_path }.to raise_error(ActionController::ParameterMissing) + end + + it "shows the correct title" do + expect(page.find("h1").text).to include "Are you sure you want to delete these logs?" + end + + it "shows the correct information text to the user" do + expect(page).to have_selector("p", text: "You've selected 2 logs to delete") + end + + context "when only one log is selected" do + let(:params) do + { + forms_delete_logs_form: { + search_term: "milk", + selected_ids: [log_1].map(&:id), + }, + } + end + + it "shows the correct information text to the user in the singular" do + expect(page).to have_selector("p", text: "You've selected 1 log to delete") + end + end + + it "shows a warning to the user" do + expect(page).to have_selector(".govuk-warning-text", text: "You will not be able to undo this action") + end + + it "shows a button to delete the selected logs" do + expect(page).to have_selector("form.button_to button", text: "Delete logs") + end + + it "the delete logs button submits the correct data to the correct path" do + form_containing_button = page.find("form.button_to") + + expect(form_containing_button[:action]).to eq delete_logs_sales_logs_path + expect(form_containing_button).to have_field "_method", type: :hidden, with: "delete" + expect(form_containing_button).to have_field "ids[]", type: :hidden, with: log_1.id + expect(form_containing_button).to have_field "ids[]", type: :hidden, with: log_2.id + end + + it "shows a cancel button with the correct style" do + expect(page).to have_selector("button.govuk-button--secondary", text: "Cancel") + end + + it "the cancel button submits the correct data to the correct path" do + form_containing_cancel = page.find_all("form").find { |form| form.has_selector?("button.govuk-button--secondary") } + expect(form_containing_cancel).to have_field("selected_ids", type: :hidden, with: [log_1, log_2].map(&:id).join(" ")) + expect(form_containing_cancel).to have_field("search", type: :hidden, with: "milk") + expect(form_containing_cancel[:method]).to eq "post" + expect(form_containing_cancel[:action]).to eq delete_logs_sales_logs_path + end + + context "when no logs are selected" do + let(:params) do + { + forms_delete_logs_form: { + log_type: :sales, + log_ids: [log_1, log_2, log_3].map(&:id).join(" "), + }, + } + end + + before do + post delete_logs_confirmation_sales_logs_path, params: params + end + + it "renders the list of logs table again" do + expect(page.find("h1").text).to include "Review the logs you want to delete" + end + + it "displays an error message" do + expect(page).to have_selector(".govuk-error-summary", text: "Select at least one log to delete or press cancel to return") + end + + it "renders the table with all checkboxes unchecked" do + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + checkboxes.each do |checkbox| + expect(checkbox).not_to be_checked + end + end + end + end + + describe "DELETE sales-logs/delete-logs" do + let(:log_1) { create(:sales_log, :in_progress, created_by: user) } + let(:params) { { ids: [log_1.id, log_2.id] } } + + context "when the user is authorized to delete the logs provided" do + let(:log_2) { create(:sales_log, :completed, created_by: user) } + + it "deletes the logs provided" do + delete delete_logs_sales_logs_path, params: params + log_1.reload + expect(log_1.status).to eq "deleted" + expect(log_1.discarded_at).not_to be nil + log_2.reload + expect(log_2.status).to eq "deleted" + expect(log_2.discarded_at).not_to be nil + end + + it "redirects to the sales log index and displays a notice that the logs have been deleted" do + delete delete_logs_sales_logs_path, params: params + expect(response).to redirect_to sales_logs_path + follow_redirect! + expect(page).to have_selector(".govuk-notification-banner--success") + expect(page).to have_selector(".govuk-notification-banner--success", text: "2 logs have been deleted") + end + end + + context "when the user is not authorized to delete all the logs provided" do + let(:log_2) { create(:sales_log, :completed) } + + it "returns unauthorised and only deletes logs for which the user is authorised" do + delete delete_logs_sales_logs_path, params: params + expect(response).to have_http_status(:unauthorized) + log_1.reload + expect(log_1.status).to eq "deleted" + expect(log_1.discarded_at).not_to be nil + log_2.reload + expect(log_2.discarded_at).to be nil + end + end + end + + context "when a support user navigates to the organisations tab" do + let(:organisation) { create(:organisation, name: "Schmorganisation") } + let(:user) { create(:user, :support, name: "Urban Chronotis") } + + describe "GET organisations/delete-lettings-logs" do + let!(:log_1) { create(:lettings_log, :in_progress, owning_organisation: organisation) } + let!(:log_2) { create(:lettings_log, :completed, owning_organisation: organisation) } + + before do + allow(FilterManager).to receive(:filter_logs).and_return LettingsLog.all + end + + it "calls the filter service with the filters in the session and the search term from the query params" do + search = "Schrödinger's cat" + logs_filters = { + "years" => [""], + "status" => ["", "in_progress"], + "user" => "all", + } + get lettings_logs_path(logs_filters) # adds the filters to the session + + expect(FilterManager).to receive(:filter_logs) { |arg1, arg2, arg3| + expect(arg1).to contain_exactly(log_1, log_2) + expect(arg2).to eq search + expect(arg3).to eq logs_filters.merge(organisation: organisation.id.to_s) + }.and_return LettingsLog.all + + get delete_lettings_logs_organisation_path(id: organisation, search:) + end + + it "displays the logs returned by the filter service" do + get delete_lettings_logs_organisation_path(id: organisation) + + table_body_rows = page.find_all("tbody tr") + expect(table_body_rows.count).to be 2 + ids_in_table = table_body_rows.map { |row| row.first("td").text.strip } + expect(ids_in_table).to match_array [log_1.id.to_s, log_2.id.to_s] + end + + it "checks all checkboxes by default" do + get delete_lettings_logs_organisation_path(id: organisation) + + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + expect(checkboxes.count).to be 2 + expect(checkboxes).to all be_checked + end + end + + describe "POST organisations/delete-lettings-logs" do + let!(:log_1) { create(:lettings_log, :in_progress, owning_organisation: organisation) } + let!(:log_2) { create(:lettings_log, :completed, owning_organisation: organisation) } + let(:selected_ids) { log_1.id } + + before do + allow(FilterManager).to receive(:filter_logs).and_return LettingsLog.all + end + + it "throws an error if selected ids are not provided" do + expect { post delete_lettings_logs_organisation_path(id: organisation) }.to raise_error ActionController::ParameterMissing + end + + it "calls the filter service with the filters in the session and the search term from the query params" do + search = "Schrödinger's cat" + logs_filters = { + "years" => [""], + "status" => ["", "in_progress"], + "user" => "all", + } + get lettings_logs_path(logs_filters) # adds the filters to the session + + expect(FilterManager).to receive(:filter_logs) { |arg1, arg2, arg3| + expect(arg1).to contain_exactly(log_1, log_2) + expect(arg2).to eq search + expect(arg3).to eq logs_filters.merge(organisation: organisation.id.to_s) + }.and_return LettingsLog.all + + post delete_lettings_logs_organisation_path(id: organisation, search:, selected_ids:) + end + + it "displays the logs returned by the filter service" do + post delete_lettings_logs_organisation_path(id: organisation, selected_ids:) + + table_body_rows = page.find_all("tbody tr") + expect(table_body_rows.count).to be 2 + ids_in_table = table_body_rows.map { |row| row.first("td").text.strip } + expect(ids_in_table).to match_array [log_1.id.to_s, log_2.id.to_s] + end + + it "only checks the selected checkboxes when selected_ids provided" do + post delete_lettings_logs_organisation_path(id: organisation, selected_ids:) + + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + checkbox_expected_checked = checkboxes.find { |cb| cb.value == log_1.id.to_s } + checkbox_expected_unchecked = checkboxes.find { |cb| cb.value == log_2.id.to_s } + expect(checkbox_expected_checked).to be_checked + expect(checkbox_expected_unchecked).not_to be_checked + end + end + + describe "POST organisations/delete-lettings-logs-confirmation" do + let(:log_1) { create(:lettings_log, :in_progress, owning_organisation: organisation) } + let(:log_2) { create(:lettings_log, :completed, owning_organisation: organisation) } + let(:log_3) { create(:lettings_log, :in_progress, owning_organisation: organisation) } + let(:params) do + { + forms_delete_logs_form: { + search_term: "milk", + selected_ids: [log_1, log_2].map(&:id), + }, + } + end + + before do + post delete_lettings_logs_confirmation_organisation_path(id: organisation), params: params + end + + it "requires delete logs form data to be provided" do + expect { post delete_lettings_logs_confirmation_organisation_path(id: organisation) }.to raise_error(ActionController::ParameterMissing) + end + + it "shows the correct title" do + expect(page.find("h1").text).to include "Are you sure you want to delete these logs?" + end + + it "shows the correct information text to the user" do + expect(page).to have_selector("p", text: "You've selected 2 logs to delete") + end + + context "when only one log is selected" do + let(:params) do + { + forms_delete_logs_form: { + search_term: "milk", + selected_ids: [log_1].map(&:id), + }, + } + end + + it "shows the correct information text to the user in the singular" do + expect(page).to have_selector("p", text: "You've selected 1 log to delete") + end + end + + it "shows a warning to the user" do + expect(page).to have_selector(".govuk-warning-text", text: "You will not be able to undo this action") + end + + it "shows a button to delete the selected logs" do + expect(page).to have_selector("form.button_to button", text: "Delete logs") + end + + it "the delete logs button submits the correct data to the correct path" do + form_containing_button = page.find("form.button_to") + + expect(form_containing_button[:action]).to eq delete_lettings_logs_organisation_path(id: organisation) + expect(form_containing_button).to have_field "_method", type: :hidden, with: "delete" + expect(form_containing_button).to have_field "ids[]", type: :hidden, with: log_1.id + expect(form_containing_button).to have_field "ids[]", type: :hidden, with: log_2.id + end + + it "shows a cancel button with the correct style" do + expect(page).to have_selector("button.govuk-button--secondary", text: "Cancel") + end + + it "the cancel button submits the correct data to the correct path" do + form_containing_cancel = page.find_all("form").find { |form| form.has_selector?("button.govuk-button--secondary") } + expect(form_containing_cancel).to have_field("selected_ids", type: :hidden, with: [log_1, log_2].map(&:id).join(" ")) + expect(form_containing_cancel).to have_field("search", type: :hidden, with: "milk") + expect(form_containing_cancel[:method]).to eq "post" + expect(form_containing_cancel[:action]).to eq delete_lettings_logs_organisation_path(id: organisation) + end + + context "when no logs are selected" do + let(:params) do + { + forms_delete_logs_form: { + log_type: :lettings, + log_ids: [log_1, log_2, log_3].map(&:id).join(" "), + }, + } + end + + before do + post delete_lettings_logs_confirmation_organisation_path(id: organisation, params:) + end + + it "renders the list of logs table again" do + expect(page.find("h1").text).to include "Review the logs you want to delete" + end + + it "displays an error message" do + expect(page).to have_selector(".govuk-error-summary", text: "Select at least one log to delete or press cancel to return") + end + + it "renders the table with all checkboxes unchecked" do + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + checkboxes.each do |checkbox| + expect(checkbox).not_to be_checked + end + end + end + end + + describe "DELETE organisations/delete-lettings-logs" do + let(:log_1) { create(:lettings_log, :in_progress, owning_organisation: organisation) } + let(:log_2) { create(:lettings_log, :completed, owning_organisation: organisation) } + let(:params) { { ids: [log_1.id, log_2.id] } } + + before do + delete delete_lettings_logs_organisation_path(id: organisation, params:) + end + + it "deletes the logs provided" do + log_1.reload + expect(log_1.status).to eq "deleted" + expect(log_1.discarded_at).not_to be nil + log_2.reload + expect(log_2.status).to eq "deleted" + expect(log_2.discarded_at).not_to be nil + end + + it "redirects to the lettings log index for that organisation and displays a notice that the logs have been deleted" do + expect(response).to redirect_to lettings_logs_organisation_path(id: organisation) + follow_redirect! + expect(page).to have_selector(".govuk-notification-banner--success") + expect(page).to have_selector(".govuk-notification-banner--success", text: "2 logs have been deleted") + end + end + + describe "GET organisations/delete-sales-logs" do + let!(:log_1) { create(:sales_log, :in_progress, owning_organisation: organisation) } + let!(:log_2) { create(:sales_log, :completed, owning_organisation: organisation) } + + before do + allow(FilterManager).to receive(:filter_logs).and_return SalesLog.all + end + + it "calls the filter service with the filters in the session and the search term from the query params" do + search = "Schrödinger's cat" + logs_filters = { + "years" => [""], + "status" => ["", "in_progress"], + "user" => "all", + } + get sales_logs_path(logs_filters) # adds the filters to the session + + expect(FilterManager).to receive(:filter_logs) { |arg1, arg2, arg3| + expect(arg1).to contain_exactly(log_1, log_2) + expect(arg2).to eq search + expect(arg3).to eq logs_filters.merge(organisation: organisation.id.to_s) + }.and_return SalesLog.all + + get delete_sales_logs_organisation_path(id: organisation, search:) + end + + it "displays the logs returned by the filter service" do + get delete_sales_logs_organisation_path(id: organisation) + + table_body_rows = page.find_all("tbody tr") + expect(table_body_rows.count).to be 2 + ids_in_table = table_body_rows.map { |row| row.first("td").text.strip } + expect(ids_in_table).to match_array [log_1.id.to_s, log_2.id.to_s] + end + + it "checks all checkboxes by default" do + get delete_sales_logs_organisation_path(id: organisation) + + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + expect(checkboxes.count).to be 2 + expect(checkboxes).to all be_checked + end + end + + describe "POST organisations/delete-sales-logs" do + let!(:log_1) { create(:sales_log, :in_progress, owning_organisation: organisation) } + let!(:log_2) { create(:sales_log, :completed, owning_organisation: organisation) } + let(:selected_ids) { log_1.id } + + before do + allow(FilterManager).to receive(:filter_logs).and_return SalesLog.all + end + + it "throws an error if selected ids are not provided" do + expect { post delete_sales_logs_organisation_path(id: organisation) }.to raise_error ActionController::ParameterMissing + end + + it "calls the filter service with the filters in the session and the search term from the query params" do + search = "Schrödinger's cat" + logs_filters = { + "years" => [""], + "status" => ["", "in_progress"], + "user" => "all", + } + get sales_logs_path(logs_filters) # adds the filters to the session + + expect(FilterManager).to receive(:filter_logs) { |arg1, arg2, arg3| + expect(arg1).to contain_exactly(log_1, log_2) + expect(arg2).to eq search + expect(arg3).to eq logs_filters.merge(organisation: organisation.id.to_s) + }.and_return SalesLog.all + + post delete_sales_logs_organisation_path(id: organisation, search:, selected_ids:) + end + + it "displays the logs returned by the filter service" do + post delete_sales_logs_organisation_path(id: organisation, selected_ids:) + + table_body_rows = page.find_all("tbody tr") + expect(table_body_rows.count).to be 2 + ids_in_table = table_body_rows.map { |row| row.first("td").text.strip } + expect(ids_in_table).to match_array [log_1.id.to_s, log_2.id.to_s] + end + + it "only checks the selected checkboxes when selected_ids provided" do + post delete_sales_logs_organisation_path(id: organisation, selected_ids:) + + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + checkbox_expected_checked = checkboxes.find { |cb| cb.value == log_1.id.to_s } + checkbox_expected_unchecked = checkboxes.find { |cb| cb.value == log_2.id.to_s } + expect(checkbox_expected_checked).to be_checked + expect(checkbox_expected_unchecked).not_to be_checked + end + end + + describe "POST organisations/delete-sales-logs-confirmation" do + let(:log_1) { create(:sales_log, :in_progress, owning_organisation: organisation) } + let(:log_2) { create(:sales_log, :completed, owning_organisation: organisation) } + let(:log_3) { create(:sales_log, :in_progress, owning_organisation: organisation) } + let(:params) do + { + forms_delete_logs_form: { + search_term: "milk", + selected_ids: [log_1, log_2].map(&:id), + }, + } + end + + before do + post delete_sales_logs_confirmation_organisation_path(id: organisation), params: params + end + + it "requires delete logs form data to be provided" do + expect { post delete_sales_logs_confirmation_organisation_path(id: organisation) }.to raise_error(ActionController::ParameterMissing) + end + + it "shows the correct title" do + expect(page.find("h1").text).to include "Are you sure you want to delete these logs?" + end + + it "shows the correct information text to the user" do + expect(page).to have_selector("p", text: "You've selected 2 logs to delete") + end + + context "when only one log is selected" do + let(:params) do + { + forms_delete_logs_form: { + search_term: "milk", + selected_ids: [log_1].map(&:id), + }, + } + end + + it "shows the correct information text to the user in the singular" do + expect(page).to have_selector("p", text: "You've selected 1 log to delete") + end + end + + it "shows a warning to the user" do + expect(page).to have_selector(".govuk-warning-text", text: "You will not be able to undo this action") + end + + it "shows a button to delete the selected logs" do + expect(page).to have_selector("form.button_to button", text: "Delete logs") + end + + it "the delete logs button submits the correct data to the correct path" do + form_containing_button = page.find("form.button_to") + + expect(form_containing_button[:action]).to eq delete_sales_logs_organisation_path(id: organisation) + expect(form_containing_button).to have_field "_method", type: :hidden, with: "delete" + expect(form_containing_button).to have_field "ids[]", type: :hidden, with: log_1.id + expect(form_containing_button).to have_field "ids[]", type: :hidden, with: log_2.id + end + + it "shows a cancel button with the correct style" do + expect(page).to have_selector("button.govuk-button--secondary", text: "Cancel") + end + + it "the cancel button submits the correct data to the correct path" do + form_containing_cancel = page.find_all("form").find { |form| form.has_selector?("button.govuk-button--secondary") } + expect(form_containing_cancel).to have_field("selected_ids", type: :hidden, with: [log_1, log_2].map(&:id).join(" ")) + expect(form_containing_cancel).to have_field("search", type: :hidden, with: "milk") + expect(form_containing_cancel[:method]).to eq "post" + expect(form_containing_cancel[:action]).to eq delete_sales_logs_organisation_path(id: organisation) + end + + context "when no logs are selected" do + let(:params) do + { + forms_delete_logs_form: { + log_type: :sales, + log_ids: [log_1, log_2, log_3].map(&:id).join(" "), + }, + } + end + + before do + post delete_sales_logs_confirmation_organisation_path(id: organisation, params:) + end + + it "renders the list of logs table again" do + expect(page.find("h1").text).to include "Review the logs you want to delete" + end + + it "displays an error message" do + expect(page).to have_selector(".govuk-error-summary", text: "Select at least one log to delete or press cancel to return") + end + + it "renders the table with all checkboxes unchecked" do + checkboxes = page.find_all("tbody tr").map { |row| row.find("input") } + checkboxes.each do |checkbox| + expect(checkbox).not_to be_checked + end + end + end + end + + describe "DELETE organisations/delete-sales-logs" do + let(:log_1) { create(:sales_log, :in_progress, owning_organisation: organisation) } + let(:log_2) { create(:sales_log, :completed, owning_organisation: organisation) } + let(:params) { { ids: [log_1.id, log_2.id] } } + + before do + delete delete_sales_logs_organisation_path(id: organisation, params:) + end + + it "deletes the logs provided" do + log_1.reload + expect(log_1.status).to eq "deleted" + expect(log_1.discarded_at).not_to be nil + log_2.reload + expect(log_2.status).to eq "deleted" + expect(log_2.discarded_at).not_to be nil + end + + it "redirects to the sales log index for that organisation and displays a notice that the logs have been deleted" do + expect(response).to redirect_to sales_logs_organisation_path(id: organisation) + follow_redirect! + expect(page).to have_selector(".govuk-notification-banner--success") + expect(page).to have_selector(".govuk-notification-banner--success", text: "2 logs have been deleted") + end + end + end +end diff --git a/spec/requests/lettings_logs_controller_spec.rb b/spec/requests/lettings_logs_controller_spec.rb index 33108e465..50c4d6763 100644 --- a/spec/requests/lettings_logs_controller_spec.rb +++ b/spec/requests/lettings_logs_controller_spec.rb @@ -246,10 +246,39 @@ RSpec.describe LettingsLogsController, type: :request do end it "does not have a button for creating sales logs" do - get "/lettings-logs", headers:, params: {} + get lettings_logs_path, headers:, params: {} page.assert_selector(".govuk-button", text: "Create a new sales log", count: 0) page.assert_selector(".govuk-button", text: "Create a new lettings log", count: 1) end + + context "and the state of filters and search is such that display_delete_logs returns true" do + before do + allow_any_instance_of(LogListHelper).to receive(:display_delete_logs?).and_return(true) # rubocop:disable RSpec/AnyInstance + end + + it "displays the delete logs button with the correct path if there are logs visibile" do + get lettings_logs_path(search: "LC783") + expect(page).to have_link "Delete logs", href: delete_logs_lettings_logs_path(search: "LC783") + end + + it "does not display the delete logs button if there are no logs displayed" do + LettingsLog.destroy_all + get lettings_logs_path + expect(page).not_to have_link "Delete logs" + end + end + + context "and the state of filters and search is such that display_delete_logs returns false" do + before do + allow_any_instance_of(LogListHelper).to receive(:display_delete_logs?).and_return(false) # rubocop:disable RSpec/AnyInstance + end + + it "does not display the delete logs button even if there are logs displayed" do + get lettings_logs_path + expect(page).to have_selector "article.app-log-summary" + expect(page).not_to have_link "Delete logs" + end + end end context "when the user is a customer support user" do @@ -347,7 +376,6 @@ RSpec.describe LettingsLogsController, type: :request do Timecop.freeze(2022, 3, 1) do example.run end - Timecop.return end let!(:lettings_log_2021) do @@ -538,7 +566,7 @@ RSpec.describe LettingsLogsController, type: :request do end end - context "when the user is not a customer support user" do + context "when the user is a data provider" do before do sign_in user end @@ -1348,7 +1376,7 @@ RSpec.describe LettingsLogsController, type: :request do context "when user not authorised" do let(:user) { create(:user) } - it "returns 404" do + it "returns 401" do delete_request expect(response).to have_http_status(:unauthorized) end @@ -1400,7 +1428,7 @@ RSpec.describe LettingsLogsController, type: :request do end end - describe "GET #csv-download" do + describe "GET csv-download" do let(:page) { Capybara::Node::Simple.new(response.body) } let(:user) { FactoryBot.create(:user) } let(:headers) { { "Accept" => "text/html" } } @@ -1482,7 +1510,7 @@ RSpec.describe LettingsLogsController, type: :request do end end - describe "POST #email-csv" do + describe "POST email-csv" do let(:other_organisation) { FactoryBot.create(:organisation) } let(:user) { FactoryBot.create(:user, :support) } diff --git a/spec/requests/organisations_controller_spec.rb b/spec/requests/organisations_controller_spec.rb index 466e7522e..a3d5ac41e 100644 --- a/spec/requests/organisations_controller_spec.rb +++ b/spec/requests/organisations_controller_spec.rb @@ -1221,210 +1221,228 @@ RSpec.describe OrganisationsController, type: :request do end end end - end - end - - context "when the user is a support user" do - let(:user) { create(:user, :support) } - before do - allow(user).to receive(:need_two_factor_authentication?).and_return(false) - sign_in user - end - - context "when they view the lettings logs tab" do - before do - create(:lettings_log, owning_organisation: organisation) - end - - it "has CSV download buttons with the correct paths if at least 1 log exists" do - get "/organisations/#{organisation.id}/lettings-logs" - expect(page).to have_link("Download (CSV)", href: "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=false") - expect(page).to have_link("Download (CSV, codes only)", href: "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=true") - end - - context "when you download the CSV" do - let(:other_organisation) { create(:organisation) } + context "when they view the lettings logs tab" do + let(:tenancycode) { "42" } before do - create_list(:lettings_log, 2, owning_organisation: organisation) - create(:lettings_log, owning_organisation: organisation, status: "pending", skip_update_status: true) - create_list(:lettings_log, 2, owning_organisation: other_organisation) + create(:lettings_log, owning_organisation: organisation, tenancycode:) end - it "only includes logs from that organisation" do - get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=false" + context "when there is at least one log visible" do + before do + get lettings_logs_organisation_path(organisation, search: tenancycode) + end + + it "shows the delete logs button with the correct path" do + expect(page).to have_link "Delete logs", href: delete_lettings_logs_organisation_path(search: tenancycode) + end - expect(page).to have_text("You've selected 3 logs.") + it "has CSV download buttons with the correct paths" do + expect(page).to have_link "Download (CSV)", href: lettings_logs_csv_download_organisation_path(organisation, codes_only: false, search: tenancycode) + expect(page).to have_link "Download (CSV, codes only)", href: lettings_logs_csv_download_organisation_path(organisation, codes_only: true, search: tenancycode) + end end - it "provides the organisation to the mail job" do - expect { - post "/organisations/#{organisation.id}/lettings-logs/email-csv?status[]=completed&codes_only=false", headers:, params: {} - }.to enqueue_job(EmailCsvJob).with(user, nil, { "status" => %w[completed] }, false, organisation, false) - end + context "when there are no visible logs" do + before do + LettingsLog.destroy_all + get lettings_logs_organisation_path(organisation) + end - it "provides the export type to the mail job" do - codes_only_export_type = false - expect { - post "/organisations/#{organisation.id}/lettings-logs/email-csv?codes_only=#{codes_only_export_type}", headers:, params: {} - }.to enqueue_job(EmailCsvJob).with(user, nil, {}, false, organisation, codes_only_export_type) - codes_only_export_type = true - expect { - post "/organisations/#{organisation.id}/lettings-logs/email-csv?codes_only=#{codes_only_export_type}", headers:, params: {} - }.to enqueue_job(EmailCsvJob).with(user, nil, {}, false, organisation, codes_only_export_type) + it "does not show the delete logs button " do + expect(page).not_to have_link "Delete logs" + end + + it "does not show the csv download buttons" do + expect(page).not_to have_link "Download (CSV)" + expect(page).not_to have_link "Download (CSV, codes only)" + end end - end - end - context "when they view the sales logs tab" do - before do - create(:sales_log, owning_organisation: organisation) - end + context "when you download the CSV" do + let(:other_organisation) { create(:organisation) } - it "has CSV download buttons with the correct paths if at least 1 log exists" do - get "/organisations/#{organisation.id}/sales-logs" - expect(page).to have_link("Download (CSV)", href: "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=false") - expect(page).to have_link("Download (CSV, codes only)", href: "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=true") - end + before do + create_list(:lettings_log, 2, owning_organisation: organisation) + create(:lettings_log, owning_organisation: organisation, status: "pending", skip_update_status: true) + create_list(:lettings_log, 2, owning_organisation: other_organisation) + end - context "when you download the CSV" do - let(:other_organisation) { create(:organisation) } + it "only includes logs from that organisation" do + get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=false" - before do - create_list(:sales_log, 2, owning_organisation: organisation) - create(:sales_log, owning_organisation: organisation, status: "pending", skip_update_status: true) - create_list(:sales_log, 2, owning_organisation: other_organisation) - end + expect(page).to have_text("You've selected 3 logs.") + end - it "only includes logs from that organisation" do - get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=false" + it "provides the organisation to the mail job" do + expect { + post "/organisations/#{organisation.id}/lettings-logs/email-csv?status[]=completed&codes_only=false", headers:, params: {} + }.to enqueue_job(EmailCsvJob).with(user, nil, { "status" => %w[completed] }, false, organisation, false) + end - expect(page).to have_text("You've selected 3 logs.") + it "provides the export type to the mail job" do + codes_only_export_type = false + expect { + post "/organisations/#{organisation.id}/lettings-logs/email-csv?codes_only=#{codes_only_export_type}", headers:, params: {} + }.to enqueue_job(EmailCsvJob).with(user, nil, {}, false, organisation, codes_only_export_type) + codes_only_export_type = true + expect { + post "/organisations/#{organisation.id}/lettings-logs/email-csv?codes_only=#{codes_only_export_type}", headers:, params: {} + }.to enqueue_job(EmailCsvJob).with(user, nil, {}, false, organisation, codes_only_export_type) + end end + end - it "provides the organisation to the mail job" do - expect { - post "/organisations/#{organisation.id}/sales-logs/email-csv?status[]=completed&codes_only=false", headers:, params: {} - }.to enqueue_job(EmailCsvJob).with(user, nil, { "status" => %w[completed] }, false, organisation, false, "sales") + context "when they view the sales logs tab" do + before do + create(:sales_log, owning_organisation: organisation) end - it "provides the log type to the mail job" do - log_type = "sales" - expect { - post "/organisations/#{organisation.id}/sales-logs/email-csv?status[]=completed&codes_only=false", headers:, params: {} - }.to enqueue_job(EmailCsvJob).with(user, nil, { "status" => %w[completed] }, false, organisation, false, log_type) + it "has CSV download buttons with the correct paths if at least 1 log exists" do + get "/organisations/#{organisation.id}/sales-logs" + expect(page).to have_link("Download (CSV)", href: "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=false") + expect(page).to have_link("Download (CSV, codes only)", href: "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=true") end - it "provides the export type to the mail job" do - codes_only_export_type = false - expect { - post "/organisations/#{organisation.id}/sales-logs/email-csv?codes_only=#{codes_only_export_type}", headers:, params: {} - }.to enqueue_job(EmailCsvJob).with(user, nil, {}, false, organisation, codes_only_export_type, "sales") - codes_only_export_type = true - expect { - post "/organisations/#{organisation.id}/sales-logs/email-csv?codes_only=#{codes_only_export_type}", headers:, params: {} - }.to enqueue_job(EmailCsvJob).with(user, nil, {}, false, organisation, codes_only_export_type, "sales") - end - end - end + context "when you download the CSV" do + let(:other_organisation) { create(:organisation) } - describe "GET #download_lettings_csv" do - it "renders a page with the correct header" do - get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=false", headers:, params: {} - header = page.find_css("h1") - expect(header.text).to include("Download CSV") - end + before do + create_list(:sales_log, 2, owning_organisation: organisation) + create(:sales_log, owning_organisation: organisation, status: "pending", skip_update_status: true) + create_list(:sales_log, 2, owning_organisation: other_organisation) + end - it "renders a form with the correct target containing a button with the correct text" do - get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=false", headers:, params: {} - form = page.find("form.button_to") - expect(form[:method]).to eq("post") - expect(form[:action]).to eq("/organisations/#{organisation.id}/lettings-logs/email-csv") - expect(form).to have_button("Send email") - end + it "only includes logs from that organisation" do + get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=false" - it "when codes_only query parameter is false, form contains hidden field with correct value" do - codes_only = false - get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=#{codes_only}", headers:, params: {} - hidden_field = page.find("form.button_to").find_field("codes_only", type: "hidden") - expect(hidden_field.value).to eq(codes_only.to_s) - end + expect(page).to have_text("You've selected 3 logs.") + end - it "when codes_only query parameter is true, form contains hidden field with correct value" do - codes_only = true - get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=#{codes_only}", headers:, params: {} - hidden_field = page.find("form.button_to").find_field("codes_only", type: "hidden") - expect(hidden_field.value).to eq(codes_only.to_s) - end + it "provides the organisation to the mail job" do + expect { + post "/organisations/#{organisation.id}/sales-logs/email-csv?status[]=completed&codes_only=false", headers:, params: {} + }.to enqueue_job(EmailCsvJob).with(user, nil, { "status" => %w[completed] }, false, organisation, false, "sales") + end - it "when query string contains search parameter, form contains hidden field with correct value" do - search_term = "blam" - get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=true&search=#{search_term}", headers:, params: {} - hidden_field = page.find("form.button_to").find_field("search", type: "hidden") - expect(hidden_field.value).to eq(search_term) - end - end + it "provides the log type to the mail job" do + log_type = "sales" + expect { + post "/organisations/#{organisation.id}/sales-logs/email-csv?status[]=completed&codes_only=false", headers:, params: {} + }.to enqueue_job(EmailCsvJob).with(user, nil, { "status" => %w[completed] }, false, organisation, false, log_type) + end - describe "GET #download_sales_csv" do - it "renders a page with the correct header" do - get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=false", headers:, params: {} - header = page.find_css("h1") - expect(header.text).to include("Download CSV") + it "provides the export type to the mail job" do + codes_only_export_type = false + expect { + post "/organisations/#{organisation.id}/sales-logs/email-csv?codes_only=#{codes_only_export_type}", headers:, params: {} + }.to enqueue_job(EmailCsvJob).with(user, nil, {}, false, organisation, codes_only_export_type, "sales") + codes_only_export_type = true + expect { + post "/organisations/#{organisation.id}/sales-logs/email-csv?codes_only=#{codes_only_export_type}", headers:, params: {} + }.to enqueue_job(EmailCsvJob).with(user, nil, {}, false, organisation, codes_only_export_type, "sales") + end + end end - it "renders a form with the correct target containing a button with the correct text" do - get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=false", headers:, params: {} - form = page.find("form.button_to") - expect(form[:method]).to eq("post") - expect(form[:action]).to eq("/organisations/#{organisation.id}/sales-logs/email-csv") - expect(form).to have_button("Send email") - end + describe "GET #download_lettings_csv" do + it "renders a page with the correct header" do + get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=false", headers:, params: {} + header = page.find_css("h1") + expect(header.text).to include("Download CSV") + end - it "when codes_only query parameter is false, form contains hidden field with correct value" do - codes_only = false - get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=#{codes_only}", headers:, params: {} - hidden_field = page.find("form.button_to").find_field("codes_only", type: "hidden") - expect(hidden_field.value).to eq(codes_only.to_s) - end + it "renders a form with the correct target containing a button with the correct text" do + get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=false", headers:, params: {} + form = page.find("form.button_to") + expect(form[:method]).to eq("post") + expect(form[:action]).to eq("/organisations/#{organisation.id}/lettings-logs/email-csv") + expect(form).to have_button("Send email") + end - it "when codes_only query parameter is true, form contains hidden field with correct value" do - codes_only = true - get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=#{codes_only}", headers:, params: {} - hidden_field = page.find("form.button_to").find_field("codes_only", type: "hidden") - expect(hidden_field.value).to eq(codes_only.to_s) - end + it "when codes_only query parameter is false, form contains hidden field with correct value" do + codes_only = false + get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=#{codes_only}", headers:, params: {} + hidden_field = page.find("form.button_to").find_field("codes_only", type: "hidden") + expect(hidden_field.value).to eq(codes_only.to_s) + end - it "when query string contains search parameter, form contains hidden field with correct value" do - search_term = "blam" - get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=true&search=#{search_term}", headers:, params: {} - hidden_field = page.find("form.button_to").find_field("search", type: "hidden") - expect(hidden_field.value).to eq(search_term) - end - end + it "when codes_only query parameter is true, form contains hidden field with correct value" do + codes_only = true + get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=#{codes_only}", headers:, params: {} + hidden_field = page.find("form.button_to").find_field("codes_only", type: "hidden") + expect(hidden_field.value).to eq(codes_only.to_s) + end - context "when they view the users tab" do - before do - get "/organisations/#{organisation.id}/users" + it "when query string contains search parameter, form contains hidden field with correct value" do + search_term = "blam" + get "/organisations/#{organisation.id}/lettings-logs/csv-download?codes_only=true&search=#{search_term}", headers:, params: {} + hidden_field = page.find("form.button_to").find_field("search", type: "hidden") + expect(hidden_field.value).to eq(search_term) + end end - it "has a CSV download button with the correct path" do - expect(page).to have_link("Download (CSV)", href: "/organisations/#{organisation.id}/users.csv") - end + describe "GET #download_sales_csv" do + it "renders a page with the correct header" do + get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=false", headers:, params: {} + header = page.find_css("h1") + expect(header.text).to include("Download CSV") + end - context "when you download the CSV" do - let(:headers) { { "Accept" => "text/csv" } } - let(:other_organisation) { create(:organisation) } + it "renders a form with the correct target containing a button with the correct text" do + get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=false", headers:, params: {} + form = page.find("form.button_to") + expect(form[:method]).to eq("post") + expect(form[:action]).to eq("/organisations/#{organisation.id}/sales-logs/email-csv") + expect(form).to have_button("Send email") + end + + it "when codes_only query parameter is false, form contains hidden field with correct value" do + codes_only = false + get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=#{codes_only}", headers:, params: {} + hidden_field = page.find("form.button_to").find_field("codes_only", type: "hidden") + expect(hidden_field.value).to eq(codes_only.to_s) + end + + it "when codes_only query parameter is true, form contains hidden field with correct value" do + codes_only = true + get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=#{codes_only}", headers:, params: {} + hidden_field = page.find("form.button_to").find_field("codes_only", type: "hidden") + expect(hidden_field.value).to eq(codes_only.to_s) + end + it "when query string contains search parameter, form contains hidden field with correct value" do + search_term = "blam" + get "/organisations/#{organisation.id}/sales-logs/csv-download?codes_only=true&search=#{search_term}", headers:, params: {} + hidden_field = page.find("form.button_to").find_field("search", type: "hidden") + expect(hidden_field.value).to eq(search_term) + end + end + + context "when they view the users tab" do before do - create_list(:user, 3, organisation:) - create_list(:user, 2, organisation: other_organisation) + get "/organisations/#{organisation.id}/users" + end + + it "has a CSV download button with the correct path" do + expect(page).to have_link("Download (CSV)", href: "/organisations/#{organisation.id}/users.csv") end - it "only includes users from that organisation" do - get "/organisations/#{other_organisation.id}/users", headers:, params: {} - csv = CSV.parse(response.body) - expect(csv.count).to eq(other_organisation.users.count + 1) + context "when you download the CSV" do + let(:headers) { { "Accept" => "text/csv" } } + let(:other_organisation) { create(:organisation) } + + before do + create_list(:user, 3, organisation:) + create_list(:user, 2, organisation: other_organisation) + end + + it "only includes users from that organisation" do + get "/organisations/#{other_organisation.id}/users", headers:, params: {} + csv = CSV.parse(response.body) + expect(csv.count).to eq(other_organisation.users.count + 1) + end end end end diff --git a/spec/requests/sales_logs_controller_spec.rb b/spec/requests/sales_logs_controller_spec.rb index 30d756356..ff9130e75 100644 --- a/spec/requests/sales_logs_controller_spec.rb +++ b/spec/requests/sales_logs_controller_spec.rb @@ -112,9 +112,11 @@ RSpec.describe SalesLogsController, type: :request do let(:user) { FactoryBot.create(:user) } let(:organisation) { user.organisation } let(:other_organisation) { FactoryBot.create(:organisation) } + let(:purchaser_code) { "coop123" } let!(:sales_log) do FactoryBot.create( :sales_log, + purchid: purchaser_code, owning_organisation: organisation, ) end @@ -175,6 +177,36 @@ RSpec.describe SalesLogsController, type: :request do end end + context "and the state of filters and search is such that display_delete_logs returns true" do + before do + allow_any_instance_of(LogListHelper).to receive(:display_delete_logs?).and_return(true) # rubocop:disable RSpec/AnyInstance + end + + it "displays the delete logs button with the correct path if there are logs visibile" do + get sales_logs_path(search: purchaser_code) + expect(page).to have_link "Delete logs", href: delete_logs_sales_logs_path(search: purchaser_code) + end + + it "does not display the delete logs button if there are no logs displayed" do + SalesLog.destroy_all + get sales_logs_path(search: "gibberish_e9o87tvbyc4875g") + expect(page).not_to have_selector "article.app-log-summary" + expect(page).not_to have_link "Delete logs" + end + end + + context "and the state of filters and search is such that display_delete_logs returns false" do + before do + allow_any_instance_of(LogListHelper).to receive(:display_delete_logs?).and_return(false) # rubocop:disable RSpec/AnyInstance + end + + it "does not display the delete logs button even if there are logs displayed" do + get sales_logs_path + expect(page).to have_selector "article.app-log-summary" + expect(page).not_to have_link "Delete logs" + end + end + context "when there is a pending log" do let!(:invisible_log) do FactoryBot.create( diff --git a/spec/views/form/page_view_spec.rb b/spec/views/form/page_view_spec.rb index 074a7fd6a..614f56562 100644 --- a/spec/views/form/page_view_spec.rb +++ b/spec/views/form/page_view_spec.rb @@ -22,8 +22,6 @@ RSpec.describe "form/page" do Singleton.__init__(FormHandler) example.run end - Timecop.return - Singleton.__init__(FormHandler) end before do @@ -45,7 +43,7 @@ RSpec.describe "form/page" do context "with a page containing a description" do let(:description) { "Test description with link." } let(:page_attributes) { { description: } } - let(:expected_html) { '

Test description with link.

' } + let(:expected_html) { "

#{description}

" } it "renders the description" do expect(rendered).to match(expected_html) diff --git a/spec/views/logs/delete_logs_spec.rb b/spec/views/logs/delete_logs_spec.rb new file mode 100644 index 000000000..6dcb8c423 --- /dev/null +++ b/spec/views/logs/delete_logs_spec.rb @@ -0,0 +1,121 @@ +require "rails_helper" + +RSpec.describe "logs/delete_logs.html.erb" do + let(:user) { create(:user, :support, name: "Dirk Gently") } + let(:lettings_log_1) { create(:lettings_log, tenancycode: "Holistic", propcode: "Detective Agency", created_by: user) } + let(:lettings_logs) { [lettings_log_1] } + let(:paths) do + { + delete_confirmation_path: delete_logs_confirmation_lettings_logs_path, + back_to_logs_path: lettings_logs_path(search: "search_term"), + delete_path: delete_logs_lettings_logs_path, + } + end + let(:delete_logs_form) { Forms::DeleteLogsForm.new(log_type: :lettings, current_user: user, **paths) } + + before do + sign_in user + allow(FilterManager).to receive(:filter_logs).and_return lettings_logs + assign(:delete_logs_form, delete_logs_form) + end + + it "has the correct h1 content" do + render + fragment = Capybara::Node::Simple.new(rendered) + h1 = fragment.find("h1") + expect(h1.text).to include "Review the logs you want to delete" + end + + context "when there is one log to delete" do + it "shows the informative text in the singular" do + render + fragment = Capybara::Node::Simple.new(rendered) + info_text = fragment.first("p").text + expect(info_text).to eq "You've selected 1 log to delete" + end + end + + context "when there is more than one log to delete" do + let(:lettings_log_2) { create(:lettings_log, tenancycode: "01-354", propcode: "9112") } + + before do + lettings_logs << lettings_log_2 + allow(FilterManager).to receive(:filter_logs).and_return lettings_logs + delete_logs_form = Forms::DeleteLogsForm.new(log_type: :lettings, current_user: user, **paths) + assign(:delete_logs_form, delete_logs_form) + end + + it "shows the informative text in the plural" do + render + fragment = Capybara::Node::Simple.new(rendered) + info_text = fragment.first("p").text + expect(info_text).to eq "You've selected #{lettings_logs.count} logs to delete" + end + end + + context "when the table contains lettings logs" do + it "shows the correct headers in the table" do + render + fragment = Capybara::Node::Simple.new(rendered) + headers = fragment.find_all("table thead tr th").map(&:text) + expect(headers).to eq ["Log ID", "Tenancy code", "Property reference", "Status", "Delete?"] + end + + it "shows the correct information in each row" do + render + fragment = Capybara::Node::Simple.new(rendered) + row_data = fragment.find_all("table tbody tr td").map(&:text)[0...-1].map(&:strip) + expect(row_data).to eq [lettings_log_1.id.to_s, lettings_log_1.tenancycode, lettings_log_1.propcode, lettings_log_1.status.humanize.capitalize] + end + + it "links to the relevant log on each row" do + render + fragment = Capybara::Node::Simple.new(rendered) + first_cell = fragment.first("table tbody tr td") + expect(first_cell).to have_link lettings_log_1.id.to_s, href: lettings_log_path(lettings_log_1) + end + end + + context "when the table contains sales logs" do + let(:sales_log) { create(:sales_log, purchid: "Interconnectedness", saledate: Time.zone.today, created_by: user) } + let(:sales_logs) { [sales_log] } + let(:delete_logs_form_sales) { Forms::DeleteLogsForm.new(log_type: :sales, current_user: user, **paths) } + + before do + sign_in user + allow(FilterManager).to receive(:filter_logs).and_return sales_logs + assign(:delete_logs_form, delete_logs_form_sales) + end + + it "shows the correct headers in the table" do + render + fragment = Capybara::Node::Simple.new(rendered) + headers = fragment.find_all("table thead tr th").map(&:text) + expect(headers).to eq ["Log ID", "Purchaser code", "Sale completion date", "Status", "Delete?"] + end + + it "shows the correct information in each row" do + render + fragment = Capybara::Node::Simple.new(rendered) + row_data = fragment.find_all("table tbody tr td").map(&:text)[0...-1].map(&:strip) + expect(row_data).to eq [sales_log.id.to_s, sales_log.purchid, sales_log.saledate.to_formatted_s(:govuk_date), sales_log.status.humanize.capitalize] + end + + it "links to the relevant log on each row" do + render + fragment = Capybara::Node::Simple.new(rendered) + first_cell = fragment.first("table tbody tr td") + expect(first_cell).to have_link sales_log.id.to_s, href: sales_log_path(sales_log) + end + end + + it "shows a checkbox with the correct hidden label in the final cell of each row" do + render + fragment = Capybara::Node::Simple.new(rendered) + final_cell = fragment.find_all("table tbody tr td")[-1] + expect(final_cell.find("input")[:type]).to eq "checkbox" + checkbox_label = final_cell.find("label span") + expect(checkbox_label.text).to eq lettings_log_1.id.to_s + expect(checkbox_label[:class]).to include "govuk-visually-hidden" + end +end From a4ebcc3f460025c954903c6c88d4779e2de1e678 Mon Sep 17 00:00:00 2001 From: Arthur Campbell <51094020+arfacamble@users.noreply.github.com> Date: Thu, 15 Jun 2023 15:02:19 +0100 Subject: [PATCH 03/19] CLDC-2449 production recursion error on finding url for next incomplete section (#1698) * protect against stack level errors in the case where a log has the status in progress but due to changes in the form should now have the status completed further update the lettings log factory as the :completed trait was producing logs that were not copmlete * add tests for calculate_status that implicitly also check if the factory traits for copmleted are working also some minor renaming and refactoring * correct linting errors * remove status update but protect against error resurfacing in the future --- app/models/form.rb | 2 +- app/models/log.rb | 8 ++++---- spec/factories/lettings_log.rb | 2 ++ spec/models/form_spec.rb | 16 ++++++++++++++-- spec/models/log_spec.rb | 22 ++++++++++++++++++++++ 5 files changed, 43 insertions(+), 7 deletions(-) diff --git a/app/models/form.rb b/app/models/form.rb index 16c896e4c..7a55b2e0d 100644 --- a/app/models/form.rb +++ b/app/models/form.rb @@ -124,7 +124,7 @@ class Form def next_incomplete_section_redirect_path(subsection, log) subsection_ids = subsections.map(&:id) - if log.status == "completed" + if log.status == "completed" || log.calculate_status == "completed" # if a log's status in in progress but then fields are made optional, all its subsections are complete, resulting in a stack error return first_question_in_last_subsection(subsection_ids) end diff --git a/app/models/log.rb b/app/models/log.rb index 452aa67c6..c02fdb02d 100644 --- a/app/models/log.rb +++ b/app/models/log.rb @@ -139,9 +139,9 @@ class Log < ApplicationRecord def calculate_status return "deleted" if discarded_at.present? - if all_fields_completed? && errors.empty? + if all_subsections_completed? && errors.empty? "completed" - elsif all_fields_nil? + elsif all_subsections_unstarted? "not_started" else "in_progress" @@ -210,11 +210,11 @@ private self.status = calculate_status end - def all_fields_completed? + def all_subsections_completed? form.subsections.all? { |subsection| subsection.complete?(self) || subsection.not_displayed_in_tasklist?(self) } end - def all_fields_nil? + def all_subsections_unstarted? not_started_statuses = %i[not_started cannot_start_yet] form.subsections.all? { |subsection| not_started_statuses.include? subsection.status(self) } end diff --git a/spec/factories/lettings_log.rb b/spec/factories/lettings_log.rb index ec119fbdc..8cb459117 100644 --- a/spec/factories/lettings_log.rb +++ b/spec/factories/lettings_log.rb @@ -146,6 +146,8 @@ FactoryBot.define do joint { 3 } address_line1 { "fake address" } town_or_city { "London" } + ppcodenk { 0 } + tshortfall_known { 1 } end trait :export do tenancycode { "987654" } diff --git a/spec/models/form_spec.rb b/spec/models/form_spec.rb index b63d05f62..439cd24b1 100644 --- a/spec/models/form_spec.rb +++ b/spec/models/form_spec.rb @@ -190,8 +190,6 @@ RSpec.describe Form, type: :model do FormHandler.instance.use_real_forms! example.run - - FormHandler.instance.use_fake_forms! end it "finds the path to the section after" do @@ -202,6 +200,20 @@ RSpec.describe Form, type: :model do expect(form.next_incomplete_section_redirect_path(subsection, lettings_log)).to eq("joint") end end + + context "when a log has status in progress but all subsections are complete" do + let(:lettings_log) { build(:lettings_log, :completed, status: "in_progress") } + let(:subsection) { form.get_subsection("setup") } + + before do + Timecop.return + FormHandler.instance.use_real_forms! + end + + it "does not raise a Stack Error" do + expect { form.next_incomplete_section_redirect_path(subsection, lettings_log) }.not_to raise_error + end + end end describe "#reset_not_routed_questions_and_invalid_answers" do diff --git a/spec/models/log_spec.rb b/spec/models/log_spec.rb index 0cba24086..fb686e744 100644 --- a/spec/models/log_spec.rb +++ b/spec/models/log_spec.rb @@ -5,4 +5,26 @@ RSpec.describe Log, type: :model do expect(SalesLog).to be < described_class expect(LettingsLog).to be < described_class end + + describe "#calculate_status" do + it "returns the correct status for a completed sales log" do + complete_sales_log = create(:sales_log, :completed, status: nil) + expect(complete_sales_log.calculate_status).to eq "completed" + end + + it "returns the correct status for an in progress sales log" do + in_progress_sales_log = create(:sales_log, :in_progress, status: nil) + expect(in_progress_sales_log.calculate_status).to eq "in_progress" + end + + it "returns the correct status for a completed lettings log" do + complete_lettings_log = create(:lettings_log, :completed, status: nil) + expect(complete_lettings_log.calculate_status).to eq "completed" + end + + it "returns the correct status for an in progress lettings log" do + in_progress_lettings_log = create(:lettings_log, :in_progress, status: nil) + expect(in_progress_lettings_log.calculate_status).to eq "in_progress" + end + end end From c53f4a770b5a43e40d0f6ffaa604ef087ec48c47 Mon Sep 17 00:00:00 2001 From: Arthur Campbell <51094020+arfacamble@users.noreply.github.com> Date: Thu, 15 Jun 2023 15:06:38 +0100 Subject: [PATCH 04/19] Remove feature toggle for schemes and locations (#1700) * remove scheme toggle * remove location toggle * minor linting complaint --- app/helpers/locations_helper.rb | 9 +- app/helpers/schemes_helper.rb | 5 +- app/services/feature_toggle.rb | 8 -- app/views/locations/index.html.erb | 148 ++++++--------------- app/views/locations/show.html.erb | 6 +- app/views/schemes/_scheme_list.html.erb | 22 +-- app/views/schemes/show.html.erb | 37 +++--- spec/features/schemes_spec.rb | 17 --- spec/helpers/schemes_helper_spec.rb | 8 -- spec/requests/locations_controller_spec.rb | 26 ---- 10 files changed, 69 insertions(+), 217 deletions(-) diff --git a/app/helpers/locations_helper.rb b/app/helpers/locations_helper.rb index 7e38ed36c..58fffd2e0 100644 --- a/app/helpers/locations_helper.rb +++ b/app/helpers/locations_helper.rb @@ -24,7 +24,7 @@ module LocationsHelper end def display_location_attributes(location) - base_attributes = [ + [ { name: "Postcode", value: location.postcode, attribute: "postcode" }, { name: "Location name", value: location.name, attribute: "name" }, { name: "Local authority", value: formatted_local_authority_timeline(location, "name"), attribute: "local_authority" }, @@ -33,13 +33,8 @@ module LocationsHelper { name: "Mobility standards", value: location.mobility_type, attribute: "mobility_standards" }, { name: "Location code", value: formatted_local_authority_timeline(location, "code"), attribute: "location_code" }, { name: "Availability", value: location_availability(location), attribute: "availability" }, + { name: "Status", value: location.status, attribute: "status" }, ] - - if FeatureToggle.location_toggle_enabled? - base_attributes.append({ name: "Status", value: location.status, attribute: "status" }) - end - - base_attributes end def display_location_attributes_for_check_answers(location) diff --git a/app/helpers/schemes_helper.rb b/app/helpers/schemes_helper.rb index 0503f88b3..50bd4efb2 100644 --- a/app/helpers/schemes_helper.rb +++ b/app/helpers/schemes_helper.rb @@ -14,12 +14,9 @@ module SchemesHelper { name: "Level of support given", value: scheme.support_type }, { name: "Intended length of stay", value: scheme.intended_stay }, { name: "Availability", value: scheme_availability(scheme) }, + { name: "Status", value: status_tag(scheme.status) }, ] - if FeatureToggle.scheme_toggle_enabled? - base_attributes.append({ name: "Status", value: status_tag(scheme.status) }) - end - if user.data_coordinator? base_attributes.delete_if { |item| item[:name] == "Housing stock owned by" } end diff --git a/app/services/feature_toggle.rb b/app/services/feature_toggle.rb index 88e1d17ef..2690d1e06 100644 --- a/app/services/feature_toggle.rb +++ b/app/services/feature_toggle.rb @@ -12,14 +12,6 @@ class FeatureToggle Rails.env.production? || Rails.env.test? || Rails.env.staging? || Rails.env.review? end - def self.scheme_toggle_enabled? - true - end - - def self.location_toggle_enabled? - true - end - def self.bulk_upload_duplicate_log_check_enabled? !Rails.env.staging? end diff --git a/app/views/locations/index.html.erb b/app/views/locations/index.html.erb index 85ae27fed..e974d7dfb 100644 --- a/app/views/locations/index.html.erb +++ b/app/views/locations/index.html.erb @@ -10,120 +10,60 @@ <%= render partial: "organisations/headings", locals: { main: @scheme.service_name, sub: nil } %> -<% if FeatureToggle.location_toggle_enabled? %> -
-
-<% end %> - <%= render SubNavigationComponent.new(items: scheme_items(request.path, @scheme.id, "Locations")) %> +
+
+ <%= render SubNavigationComponent.new(items: scheme_items(request.path, @scheme.id, "Locations")) %> -

Locations

+

Locations

- <%= render SearchComponent.new(current_user:, search_label: "Search by location name or postcode", value: @searched) %> + <%= render SearchComponent.new(current_user:, search_label: "Search by location name or postcode", value: @searched) %> - <%= govuk_section_break(visible: true, size: "m") %> -<% if FeatureToggle.location_toggle_enabled? %> -
+ <%= govuk_section_break(visible: true, size: "m") %>
-<% end %> - -<% if FeatureToggle.location_toggle_enabled? %> -
-
- <%= govuk_table do |table| %> - <%= table.caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %> - <%= render(SearchResultCaptionComponent.new(searched: @searched, count: @pagy.count, item_label:, total_count: @total_count, item: "locations", path: request.path)) %> - <% end %> - <%= table.head do |head| %> - <%= head.row do |row| %> - <% row.cell(header: true, text: "Postcode", html_attributes: { - scope: "col", - }) %> - <% row.cell(header: true, text: "Name", html_attributes: { - scope: "col", - }) %> - <% row.cell(header: true, text: "Location code", html_attributes: { - scope: "col", - }) %> - <% row.cell(header: true, text: "Status", html_attributes: { - scope: "col", - }) %> - <% end %> - <% end %> - <% @locations.each do |location| %> - <%= table.body do |body| %> - <%= body.row do |row| %> - <% row.cell(text: simple_format(location_cell_postcode(location, if location.confirmed - scheme_location_path(@scheme, location) - else - location.postcode.present? ? scheme_location_check_answers_path(@scheme, location, route: "locations") : scheme_location_postcode_path(@scheme, location) - end), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %> - <% row.cell(text: location.name) %> - <% row.cell(text: location.id) %> - <% row.cell(text: status_tag(location.status)) %> - <% end %> - <% end %> - <% end %> - <% end %> +
- <% if LocationPolicy.new(current_user, @scheme.locations.new).create? %> - <%= govuk_button_to "Add a location", scheme_locations_path(@scheme), method: "post", secondary: true %> +
+
+ <%= govuk_table do |table| %> + <%= table.caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %> + <%= render(SearchResultCaptionComponent.new(searched: @searched, count: @pagy.count, item_label:, total_count: @total_count, item: "locations", path: request.path)) %> <% end %> -
-
-<% else %> - <%= govuk_table do |table| %> - <%= table.caption(classes: %w[govuk-!-font-size-19 govuk-!-font-weight-regular]) do |caption| %> - <%= render(SearchResultCaptionComponent.new(searched: @searched, count: @pagy.count, item_label:, total_count: @total_count, item: "locations", path: request.path)) %> - <% end %> - <%= table.head do |head| %> - <%= head.row do |row| %> - <% row.cell(header: true, text: "Code", html_attributes: { - scope: "col", - }) %> - <% row.cell(header: true, text: "Postcode", html_attributes: { - scope: "col", - }) %> - <% row.cell(header: true, text: "Units", html_attributes: { - scope: "col", - }) %> - <% row.cell(header: true, text: "Common unit type", html_attributes: { - scope: "col", - }) %> - <% row.cell(header: true, text: "Mobility type", html_attributes: { - scope: "col", - }) %> - <% row.cell(header: true, text: "Local authority", html_attributes: { - scope: "col", - }) %> - <% row.cell(header: true, text: "Available from", html_attributes: { - scope: "col", - }) %> + <%= table.head do |head| %> + <%= head.row do |row| %> + <% row.cell(header: true, text: "Postcode", html_attributes: { + scope: "col", + }) %> + <% row.cell(header: true, text: "Name", html_attributes: { + scope: "col", + }) %> + <% row.cell(header: true, text: "Location code", html_attributes: { + scope: "col", + }) %> + <% row.cell(header: true, text: "Status", html_attributes: { + scope: "col", + }) %> + <% end %> <% end %> - <% end %> - <% @locations.each do |location| %> - <%= table.body do |body| %> - <%= body.row do |row| %> - <% row.cell(text: location.id) %> - <% row.cell(text: simple_format(location_cell_postcode(location, if location.confirmed - scheme_location_path(@scheme, location) - else - location.postcode.present? ? scheme_location_check_answers_path(@scheme, location, route: "locations") : scheme_location_postcode_path(@scheme, location) - end), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %> - <% row.cell(text: location.units) %> - <% row.cell do %> - <%= simple_format(location.type_of_unit) %> - <% end %> - <% row.cell(text: location.mobility_type) %> - <% row.cell(text: location.location_admin_district) %> - <% row.cell(text: location.startdate&.to_formatted_s(:govuk_date)) %> + <% @locations.each do |location| %> + <%= table.body do |body| %> + <%= body.row do |row| %> + <% row.cell(text: simple_format(location_cell_postcode(location, if location.confirmed + scheme_location_path(@scheme, location) + else + location.postcode.present? ? scheme_location_check_answers_path(@scheme, location, route: "locations") : scheme_location_postcode_path(@scheme, location) + end), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %> + <% row.cell(text: location.name) %> + <% row.cell(text: location.id) %> + <% row.cell(text: status_tag(location.status)) %> <% end %> + <% end %> <% end %> <% end %> - <% end %> - <% if user_can_edit_scheme?(current_user, @scheme) %> - <%= govuk_button_to "Add a location", scheme_locations_path(@scheme), method: "post", secondary: true %> - <% end %> -<% end %> + <% if LocationPolicy.new(current_user, @scheme.locations.new).create? %> + <%= govuk_button_to "Add a location", scheme_locations_path(@scheme), method: "post", secondary: true %> + <% end %> +
+
<%== render partial: "pagy/nav", locals: { pagy: @pagy, item_name: "locations" } %> diff --git a/app/views/locations/show.html.erb b/app/views/locations/show.html.erb index d949a8c63..dd819b2fb 100644 --- a/app/views/locations/show.html.erb +++ b/app/views/locations/show.html.erb @@ -25,8 +25,6 @@
-<% if FeatureToggle.location_toggle_enabled? %> - <% if LocationPolicy.new(current_user, @location).deactivate? %> - <%= toggle_location_link(@location) %> - <% end %> +<% if LocationPolicy.new(current_user, @location).deactivate? %> + <%= toggle_location_link(@location) %> <% end %> diff --git a/app/views/schemes/_scheme_list.html.erb b/app/views/schemes/_scheme_list.html.erb index 5fa712d1e..4f3b06454 100644 --- a/app/views/schemes/_scheme_list.html.erb +++ b/app/views/schemes/_scheme_list.html.erb @@ -5,20 +5,10 @@ <% end %> <%= table.head do |head| %> <%= head.row do |row| %> - <% row.cell(header: true, text: "Scheme", html_attributes: { - scope: "col", - }) %> - <% row.cell(header: true, text: "Code", html_attributes: { - scope: "col", - }) %> - <% row.cell(header: true, text: "Locations", html_attributes: { - scope: "col", - }) %> - <% if FeatureToggle.scheme_toggle_enabled? %> - <% row.cell(header: true, text: "Status", html_attributes: { - scope: "col", - }) %> - <% end %> + <% row.cell(header: true, text: "Scheme", html_attributes: { scope: "col" }) %> + <% row.cell(header: true, text: "Code", html_attributes: { scope: "col" }) %> + <% row.cell(header: true, text: "Locations", html_attributes: { scope: "col" }) %> + <% row.cell(header: true, text: "Status", html_attributes: { scope: "col" }) %> <% end %> <% end %> <% @schemes.each do |scheme| %> @@ -27,9 +17,7 @@ <% row.cell(text: simple_format(scheme_cell(scheme), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %> <% row.cell(text: scheme.id_to_display) %> <% row.cell(text: scheme.locations&.count) %> - <% if FeatureToggle.scheme_toggle_enabled? %> - <% row.cell(text: status_tag(scheme.status)) %> - <% end %> + <% row.cell(text: status_tag(scheme.status)) %> <% end %> <% end %> <% end %> diff --git a/app/views/schemes/show.html.erb b/app/views/schemes/show.html.erb index 0005582e8..4fe3a65de 100644 --- a/app/views/schemes/show.html.erb +++ b/app/views/schemes/show.html.erb @@ -9,33 +9,26 @@ <%= render partial: "organisations/headings", locals: { main: @scheme.service_name, sub: nil } %> -<% if FeatureToggle.location_toggle_enabled? %> -
-
-<% end %> - <%= render SubNavigationComponent.new(items: scheme_items(request.path, @scheme.id, "Locations")) %> +
+
+ <%= render SubNavigationComponent.new(items: scheme_items(request.path, @scheme.id, "Locations")) %> -

Scheme

+

Scheme

- <%= govuk_summary_list do |summary_list| %> - <% display_scheme_attributes(@scheme, current_user).each do |attr| %> - <%= summary_list.row do |row| %> - <% row.key { attr[:name] } %> - <% row.value { details_html(attr) } %> - <% if SchemePolicy.new(current_user, @scheme).update? %> - <% row.action(text: "Change", href: scheme_edit_name_path(scheme_id: @scheme.id)) if attr[:edit] %> - <% end %> + <%= govuk_summary_list do |summary_list| %> + <% display_scheme_attributes(@scheme, current_user).each do |attr| %> + <%= summary_list.row do |row| %> + <% row.key { attr[:name] } %> + <% row.value { details_html(attr) } %> + <% if SchemePolicy.new(current_user, @scheme).update? %> + <% row.action(text: "Change", href: scheme_edit_name_path(scheme_id: @scheme.id)) if attr[:edit] %> <% end %> <% end %> <% end %> - -<% if FeatureToggle.location_toggle_enabled? %> -
+ <% end %>
-<% end %> +
-<% if FeatureToggle.scheme_toggle_enabled? %> - <% if SchemePolicy.new(current_user, @scheme).deactivate? %> - <%= toggle_scheme_link(@scheme) %> - <% end %> +<% if SchemePolicy.new(current_user, @scheme).deactivate? %> + <%= toggle_scheme_link(@scheme) %> <% end %> diff --git a/spec/features/schemes_spec.rb b/spec/features/schemes_spec.rb index 1e45634c6..777a6c88d 100644 --- a/spec/features/schemes_spec.rb +++ b/spec/features/schemes_spec.rb @@ -211,23 +211,6 @@ RSpec.describe "Schemes scheme Features" do end end - context "when I click locations link and the new locations layout feature toggle is disabled" do - before do - allow(FeatureToggle).to receive(:location_toggle_enabled?).and_return(false) - click_link("Locations") - end - - it "shows details of those locations" do - locations.each do |location| - expect(page).to have_content(location.id) - expect(page).to have_content(location.postcode) - expect(page).to have_content(location.units) - expect(page).to have_content(location.type_of_unit) - expect(page).to have_content(location.startdate&.to_formatted_s(:govuk_date)) - end - end - end - context "when I search for a specific location" do before do click_link("Locations") diff --git a/spec/helpers/schemes_helper_spec.rb b/spec/helpers/schemes_helper_spec.rb index 09a9c9e2e..905494ac8 100644 --- a/spec/helpers/schemes_helper_spec.rb +++ b/spec/helpers/schemes_helper_spec.rb @@ -195,14 +195,6 @@ RSpec.describe SchemesHelper do expect(display_scheme_attributes(scheme, coordinator_user)).to eq(attributes) end - context "when the scheme toggle is disabled" do - it "doesn't show the scheme status" do - allow(FeatureToggle).to receive(:scheme_toggle_enabled?).and_return(false) - attributes = display_scheme_attributes(scheme, support_user).find { |x| x[:name] == "Status" } - expect(attributes).to be_nil - end - end - context "when the managing organisation is the owning organisation" do it "doesn't show the organisation providing support" do attributes = display_scheme_attributes(scheme_where_managing_organisation_is_owning_organisation, support_user).find { |x| x[:name] == "Organisation providing support" } diff --git a/spec/requests/locations_controller_spec.rb b/spec/requests/locations_controller_spec.rb index 04eb71f1a..88d73901c 100644 --- a/spec/requests/locations_controller_spec.rb +++ b/spec/requests/locations_controller_spec.rb @@ -153,19 +153,6 @@ RSpec.describe LocationsController, type: :request do end end - it "shows locations with correct data when the new locations layout feature toggle is disabled" do - allow(FeatureToggle).to receive(:location_toggle_enabled?).and_return(false) - get "/schemes/#{scheme.id}/locations" - locations.each do |location| - expect(page).to have_content(location.id) - expect(page).to have_content(location.postcode) - expect(page).to have_content(location.type_of_unit) - expect(page).to have_content(location.mobility_type) - expect(page).to have_content(location.location_admin_district) - expect(page).to have_content(location.startdate&.to_formatted_s(:govuk_date)) - end - end - it "has page heading" do expect(page).to have_content(scheme.service_name) end @@ -294,19 +281,6 @@ RSpec.describe LocationsController, type: :request do expect(page).to have_button("Add a location") end - it "shows locations with correct data when the new locations layout feature toggle is disabled" do - allow(FeatureToggle).to receive(:location_toggle_enabled?).and_return(false) - get "/schemes/#{scheme.id}/locations" - locations.each do |location| - expect(page).to have_content(location.id) - expect(page).to have_content(location.postcode) - expect(page).to have_content(location.type_of_unit) - expect(page).to have_content(location.mobility_type) - expect(page).to have_content(location.location_admin_district) - expect(page).to have_content(location.startdate&.to_formatted_s(:govuk_date)) - end - end - it "has page heading" do expect(page).to have_content(scheme.service_name) end From 72795874910202c995a13b4b331fe6ec85065ebf Mon Sep 17 00:00:00 2001 From: Jack <113976590+bibblobcode@users.noreply.github.com> Date: Thu, 15 Jun 2023 15:34:40 +0100 Subject: [PATCH 05/19] CLDC-2321 Add Data Protection Confirmation validation on log change (#1694) * Add Data Protection Confirmation on log change * Validate managing and owning org DPC presence * Add missing translation --- app/models/log.rb | 9 --- app/models/validations/setup_validations.rb | 8 +++ app/models/validations/shared_validations.rb | 8 +++ config/locales/en.yml | 2 + .../validations/setup_validations_spec.rb | 63 +++++++++++++++++ .../validations/shared_validations_spec.rb | 69 ++++++++++++++++++- spec/shared/shared_log_examples.rb | 49 ------------- 7 files changed, 148 insertions(+), 60 deletions(-) diff --git a/app/models/log.rb b/app/models/log.rb index c02fdb02d..3291b0ed8 100644 --- a/app/models/log.rb +++ b/app/models/log.rb @@ -9,7 +9,6 @@ class Log < ApplicationRecord belongs_to :bulk_upload, optional: true before_save :update_status! - before_validation :verify_data_protection_confirmation, on: :create STATUS = { "not_started" => 0, @@ -178,14 +177,6 @@ class Log < ApplicationRecord private - def verify_data_protection_confirmation - return unless FeatureToggle.new_data_protection_confirmation? - return unless owning_organisation - return if owning_organisation.data_protection_confirmed? - - errors.add :owning_organisation, I18n.t("validations.organisation.data_sharing_agreement_not_signed") - end - # Handle logs that are older than previous collection start date def older_than_previous_collection_year? return false unless startdate diff --git a/app/models/validations/setup_validations.rb b/app/models/validations/setup_validations.rb index d42ca2ec7..fd71dd4bc 100644 --- a/app/models/validations/setup_validations.rb +++ b/app/models/validations/setup_validations.rb @@ -42,6 +42,14 @@ module Validations::SetupValidations end end + def validate_managing_organisation_data_sharing_agremeent_signed(record) + return unless FeatureToggle.new_data_protection_confirmation? + + if record.managing_organisation_id_changed? && record.managing_organisation.present? && !record.managing_organisation.data_protection_confirmed? + record.errors.add :managing_organisation_id, I18n.t("validations.setup.managing_organisation.data_sharing_agreement_not_signed") + end + end + private def active_collection_start_date diff --git a/app/models/validations/shared_validations.rb b/app/models/validations/shared_validations.rb index 6a32563a7..3b361e9d6 100644 --- a/app/models/validations/shared_validations.rb +++ b/app/models/validations/shared_validations.rb @@ -117,6 +117,14 @@ module Validations::SharedValidations end end + def validate_owning_organisation_data_sharing_agremeent_signed(record) + return unless FeatureToggle.new_data_protection_confirmation? + + if record.owning_organisation_id_changed? && record.owning_organisation.present? && !record.owning_organisation.data_protection_confirmed? + record.errors.add :owning_organisation_id, I18n.t("validations.setup.owning_organisation.data_sharing_agreement_not_signed") + end + end + private def person_is_partner?(relationship) diff --git a/config/locales/en.yml b/config/locales/en.yml index e1635e748..59fa8ef32 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -254,8 +254,10 @@ en: activating_soon: "%{name} is not available until %{date}. Enter a tenancy start date after %{date}" owning_organisation: 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." managing_organisation: 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." created_by: invalid: "Please select the owning organisation or managing organisation that you belong to" lettype: diff --git a/spec/models/validations/setup_validations_spec.rb b/spec/models/validations/setup_validations_spec.rb index 5bb180aa2..51c157215 100644 --- a/spec/models/validations/setup_validations_spec.rb +++ b/spec/models/validations/setup_validations_spec.rb @@ -400,4 +400,67 @@ RSpec.describe Validations::SetupValidations do end end end + + describe "#validate_managing_organisation_data_sharing_agremeent_signed" do + before do + allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(false) + end + + it "is valid if the DSA is signed" do + log = build(:lettings_log, :in_progress, owning_organisation: create(:organisation)) + + expect(log).to be_valid + end + + it "is valid when owning_organisation nil" do + log = build(:lettings_log, owning_organisation: nil) + + expect(log).to be_valid + end + + it "is not valid if the DSA is not signed" do + log = build(:lettings_log, owning_organisation: create(:organisation, :without_dpc)) + + expect(log).to be_valid + end + end + + context "when flag enabled" do + before do + allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(true) + end + + it "is valid if the Data Protection Confirmation is signed" do + log = build(:lettings_log, :in_progress, managing_organisation: create(:organisation)) + + expect(log).to be_valid + end + + it "is valid when managing_organisation nil" do + log = build(:lettings_log, managing_organisation: nil) + + expect(log).to be_valid + end + + it "is not valid if the Data Protection Confirmation is not signed" do + log = build(:lettings_log, managing_organisation: create(:organisation, :without_dpc)) + + expect(log).not_to be_valid + expect(log.errors[:managing_organisation_id]).to eq(["The organisation must accept the Data Sharing Agreement before it can be selected as the managing organisation."]) + end + + context "when updating" do + let(:log) { create(:lettings_log, :in_progress) } + let(:org_with_dpc) { create(:organisation) } + let(:org_without_dpc) { create(:organisation, :without_dpc) } + + it "is valid when changing to another org with a signed Data Protection Confirmation" do + expect { log.managing_organisation = org_with_dpc }.to not_change(log, :valid?) + end + + it "invalid when changing to another org without a signed Data Protection Confirmation" do + expect { log.managing_organisation = org_without_dpc }.to change(log, :valid?).from(true).to(false).and(change { log.errors[:managing_organisation_id] }.to(["The organisation must accept the Data Sharing Agreement before it can be selected as the managing organisation."])) + end + end + end end diff --git a/spec/models/validations/shared_validations_spec.rb b/spec/models/validations/shared_validations_spec.rb index fe3612c3c..8751f443a 100644 --- a/spec/models/validations/shared_validations_spec.rb +++ b/spec/models/validations/shared_validations_spec.rb @@ -4,8 +4,8 @@ RSpec.describe Validations::SharedValidations do subject(:shared_validator) { validator_class.new } let(:validator_class) { Class.new { include Validations::SharedValidations } } - let(:lettings_log) { FactoryBot.create(:lettings_log) } - let(:sales_log) { FactoryBot.create(:sales_log, :completed) } + let(:lettings_log) { create(:lettings_log) } + let(:sales_log) { create(:sales_log, :completed) } let(:fake_2021_2022_form) { Form.new("spec/fixtures/forms/2021_2022.json") } describe "numeric min max validations" do @@ -174,5 +174,70 @@ RSpec.describe Validations::SharedValidations do expect(sales_log.errors).to be_empty end end + + %i[sales_log lettings_log].each do |log_type| + describe "validate_owning_organisation_data_sharing_agremeent_signed" do + before do + allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(false) + end + + it "is valid if the DSA is signed" do + log = build(log_type, :in_progress, owning_organisation: create(:organisation)) + + expect(log).to be_valid + end + + it "is valid when owning_organisation nil" do + log = build(log_type, owning_organisation: nil) + + expect(log).to be_valid + end + + it "is not valid if the DSA is not signed" do + log = build(log_type, owning_organisation: create(:organisation, :without_dpc)) + + expect(log).to be_valid + end + end + + context "when flag enabled" do + before do + allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(true) + end + + it "is valid if the Data Protection Confirmation is signed" do + log = build(log_type, :in_progress, owning_organisation: create(:organisation)) + + expect(log).to be_valid + end + + it "is valid when owning_organisation nil" do + log = build(log_type, owning_organisation: nil) + + expect(log).to be_valid + end + + it "is not valid if the Data Protection Confirmation is not signed" do + log = build(log_type, owning_organisation: create(:organisation, :without_dpc)) + + expect(log).not_to be_valid + expect(log.errors[:owning_organisation_id]).to eq(["The organisation must accept the Data Sharing Agreement before it can be selected as the owning organisation."]) + end + + context "when updating" do + let(:log) { create(log_type, :in_progress) } + let(:org_with_dpc) { create(:organisation) } + let(:org_without_dpc) { create(:organisation, :without_dpc) } + + it "is valid when changing to another org with a signed Data Protection Confirmation" do + expect { log.owning_organisation = org_with_dpc }.not_to change(log, :valid?) + end + + it "invalid when changing to another org without a signed Data Protection Confirmation" do + expect { log.owning_organisation = org_without_dpc }.to change(log, :valid?).from(true).to(false).and(change { log.errors[:owning_organisation_id] }.to(["The organisation must accept the Data Sharing Agreement before it can be selected as the owning organisation."])) + end + end + end + end end end diff --git a/spec/shared/shared_log_examples.rb b/spec/shared/shared_log_examples.rb index 1a2629ba2..f9cc59633 100644 --- a/spec/shared/shared_log_examples.rb +++ b/spec/shared/shared_log_examples.rb @@ -104,54 +104,5 @@ RSpec.shared_examples "shared log examples" do |log_type| end end end - - describe "#verify_data_protection_confirmation" do - before do - allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(false) - end - - it "is valid if the DSA is signed" do - log = build(log_type, :in_progress, owning_organisation: create(:organisation)) - - expect(log).to be_valid - end - - it "is valid when owning_organisation nil" do - log = build(log_type, owning_organisation: nil) - - expect(log).to be_valid - end - - it "is not valid if the DSA is not signed" do - log = build(log_type, owning_organisation: create(:organisation, :without_dpc)) - - expect(log).to be_valid - end - end - - context "when flag enabled" do - before do - allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(true) - end - - it "is valid if the DSA is signed" do - log = build(log_type, :in_progress, owning_organisation: create(:organisation)) - - expect(log).to be_valid - end - - it "is valid when owning_organisation nil" do - log = build(log_type, owning_organisation: nil) - - expect(log).to be_valid - end - - it "is not valid if the DSA is not signed" do - log = build(log_type, owning_organisation: create(:organisation, :without_dpc)) - - expect(log).not_to be_valid - expect(log.errors[:owning_organisation]).to eq(["Your organisation must accept the Data Sharing Agreement before you can create any logs."]) - end - end end # rubocop:enable RSpec/AnyInstance From ede9beb38563c021318afe9b268a40726a69cee7 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Fri, 16 Jun 2023 12:56:02 +0100 Subject: [PATCH 06/19] Only set @interruption_page_id if http referrer is provided (#1697) --- app/controllers/form_controller.rb | 2 +- spec/requests/form_controller_spec.rb | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/app/controllers/form_controller.rb b/app/controllers/form_controller.rb index 5bd151181..07ec12c5d 100644 --- a/app/controllers/form_controller.rb +++ b/app/controllers/form_controller.rb @@ -46,7 +46,7 @@ class FormController < ApplicationController end def show_page - if request.params["referrer"] == "interruption_screen" + if request.params["referrer"] == "interruption_screen" && request.headers["HTTP_REFERER"].present? @interruption_page_id = URI.parse(request.headers["HTTP_REFERER"]).path.split("/").last.underscore @interruption_page_referrer_type = referrer_from_query end diff --git a/spec/requests/form_controller_spec.rb b/spec/requests/form_controller_spec.rb index 5d9042c7c..a2c06c4f1 100644 --- a/spec/requests/form_controller_spec.rb +++ b/spec/requests/form_controller_spec.rb @@ -582,6 +582,31 @@ RSpec.describe FormController, type: :request do end end end + + context "when requesting a soft validation page without a http referrer header" do + before do + get "/lettings-logs/#{lettings_log.id}/#{page_path}?referrer=interruption_screen", headers: + end + + context "when the page is routed to" do + let(:page_path) { page_id.dasherize } + + it "directs to the question page" do + expect(response.body).to include("What is the tenant’s age?") + expect(response.body).to include("Skip for now") + end + end + + context "when the page is not routed to" do + let(:page_path) { "person-2-working-situation" } + + it "redirects to the log page" do + follow_redirect! + expect(response.body).to include("Before you start") + expect(response.body).not_to include("Skip for now") + end + end + end end context "with checkbox questions" do From f3ab2c496dcf84c2da571bac82ed609ca366a654 Mon Sep 17 00:00:00 2001 From: Jack <113976590+bibblobcode@users.noreply.github.com> Date: Fri, 16 Jun 2023 14:53:29 +0100 Subject: [PATCH 07/19] Do not send deactivation emails when user not present (#1705) --- app/controllers/locations_controller.rb | 17 +++++++++----- app/controllers/schemes_controller.rb | 15 ++++++++---- spec/requests/locations_controller_spec.rb | 27 ++++++++++++++++++---- spec/requests/schemes_controller_spec.rb | 12 +++++++++- 4 files changed, 55 insertions(+), 16 deletions(-) diff --git a/app/controllers/locations_controller.rb b/app/controllers/locations_controller.rb index 453c0934f..7ba4a1115 100644 --- a/app/controllers/locations_controller.rb +++ b/app/controllers/locations_controller.rb @@ -180,12 +180,17 @@ class LocationsController < ApplicationController logs = reset_location_and_scheme_for_logs! flash[:notice] = deactivate_success_notice - logs.group_by(&:created_by).transform_values(&:count).compact.each do |user, count| - LocationOrSchemeDeactivationMailer.send_deactivation_mail(user, - count, - url_for(controller: "lettings_logs", action: "update_logs"), - @location.scheme.service_name, - @location.postcode).deliver_later + + logs.group_by(&:created_by).transform_values(&:count).each do |user, count| + next unless user + + LocationOrSchemeDeactivationMailer.send_deactivation_mail( + user, + count, + url_for(controller: "lettings_logs", action: "update_logs"), + @location.scheme.service_name, + @location.postcode, + ).deliver_later end end redirect_to scheme_location_path(@scheme, @location) diff --git a/app/controllers/schemes_controller.rb b/app/controllers/schemes_controller.rb index 784ad5f62..3a2be3909 100644 --- a/app/controllers/schemes_controller.rb +++ b/app/controllers/schemes_controller.rb @@ -58,11 +58,16 @@ class SchemesController < ApplicationController logs = reset_location_and_scheme_for_logs! flash[:notice] = deactivate_success_notice - logs.group_by(&:created_by).transform_values(&:count).compact.each do |user, count| - LocationOrSchemeDeactivationMailer.send_deactivation_mail(user, - count, - url_for(controller: "lettings_logs", action: "update_logs"), - @scheme.service_name).deliver_later + + logs.group_by(&:created_by).transform_values(&:count).each do |user, count| + next unless user + + LocationOrSchemeDeactivationMailer.send_deactivation_mail( + user, + count, + url_for(controller: "lettings_logs", action: "update_logs"), + @scheme.service_name, + ).deliver_later end end redirect_to scheme_details_path(@scheme) diff --git a/spec/requests/locations_controller_spec.rb b/spec/requests/locations_controller_spec.rb index 88d73901c..81b2e56b0 100644 --- a/spec/requests/locations_controller_spec.rb +++ b/spec/requests/locations_controller_spec.rb @@ -1467,13 +1467,14 @@ RSpec.describe LocationsController, type: :request do context "when confirming deactivation" do let(:params) { { deactivation_date:, confirm: true, deactivation_date_type: "other" } } - let(:mailer) { instance_double(LocationOrSchemeDeactivationMailer) } - let(:user_a) { create(:user, email: "user_a@example.com") } - let(:user_b) { create(:user, email: "user_b@example.com") } + let(:user_a) { create(:user) } + let(:user_b) { create(:user) } before do - create_list(:lettings_log, 1, :sh, location:, scheme:, startdate:, created_by: user_a) + allow(LocationOrSchemeDeactivationMailer).to receive(:send_deactivation_mail).and_call_original + + create(:lettings_log, :sh, location:, scheme:, startdate:, created_by: user_a) create_list(:lettings_log, 3, :sh, location:, scheme:, startdate:, created_by: user_b) Timecop.freeze(Time.utc(2022, 10, 10)) @@ -1511,6 +1512,24 @@ RSpec.describe LocationsController, type: :request do lettings_log.reload expect(lettings_log.unresolved).to eq(true) end + + it "sends deactivation emails" do + expect(LocationOrSchemeDeactivationMailer).to have_received(:send_deactivation_mail).with( + user_a, + 1, + update_logs_lettings_logs_url, + location.scheme.service_name, + location.postcode, + ) + + expect(LocationOrSchemeDeactivationMailer).to have_received(:send_deactivation_mail).with( + user_b, + 3, + update_logs_lettings_logs_url, + location.scheme.service_name, + location.postcode, + ) + end end context "and the users need to be notified" do diff --git a/spec/requests/schemes_controller_spec.rb b/spec/requests/schemes_controller_spec.rb index 4c7bad55a..18ee51756 100644 --- a/spec/requests/schemes_controller_spec.rb +++ b/spec/requests/schemes_controller_spec.rb @@ -1930,7 +1930,6 @@ RSpec.describe SchemesController, type: :request do context "when confirming deactivation" do let(:params) { { deactivation_date:, confirm: true, deactivation_date_type: "other" } } - let(:mailer) { instance_double(LocationOrSchemeDeactivationMailer) } before do Timecop.freeze(Time.utc(2022, 10, 10)) @@ -1943,6 +1942,8 @@ RSpec.describe SchemesController, type: :request do context "and a log startdate is after scheme deactivation date" do before do + allow(LocationOrSchemeDeactivationMailer).to receive(:send_deactivation_mail).and_call_original + patch "/schemes/#{scheme.id}/deactivate", params: end @@ -1969,6 +1970,15 @@ RSpec.describe SchemesController, type: :request do lettings_log.reload expect(lettings_log.unresolved).to eq(true) end + + it "sends deactivation emails" do + expect(LocationOrSchemeDeactivationMailer).to have_received(:send_deactivation_mail).with( + user, + 1, + update_logs_lettings_logs_url, + scheme.service_name, + ) + end end context "and a log startdate is before scheme deactivation date" do From 11d14a4d9eed3f5fbecbb830c91b9c8b7bb1be94 Mon Sep 17 00:00:00 2001 From: Rachael Booth Date: Fri, 16 Jun 2023 15:08:44 +0100 Subject: [PATCH 08/19] CLDC-2341: Trigger retirement validations after all relevant questions on lettings forms (#1690) --- ...lead_tenant_over_retirement_value_check.rb | 1 - ...ead_tenant_under_retirement_value_check.rb | 1 - .../person_over_retirement_value_check.rb | 1 - .../person_under_retirement_value_check.rb | 1 - .../subsections/household_characteristics.rb | 56 +++++++++++++------ ...person_over_retirement_value_check_spec.rb | 8 --- ...erson_under_retirement_value_check_spec.rb | 8 --- .../household_characteristics_spec.rb | 56 +++++++++++++------ 8 files changed, 80 insertions(+), 52 deletions(-) diff --git a/app/models/form/lettings/pages/lead_tenant_over_retirement_value_check.rb b/app/models/form/lettings/pages/lead_tenant_over_retirement_value_check.rb index ca6183f3c..9d21e21a8 100644 --- a/app/models/form/lettings/pages/lead_tenant_over_retirement_value_check.rb +++ b/app/models/form/lettings/pages/lead_tenant_over_retirement_value_check.rb @@ -1,7 +1,6 @@ class Form::Lettings::Pages::LeadTenantOverRetirementValueCheck < ::Form::Page def initialize(id, hsh, subsection) super - @id = "lead_tenant_over_retirement_value_check" @depends_on = [{ "person_1_not_retired_over_soft_max_age?" => true }] @title_text = { "translation" => "soft_validations.retirement.max.title", diff --git a/app/models/form/lettings/pages/lead_tenant_under_retirement_value_check.rb b/app/models/form/lettings/pages/lead_tenant_under_retirement_value_check.rb index b7ab02900..0c7a3ed2e 100644 --- a/app/models/form/lettings/pages/lead_tenant_under_retirement_value_check.rb +++ b/app/models/form/lettings/pages/lead_tenant_under_retirement_value_check.rb @@ -1,7 +1,6 @@ class Form::Lettings::Pages::LeadTenantUnderRetirementValueCheck < ::Form::Page def initialize(id, hsh, subsection) super - @id = "lead_tenant_under_retirement_value_check" @depends_on = [{ "person_1_retired_under_soft_min_age?" => true }] @title_text = { "translation" => "soft_validations.retirement.min.title", diff --git a/app/models/form/lettings/pages/person_over_retirement_value_check.rb b/app/models/form/lettings/pages/person_over_retirement_value_check.rb index b13a52ed5..550ae0373 100644 --- a/app/models/form/lettings/pages/person_over_retirement_value_check.rb +++ b/app/models/form/lettings/pages/person_over_retirement_value_check.rb @@ -1,7 +1,6 @@ class Form::Lettings::Pages::PersonOverRetirementValueCheck < ::Form::Page def initialize(id, hsh, subsection, person_index:) super(id, hsh, subsection) - @id = "person_#{person_index}_over_retirement_value_check" @depends_on = [{ "person_#{person_index}_not_retired_over_soft_max_age?" => true }] @title_text = { "translation" => "soft_validations.retirement.max.title", diff --git a/app/models/form/lettings/pages/person_under_retirement_value_check.rb b/app/models/form/lettings/pages/person_under_retirement_value_check.rb index 9f7540f56..ab9c81beb 100644 --- a/app/models/form/lettings/pages/person_under_retirement_value_check.rb +++ b/app/models/form/lettings/pages/person_under_retirement_value_check.rb @@ -1,7 +1,6 @@ class Form::Lettings::Pages::PersonUnderRetirementValueCheck < ::Form::Page def initialize(id, hsh, subsection, person_index:) super(id, hsh, subsection) - @id = "person_#{person_index}_under_retirement_value_check" @depends_on = [{ "person_#{person_index}_retired_under_soft_min_age?" => true }] @title_text = { "translation" => "soft_validations.retirement.min.title", diff --git a/app/models/form/lettings/subsections/household_characteristics.rb b/app/models/form/lettings/subsections/household_characteristics.rb index 9fc6948bd..bb8533c12 100644 --- a/app/models/form/lettings/subsections/household_characteristics.rb +++ b/app/models/form/lettings/subsections/household_characteristics.rb @@ -15,9 +15,12 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection Form::Lettings::Pages::LeadTenantAge.new(nil, nil, self), Form::Lettings::Pages::NoFemalesPregnantHouseholdLeadAgeValueCheck.new(nil, nil, self), Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdLeadAgeValueCheck.new(nil, nil, self), + Form::Lettings::Pages::LeadTenantUnderRetirementValueCheck.new("age_lead_tenant_under_retirement_value_check", nil, self), + Form::Lettings::Pages::LeadTenantOverRetirementValueCheck.new("age_lead_tenant_over_retirement_value_check", nil, self), Form::Lettings::Pages::LeadTenantGenderIdentity.new(nil, nil, self), Form::Lettings::Pages::NoFemalesPregnantHouseholdLeadValueCheck.new(nil, nil, self), Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdLeadValueCheck.new(nil, nil, self), + Form::Lettings::Pages::LeadTenantOverRetirementValueCheck.new("gender_lead_tenant_over_retirement_value_check", nil, self), Form::Lettings::Pages::LeadTenantEthnicGroup.new(nil, nil, self), Form::Lettings::Pages::LeadTenantEthnicBackgroundArab.new(nil, nil, self), Form::Lettings::Pages::LeadTenantEthnicBackgroundAsian.new(nil, nil, self), @@ -26,8 +29,8 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection Form::Lettings::Pages::LeadTenantEthnicBackgroundWhite.new(nil, nil, self), Form::Lettings::Pages::LeadTenantNationality.new(nil, nil, self), Form::Lettings::Pages::LeadTenantWorkingSituation.new(nil, nil, self), - Form::Lettings::Pages::LeadTenantUnderRetirementValueCheck.new(nil, nil, self), - Form::Lettings::Pages::LeadTenantOverRetirementValueCheck.new(nil, nil, self), + Form::Lettings::Pages::LeadTenantUnderRetirementValueCheck.new("working_situation_lead_tenant_under_retirement_value_check", nil, self), + Form::Lettings::Pages::LeadTenantOverRetirementValueCheck.new("working_situation_lead_tenant_over_retirement_value_check", nil, self), Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 2), Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 2), Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 2, person_type: "child"), @@ -36,13 +39,16 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection person_index: 2), Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdPersonAgeValueCheck.new(nil, nil, self, person_index: 2), + Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("age_2_under_retirement_value_check", nil, self, person_index: 2), + Form::Lettings::Pages::PersonOverRetirementValueCheck.new("age_2_over_retirement_value_check", nil, self, person_index: 2), Form::Lettings::Pages::PersonGenderIdentity.new(nil, nil, self, person_index: 2), Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonValueCheck.new(nil, nil, self, person_index: 2), Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdPersonValueCheck.new(nil, nil, self, person_index: 2), + Form::Lettings::Pages::PersonOverRetirementValueCheck.new("gender_2_over_retirement_value_check", nil, self, person_index: 2), Form::Lettings::Pages::PersonWorkingSituation.new(nil, nil, self, person_index: 2), - Form::Lettings::Pages::PersonUnderRetirementValueCheck.new(nil, nil, self, person_index: 2), - Form::Lettings::Pages::PersonOverRetirementValueCheck.new(nil, nil, self, person_index: 2), + Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_2_under_retirement_value_check", nil, self, person_index: 2), + Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_2_over_retirement_value_check", nil, self, person_index: 2), Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 3), Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 3), Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 3, person_type: "child"), @@ -51,13 +57,16 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection person_index: 3), Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdPersonAgeValueCheck.new(nil, nil, self, person_index: 3), + Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("age_3_under_retirement_value_check", nil, self, person_index: 3), + Form::Lettings::Pages::PersonOverRetirementValueCheck.new("age_3_over_retirement_value_check", nil, self, person_index: 3), Form::Lettings::Pages::PersonGenderIdentity.new(nil, nil, self, person_index: 3), Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonValueCheck.new(nil, nil, self, person_index: 3), Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdPersonValueCheck.new(nil, nil, self, person_index: 3), + Form::Lettings::Pages::PersonOverRetirementValueCheck.new("gender_3_over_retirement_value_check", nil, self, person_index: 3), Form::Lettings::Pages::PersonWorkingSituation.new(nil, nil, self, person_index: 3), - Form::Lettings::Pages::PersonUnderRetirementValueCheck.new(nil, nil, self, person_index: 3), - Form::Lettings::Pages::PersonOverRetirementValueCheck.new(nil, nil, self, person_index: 3), + Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_3_under_retirement_value_check", nil, self, person_index: 3), + Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_3_over_retirement_value_check", nil, self, person_index: 3), Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 4), Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 4), Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 4, person_type: "child"), @@ -66,13 +75,16 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection person_index: 4), Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdPersonAgeValueCheck.new(nil, nil, self, person_index: 4), + Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("age_4_under_retirement_value_check", nil, self, person_index: 4), + Form::Lettings::Pages::PersonOverRetirementValueCheck.new("age_4_over_retirement_value_check", nil, self, person_index: 4), Form::Lettings::Pages::PersonGenderIdentity.new(nil, nil, self, person_index: 4), Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonValueCheck.new(nil, nil, self, person_index: 4), Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdPersonValueCheck.new(nil, nil, self, person_index: 4), + Form::Lettings::Pages::PersonOverRetirementValueCheck.new("gender_4_over_retirement_value_check", nil, self, person_index: 4), Form::Lettings::Pages::PersonWorkingSituation.new(nil, nil, self, person_index: 4), - Form::Lettings::Pages::PersonUnderRetirementValueCheck.new(nil, nil, self, person_index: 4), - Form::Lettings::Pages::PersonOverRetirementValueCheck.new(nil, nil, self, person_index: 4), + Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_4_under_retirement_value_check", nil, self, person_index: 4), + Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_4_over_retirement_value_check", nil, self, person_index: 4), Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 5), Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 5), Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 5, person_type: "child"), @@ -81,13 +93,16 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection person_index: 5), Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdPersonAgeValueCheck.new(nil, nil, self, person_index: 5), + Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("age_5_under_retirement_value_check", nil, self, person_index: 5), + Form::Lettings::Pages::PersonOverRetirementValueCheck.new("age_5_over_retirement_value_check", nil, self, person_index: 5), Form::Lettings::Pages::PersonGenderIdentity.new(nil, nil, self, person_index: 5), Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonValueCheck.new(nil, nil, self, person_index: 5), Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdPersonValueCheck.new(nil, nil, self, person_index: 5), + Form::Lettings::Pages::PersonOverRetirementValueCheck.new("gender_5_over_retirement_value_check", nil, self, person_index: 5), Form::Lettings::Pages::PersonWorkingSituation.new(nil, nil, self, person_index: 5), - Form::Lettings::Pages::PersonUnderRetirementValueCheck.new(nil, nil, self, person_index: 5), - Form::Lettings::Pages::PersonOverRetirementValueCheck.new(nil, nil, self, person_index: 5), + Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_5_under_retirement_value_check", nil, self, person_index: 5), + Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_5_over_retirement_value_check", nil, self, person_index: 5), Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 6), Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 6), Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 6, person_type: "child"), @@ -96,13 +111,16 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection person_index: 6), Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdPersonAgeValueCheck.new(nil, nil, self, person_index: 6), + Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("age_6_under_retirement_value_check", nil, self, person_index: 6), + Form::Lettings::Pages::PersonOverRetirementValueCheck.new("age_6_over_retirement_value_check", nil, self, person_index: 6), Form::Lettings::Pages::PersonGenderIdentity.new(nil, nil, self, person_index: 6), Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonValueCheck.new(nil, nil, self, person_index: 6), Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdPersonValueCheck.new(nil, nil, self, person_index: 6), + Form::Lettings::Pages::PersonOverRetirementValueCheck.new("gender_6_over_retirement_value_check", nil, self, person_index: 6), Form::Lettings::Pages::PersonWorkingSituation.new(nil, nil, self, person_index: 6), - Form::Lettings::Pages::PersonUnderRetirementValueCheck.new(nil, nil, self, person_index: 6), - Form::Lettings::Pages::PersonOverRetirementValueCheck.new(nil, nil, self, person_index: 6), + Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_6_under_retirement_value_check", nil, self, person_index: 6), + Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_6_over_retirement_value_check", nil, self, person_index: 6), Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 7), Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 7), Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 7, person_type: "child"), @@ -111,13 +129,16 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection person_index: 7), Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdPersonAgeValueCheck.new(nil, nil, self, person_index: 7), + Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("age_7_under_retirement_value_check", nil, self, person_index: 7), + Form::Lettings::Pages::PersonOverRetirementValueCheck.new("age_7_over_retirement_value_check", nil, self, person_index: 7), Form::Lettings::Pages::PersonGenderIdentity.new(nil, nil, self, person_index: 7), Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonValueCheck.new(nil, nil, self, person_index: 7), Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdPersonValueCheck.new(nil, nil, self, person_index: 7), + Form::Lettings::Pages::PersonOverRetirementValueCheck.new("gender_7_over_retirement_value_check", nil, self, person_index: 7), Form::Lettings::Pages::PersonWorkingSituation.new(nil, nil, self, person_index: 7), - Form::Lettings::Pages::PersonUnderRetirementValueCheck.new(nil, nil, self, person_index: 7), - Form::Lettings::Pages::PersonOverRetirementValueCheck.new(nil, nil, self, person_index: 7), + Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_7_under_retirement_value_check", nil, self, person_index: 7), + Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_7_over_retirement_value_check", nil, self, person_index: 7), Form::Lettings::Pages::PersonKnown.new(nil, nil, self, person_index: 8), Form::Lettings::Pages::PersonRelationshipToLead.new(nil, nil, self, person_index: 8), Form::Lettings::Pages::PersonAge.new(nil, nil, self, person_index: 8, person_type: "child"), @@ -126,13 +147,16 @@ class Form::Lettings::Subsections::HouseholdCharacteristics < ::Form::Subsection person_index: 8), Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdPersonAgeValueCheck.new(nil, nil, self, person_index: 8), + Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("age_8_under_retirement_value_check", nil, self, person_index: 8), + Form::Lettings::Pages::PersonOverRetirementValueCheck.new("age_8_over_retirement_value_check", nil, self, person_index: 8), Form::Lettings::Pages::PersonGenderIdentity.new(nil, nil, self, person_index: 8), Form::Lettings::Pages::NoFemalesPregnantHouseholdPersonValueCheck.new(nil, nil, self, person_index: 8), Form::Lettings::Pages::FemalesInSoftAgeRangeInPregnantHouseholdPersonValueCheck.new(nil, nil, self, person_index: 8), + Form::Lettings::Pages::PersonOverRetirementValueCheck.new("gender_8_over_retirement_value_check", nil, self, person_index: 8), Form::Lettings::Pages::PersonWorkingSituation.new(nil, nil, self, person_index: 8), - Form::Lettings::Pages::PersonUnderRetirementValueCheck.new(nil, nil, self, person_index: 8), - Form::Lettings::Pages::PersonOverRetirementValueCheck.new(nil, nil, self, person_index: 8), + Form::Lettings::Pages::PersonUnderRetirementValueCheck.new("working_situation_8_under_retirement_value_check", nil, self, person_index: 8), + Form::Lettings::Pages::PersonOverRetirementValueCheck.new("working_situation_8_over_retirement_value_check", nil, self, person_index: 8), ].compact end end diff --git a/spec/models/form/lettings/pages/person_over_retirement_value_check_spec.rb b/spec/models/form/lettings/pages/person_over_retirement_value_check_spec.rb index 45587b4fb..865a01f61 100644 --- a/spec/models/form/lettings/pages/person_over_retirement_value_check_spec.rb +++ b/spec/models/form/lettings/pages/person_over_retirement_value_check_spec.rb @@ -24,10 +24,6 @@ RSpec.describe Form::Lettings::Pages::PersonOverRetirementValueCheck, type: :mod end context "with person 2" do - it "has the correct id" do - expect(page.id).to eq("person_2_over_retirement_value_check") - end - it "has correct depends_on" do expect(page.depends_on).to eq( [{ "person_2_not_retired_over_soft_max_age?" => true }], @@ -69,10 +65,6 @@ RSpec.describe Form::Lettings::Pages::PersonOverRetirementValueCheck, type: :mod context "with person 3" do let(:person_index) { 3 } - it "has the correct id" do - expect(page.id).to eq("person_3_over_retirement_value_check") - end - it "has correct depends_on" do expect(page.depends_on).to eq( [{ "person_3_not_retired_over_soft_max_age?" => true }], diff --git a/spec/models/form/lettings/pages/person_under_retirement_value_check_spec.rb b/spec/models/form/lettings/pages/person_under_retirement_value_check_spec.rb index 929009ce7..58fbcf33d 100644 --- a/spec/models/form/lettings/pages/person_under_retirement_value_check_spec.rb +++ b/spec/models/form/lettings/pages/person_under_retirement_value_check_spec.rb @@ -24,10 +24,6 @@ RSpec.describe Form::Lettings::Pages::PersonUnderRetirementValueCheck, type: :mo end context "with person 2" do - it "has the correct id" do - expect(page.id).to eq("person_2_under_retirement_value_check") - end - it "has correct depends_on" do expect(page.depends_on).to eq( [{ "person_2_retired_under_soft_min_age?" => true }], @@ -55,10 +51,6 @@ RSpec.describe Form::Lettings::Pages::PersonUnderRetirementValueCheck, type: :mo context "with person 3" do let(:person_index) { 3 } - it "has the correct id" do - expect(page.id).to eq("person_3_under_retirement_value_check") - end - it "has correct depends_on" do expect(page.depends_on).to eq( [{ "person_3_retired_under_soft_min_age?" => true }], diff --git a/spec/models/form/lettings/subsections/household_characteristics_spec.rb b/spec/models/form/lettings/subsections/household_characteristics_spec.rb index d0d55b8bf..4586d5592 100644 --- a/spec/models/form/lettings/subsections/household_characteristics_spec.rb +++ b/spec/models/form/lettings/subsections/household_characteristics_spec.rb @@ -21,9 +21,12 @@ RSpec.describe Form::Lettings::Subsections::HouseholdCharacteristics, type: :mod lead_tenant_age no_females_pregnant_household_lead_age_value_check females_in_soft_age_range_in_pregnant_household_lead_age_value_check + age_lead_tenant_under_retirement_value_check + age_lead_tenant_over_retirement_value_check lead_tenant_gender_identity no_females_pregnant_household_lead_value_check females_in_soft_age_range_in_pregnant_household_lead_value_check + gender_lead_tenant_over_retirement_value_check lead_tenant_ethnic_group lead_tenant_ethnic_background_arab lead_tenant_ethnic_background_asian @@ -32,92 +35,113 @@ RSpec.describe Form::Lettings::Subsections::HouseholdCharacteristics, type: :mod lead_tenant_ethnic_background_white lead_tenant_nationality lead_tenant_working_situation - lead_tenant_under_retirement_value_check - lead_tenant_over_retirement_value_check + working_situation_lead_tenant_under_retirement_value_check + working_situation_lead_tenant_over_retirement_value_check person_2_known person_2_relationship_to_lead person_2_age_child person_2_age_non_child no_females_pregnant_household_person_2_age_value_check females_in_soft_age_range_in_pregnant_household_person_2_age_value_check + age_2_under_retirement_value_check + age_2_over_retirement_value_check person_2_gender_identity no_females_pregnant_household_person_2_value_check females_in_soft_age_range_in_pregnant_household_person_2_value_check + gender_2_over_retirement_value_check person_2_working_situation - person_2_under_retirement_value_check - person_2_over_retirement_value_check + working_situation_2_under_retirement_value_check + working_situation_2_over_retirement_value_check person_3_known person_3_relationship_to_lead person_3_age_child person_3_age_non_child no_females_pregnant_household_person_3_age_value_check females_in_soft_age_range_in_pregnant_household_person_3_age_value_check + age_3_under_retirement_value_check + age_3_over_retirement_value_check person_3_gender_identity no_females_pregnant_household_person_3_value_check females_in_soft_age_range_in_pregnant_household_person_3_value_check + gender_3_over_retirement_value_check person_3_working_situation - person_3_under_retirement_value_check - person_3_over_retirement_value_check + working_situation_3_under_retirement_value_check + working_situation_3_over_retirement_value_check person_4_known person_4_relationship_to_lead person_4_age_child person_4_age_non_child no_females_pregnant_household_person_4_age_value_check females_in_soft_age_range_in_pregnant_household_person_4_age_value_check + age_4_under_retirement_value_check + age_4_over_retirement_value_check person_4_gender_identity no_females_pregnant_household_person_4_value_check females_in_soft_age_range_in_pregnant_household_person_4_value_check + gender_4_over_retirement_value_check person_4_working_situation - person_4_under_retirement_value_check - person_4_over_retirement_value_check + working_situation_4_under_retirement_value_check + working_situation_4_over_retirement_value_check person_5_known person_5_relationship_to_lead person_5_age_child person_5_age_non_child no_females_pregnant_household_person_5_age_value_check females_in_soft_age_range_in_pregnant_household_person_5_age_value_check + age_5_under_retirement_value_check + age_5_over_retirement_value_check person_5_gender_identity no_females_pregnant_household_person_5_value_check females_in_soft_age_range_in_pregnant_household_person_5_value_check + gender_5_over_retirement_value_check person_5_working_situation - person_5_under_retirement_value_check - person_5_over_retirement_value_check + working_situation_5_under_retirement_value_check + working_situation_5_over_retirement_value_check person_6_known person_6_relationship_to_lead person_6_age_child person_6_age_non_child no_females_pregnant_household_person_6_age_value_check females_in_soft_age_range_in_pregnant_household_person_6_age_value_check + age_6_under_retirement_value_check + age_6_over_retirement_value_check person_6_gender_identity no_females_pregnant_household_person_6_value_check females_in_soft_age_range_in_pregnant_household_person_6_value_check + gender_6_over_retirement_value_check person_6_working_situation - person_6_under_retirement_value_check - person_6_over_retirement_value_check + working_situation_6_under_retirement_value_check + working_situation_6_over_retirement_value_check person_7_known person_7_relationship_to_lead person_7_age_child person_7_age_non_child no_females_pregnant_household_person_7_age_value_check females_in_soft_age_range_in_pregnant_household_person_7_age_value_check + age_7_under_retirement_value_check + age_7_over_retirement_value_check person_7_gender_identity no_females_pregnant_household_person_7_value_check females_in_soft_age_range_in_pregnant_household_person_7_value_check + gender_7_over_retirement_value_check person_7_working_situation - person_7_under_retirement_value_check - person_7_over_retirement_value_check + working_situation_7_under_retirement_value_check + working_situation_7_over_retirement_value_check person_8_known person_8_relationship_to_lead person_8_age_child person_8_age_non_child no_females_pregnant_household_person_8_age_value_check females_in_soft_age_range_in_pregnant_household_person_8_age_value_check + age_8_under_retirement_value_check + age_8_over_retirement_value_check person_8_gender_identity no_females_pregnant_household_person_8_value_check females_in_soft_age_range_in_pregnant_household_person_8_value_check + gender_8_over_retirement_value_check person_8_working_situation - person_8_under_retirement_value_check - person_8_over_retirement_value_check + working_situation_8_under_retirement_value_check + working_situation_8_over_retirement_value_check ], ) end From 7bfe3f4d7590fad0cc5c38b2e08c7f4dc7dcd47a Mon Sep 17 00:00:00 2001 From: natdeanlewissoftwire <94526761+natdeanlewissoftwire@users.noreply.github.com> Date: Fri, 16 Jun 2023 15:28:35 +0100 Subject: [PATCH 09/19] CLDC-2447 Bulk upload 23-24 lettings net income known bug (#1695) * feat: fix bug * feat: update tests * feat: test ALL possibilities --- .../lettings/year2023/row_parser.rb | 2 -- .../lettings/year2022/row_parser_spec.rb | 32 +++++++++++++++++-- .../lettings/year2023/row_parser_spec.rb | 24 ++++++++++++-- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/app/services/bulk_upload/lettings/year2023/row_parser.rb b/app/services/bulk_upload/lettings/year2023/row_parser.rb index 225c45b4f..0912b02e4 100644 --- a/app/services/bulk_upload/lettings/year2023/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2023/row_parser.rb @@ -1442,8 +1442,6 @@ private when 2 1 when 3 - 1 - when 4 2 end end diff --git a/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb b/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb index 05120713d..365771bb6 100644 --- a/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2022/row_parser_spec.rb @@ -1295,10 +1295,36 @@ RSpec.describe BulkUpload::Lettings::Year2022::RowParser do end describe "#net_income_known" do - let(:attributes) { { bulk_upload:, field_51: "1" } } + context "when 1" do + let(:attributes) { { bulk_upload:, field_51: "1" } } - it "sets value from correct mapping" do - expect(parser.log.net_income_known).to eq(0) + it "sets value from correct mapping" do + expect(parser.log.net_income_known).to eq(0) + end + end + + context "when 2" do + let(:attributes) { { bulk_upload:, field_51: "2" } } + + it "sets value from correct mapping" do + expect(parser.log.net_income_known).to eq(1) + end + end + + context "when 3" do + let(:attributes) { { bulk_upload:, field_51: "3" } } + + it "sets value from correct mapping" do + expect(parser.log.net_income_known).to eq(1) + end + end + + context "when 4" do + let(:attributes) { { bulk_upload:, field_51: "4" } } + + it "sets value from correct mapping" do + expect(parser.log.net_income_known).to eq(2) + end 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 8586a470c..5bb6e8958 100644 --- a/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb @@ -1365,10 +1365,28 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do end describe "#net_income_known" do - let(:attributes) { { bulk_upload:, field_120: "1" } } + context "when 1" do + let(:attributes) { { bulk_upload:, field_120: "1" } } - it "sets value from correct mapping" do - expect(parser.log.net_income_known).to eq(0) + it "sets value from correct mapping" do + expect(parser.log.net_income_known).to eq(0) + end + end + + context "when 2" do + let(:attributes) { { bulk_upload:, field_120: "2" } } + + it "sets value from correct mapping" do + expect(parser.log.net_income_known).to eq(1) + end + end + + context "when 3" do + let(:attributes) { { bulk_upload:, field_120: "3" } } + + it "sets value from correct mapping" do + expect(parser.log.net_income_known).to eq(2) + end end end From bc9519f365731718da0730d486a9a3472e5ba1ed Mon Sep 17 00:00:00 2001 From: Aaron Spencer <62190777+Airk0n@users.noreply.github.com> Date: Fri, 16 Jun 2023 16:03:28 +0100 Subject: [PATCH 10/19] CLDC 2328: Resend invitation button (#1680) * CDCL-2326: Init - button moved, button added * CLDC-2328: Button cleanup, user controller endpoint created * CLDC-2328: Tests added * CLDC-2328: Tests added to user controller * CLDC-2328: WIp Testing * CLDC-2328: Testing of sent emails * CLDC-2328: Button uses post instead of get, tests reflect this. * CLDC-2328: Invite button only appears for support users. * CLDC-2328: Email test refactor. * CLDC-2328: Flash now shows email address, Button moved. --- app/controllers/users_controller.rb | 6 ++ app/views/users/show.html.erb | 27 +++++---- config/routes.rb | 1 + spec/features/user_spec.rb | 79 ++++++++++++++++++++++++++ spec/requests/users_controller_spec.rb | 35 ++++++++++++ 5 files changed, 137 insertions(+), 11 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 0c343b8c3..9e6491554 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -29,6 +29,12 @@ class UsersController < ApplicationController end end + def resend_invite + @user.send_confirmation_instructions + flash[:notice] = "Invitation sent to #{@user.email}" + render :show + end + def show; end def dpo; end diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index e6cb9fd3e..845e6ebba 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -5,17 +5,6 @@

<%= content_for(:title) %>

-

- <% if current_user.can_toggle_active?(@user) %> - <% if @user.active? %> - <%= govuk_link_to "Deactivate user", "/users/#{@user.id}/deactivate" %> - <% else %> - - This user has been deactivated. <%= govuk_link_to "Reactivate user", "/users/#{@user.id}/reactivate" %> - - <% end %> - <% end %> -

Personal details @@ -103,5 +92,21 @@ end end %> <% end %> + +
+ <% if current_user.can_toggle_active?(@user) %> + <% if @user.active? %> + <%= govuk_button_link_to "Deactivate user", deactivate_user_path(@user), warning: true %> + <% if current_user.support? %> + <%= govuk_button_to "Resend invite link", resend_invite_user_path(@user), secondary: true %> + <% end %> + <% else %> + + This user has been deactivated. <%= govuk_button_link_to "Reactivate user", reactivate_user_path(@user) %> + + <% end %> + <% end %> +
+

diff --git a/config/routes.rb b/config/routes.rb index fa0cb598e..8b61f1ece 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -110,6 +110,7 @@ Rails.application.routes.draw do member do get "deactivate", to: "users#deactivate" get "reactivate", to: "users#reactivate" + post "resend-invite", to: "users#resend_invite" end end diff --git a/spec/features/user_spec.rb b/spec/features/user_spec.rb index 0c7f6c2c6..d4e1a6052 100644 --- a/spec/features/user_spec.rb +++ b/spec/features/user_spec.rb @@ -454,6 +454,85 @@ RSpec.describe "User Features" do end end + context "when signed in as support" do + let!(:user) { FactoryBot.create(:user, :support) } + let!(:other_user) { FactoryBot.create(:user, name: "new user", organisation: user.organisation, email: "new_user@example.com", confirmation_token: "abc") } + + context "when reinviting a user before initial confirmation email has been sent" do + let(:personalisation) do + { + name: "new user", + email: "new_user@example.com", + organisation: other_user.organisation.name, + link: include("/account/confirmation?confirmation_token=#{other_user.confirmation_token}"), + } + end + + before do + other_user.update!(initial_confirmation_sent: false) + allow(user).to receive(:need_two_factor_authentication?).and_return(false) + sign_in(user) + visit(user_path(user.id)) + end + + it "sends initial confirmable template email when the resend invite link is clicked" do + other_user.legacy_users.destroy_all + visit(user_path(other_user)) + expect(notify_client).to receive(:send_email).with(email_address: "new_user@example.com", template_id: User::CONFIRMABLE_TEMPLATE_ID, personalisation:).once + click_button("Resend invite link") + end + end + + context "when reinviting a user after initial confirmation email has been sent" do + let(:personalisation) do + { + name: "new user", + email: "new_user@example.com", + organisation: other_user.organisation.name, + link: include("/account/confirmation?confirmation_token=#{other_user.confirmation_token}"), + } + end + + before do + other_user.update!(initial_confirmation_sent: true) + allow(user).to receive(:need_two_factor_authentication?).and_return(false) + sign_in(user) + visit(user_path(user.id)) + end + + it "sends and email when the resend invite link is clicked" do + other_user.legacy_users.destroy_all + visit(user_path(other_user)) + expect(notify_client).to receive(:send_email).with(email_address: "new_user@example.com", template_id: User::RECONFIRMABLE_TEMPLATE_ID, personalisation:).once + click_button("Resend invite link") + end + end + + context "when reinviting a legacy user" do + let(:personalisation) do + { + name: "new user", + email: "new_user@example.com", + organisation: other_user.organisation.name, + link: include("/account/confirmation?confirmation_token=#{other_user.confirmation_token}"), + } + end + + before do + other_user.update!(initial_confirmation_sent: true) + allow(user).to receive(:need_two_factor_authentication?).and_return(false) + sign_in(user) + visit(user_path(user.id)) + end + + it "sends beta onboarding email to be sent when user is legacy" do + visit(user_path(other_user)) + expect(notify_client).to receive(:send_email).with(email_address: "new_user@example.com", template_id: User::BETA_ONBOARDING_TEMPLATE_ID, personalisation:).once + click_button("Resend invite link") + end + end + end + context "when the user is a customer support person" do let(:support_user) { FactoryBot.create(:user, :support, last_sign_in_at: Time.zone.now) } let(:devise_notify_mailer) { DeviseNotifyMailer.new } diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb index a97086bb2..ecba12933 100644 --- a/spec/requests/users_controller_spec.rb +++ b/spec/requests/users_controller_spec.rb @@ -96,6 +96,13 @@ RSpec.describe UsersController, type: :request do expect(response).to redirect_to("/account/sign-in") end end + + describe "#resend_invite" do + it "does not allow resending activation emails" do + get deactivate_user_path(user.id), headers: headers, params: {} + expect(response).to redirect_to(new_user_session_path) + end + end end context "when user is signed in as a data provider" do @@ -123,6 +130,10 @@ RSpec.describe UsersController, type: :request do expect(page).not_to have_link("Deactivate user", href: "/users/#{user.id}/deactivate") end + it "does not allow resending invitation emails" do + expect(page).not_to have_button("Resend invite link") + end + context "when user is deactivated" do before do user.update!(active: false) @@ -132,6 +143,10 @@ RSpec.describe UsersController, type: :request do it "does not allow reactivating the user" do expect(page).not_to have_link("Reactivate user", href: "/users/#{user.id}/reactivate") end + + it "does not allow resending invitation emails" do + expect(page).not_to have_link("Resend invite link") + end end end @@ -184,6 +199,10 @@ RSpec.describe UsersController, type: :request do it "does not allow reactivating the user" do expect(page).not_to have_link("Reactivate user", href: "/users/#{other_user.id}/reactivate") end + + it "does not allow resending invitation emails" do + expect(page).not_to have_button("Resend invite link") + end end end @@ -499,6 +518,10 @@ RSpec.describe UsersController, type: :request do it "does not allow reactivating the user" do expect(page).not_to have_link("Reactivate user", href: "/users/#{user.id}/reactivate") end + + it "does not allow resending invitation emails" do + expect(page).not_to have_button("Resend invite link") + end end end @@ -530,6 +553,10 @@ RSpec.describe UsersController, type: :request do expect(page).to have_link("Deactivate user", href: "/users/#{other_user.id}/deactivate") end + it "does not allow you to resend invitation emails" do + expect(page).not_to have_button("Resend invite link") + end + context "when user is deactivated" do before do other_user.update!(active: false) @@ -543,6 +570,10 @@ RSpec.describe UsersController, type: :request do it "allows reactivating the user" do expect(page).to have_link("Reactivate user", href: "/users/#{other_user.id}/reactivate") end + + it "does not allow you to resend invitation emails" do + expect(page).not_to have_button("Resend invite link") + end end end @@ -1177,6 +1208,10 @@ RSpec.describe UsersController, type: :request do expect(page).to have_link("Deactivate user", href: "/users/#{other_user.id}/deactivate") end + it "allows you to resend invitation emails" do + expect(page).to have_button("Resend invite link") + end + context "when user is deactivated" do before do other_user.update!(active: false) From 8e81d030510acca75bc1723a6ee1f558faf1f17f Mon Sep 17 00:00:00 2001 From: natdeanlewissoftwire <94526761+natdeanlewissoftwire@users.noreply.github.com> Date: Fri, 16 Jun 2023 16:50:29 +0100 Subject: [PATCH 11/19] feat: sort dpo names in alphabetical order (#1708) --- app/components/data_protection_confirmation_banner_component.rb | 2 +- .../data_protection_confirmation_banner_component_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/data_protection_confirmation_banner_component.rb b/app/components/data_protection_confirmation_banner_component.rb index b1dc61152..b11c01f02 100644 --- a/app/components/data_protection_confirmation_banner_component.rb +++ b/app/components/data_protection_confirmation_banner_component.rb @@ -22,7 +22,7 @@ class DataProtectionConfirmationBannerComponent < ViewComponent::Base def data_protection_officers_text if org_or_user_org.data_protection_officers.any? - "You can ask: #{org_or_user_org.data_protection_officers.map(&:name).join(', ')}" + "You can ask: #{org_or_user_org.data_protection_officers.map(&:name).sort_by(&:downcase).join(', ')}" end end diff --git a/spec/components/data_protection_confirmation_banner_component_spec.rb b/spec/components/data_protection_confirmation_banner_component_spec.rb index a76db4435..792e3a5a3 100644 --- a/spec/components/data_protection_confirmation_banner_component_spec.rb +++ b/spec/components/data_protection_confirmation_banner_component_spec.rb @@ -31,7 +31,7 @@ RSpec.describe DataProtectionConfirmationBannerComponent, type: :component do create(:user, organisation:, is_dpo: true, name: "Test McTest") end - it "returns the correct text" do + it "returns the correct list of names, in alphabetical order)" do expect(component.data_protection_officers_text).to eq("You can ask: Danny Rojas, Test McTest") end end From aa4c250b3c4a6fe46327223c122f31b71a0a654f Mon Sep 17 00:00:00 2001 From: natdeanlewissoftwire <94526761+natdeanlewissoftwire@users.noreply.github.com> Date: Mon, 19 Jun 2023 08:54:30 +0100 Subject: [PATCH 12/19] CLDC-2390 CYA address redesign (#1696) * feat: update cya design lettings * feat: update cya sales * feat: update some tests * feat: fix error labels for address questions * feat: fix error labels for address questions and update row parsers * feat: update tests * feat: update tests * feat: rename display_label --- app/helpers/question_view_helper.rb | 2 +- .../form/lettings/questions/address_line1.rb | 17 ++----- app/models/form/lettings/questions/county.rb | 7 ++- .../questions/postcode_for_full_address.rb | 7 ++- .../form/lettings/questions/town_or_city.rb | 7 ++- app/models/form/question.rb | 14 +++--- .../form/sales/questions/address_line1.rb | 17 ++----- app/models/form/sales/questions/county.rb | 7 ++- .../questions/postcode_for_full_address.rb | 7 ++- .../form/sales/questions/town_or_city.rb | 7 ++- .../lettings/year2022/row_parser.rb | 4 +- .../lettings/year2023/row_parser.rb | 4 +- .../bulk_upload/sales/year2022/row_parser.rb | 4 +- .../bulk_upload/sales/year2023/row_parser.rb | 4 +- spec/helpers/question_view_helper_spec.rb | 8 ++++ .../lettings/questions/address_line1_spec.rb | 40 +++-------------- .../form/lettings/questions/county_spec.rb | 10 ++--- .../postcode_for_full_address_spec.rb | 10 ++--- .../lettings/questions/town_or_city_spec.rb | 10 ++--- .../sales/questions/address_line1_spec.rb | 44 ++++--------------- .../form/sales/questions/county_spec.rb | 10 ++--- .../postcode_for_full_address_spec.rb | 10 ++--- .../form/sales/questions/town_or_city_spec.rb | 10 ++--- .../lettings/year2023/row_parser_spec.rb | 4 +- 24 files changed, 99 insertions(+), 165 deletions(-) diff --git a/app/helpers/question_view_helper.rb b/app/helpers/question_view_helper.rb index fdb144c4e..a656344ca 100644 --- a/app/helpers/question_view_helper.rb +++ b/app/helpers/question_view_helper.rb @@ -7,7 +7,7 @@ module QuestionViewHelper def legend(question, page_header, conditional) { - text: [question.question_number_string(conditional:), question.header.html_safe].compact.join(" - "), + text: [question.question_number_string(hidden: conditional || question.hide_question_number_on_page), question.header.html_safe].compact.join(" - "), size: label_size(page_header, conditional, question), tag: label_tag(page_header, conditional), } diff --git a/app/models/form/lettings/questions/address_line1.rb b/app/models/form/lettings/questions/address_line1.rb index ef197e4fd..95702b8de 100644 --- a/app/models/form/lettings/questions/address_line1.rb +++ b/app/models/form/lettings/questions/address_line1.rb @@ -2,29 +2,20 @@ class Form::Lettings::Questions::AddressLine1 < ::Form::Question def initialize(id, hsh, page) super @id = "address_line1" - @check_answer_label = "Address" @header = "Address line 1" + @error_label = "Address line 1" @type = "text" @plain_label = true - @check_answer_label = "Q12 - Address" + @check_answer_label = "Address lines 1 and 2" @disable_clearing_if_not_routed_or_dynamic_answer_options = true + @question_number = 12 + @hide_question_number_on_page = true end def answer_label(log, _current_user = nil) [ log.address_line1, log.address_line2, - log.postcode_full, - log.town_or_city, - log.county, ].select(&:present?).join("\n") end - - def get_extra_check_answer_value(log) - return unless log.is_la_inferred? - - la = LocalAuthority.find_by(code: log.la)&.name - - la.presence - end end diff --git a/app/models/form/lettings/questions/county.rb b/app/models/form/lettings/questions/county.rb index 9f0dc7138..8eaa410eb 100644 --- a/app/models/form/lettings/questions/county.rb +++ b/app/models/form/lettings/questions/county.rb @@ -5,10 +5,9 @@ class Form::Lettings::Questions::County < ::Form::Question @header = "County (optional)" @type = "text" @plain_label = true + @check_answer_label = "County" @disable_clearing_if_not_routed_or_dynamic_answer_options = true - end - - def hidden_in_check_answers?(_log = nil, _current_user = nil) - true + @question_number = 12 + @hide_question_number_on_page = true end end diff --git a/app/models/form/lettings/questions/postcode_for_full_address.rb b/app/models/form/lettings/questions/postcode_for_full_address.rb index 4f41867d7..8f7de5f52 100644 --- a/app/models/form/lettings/questions/postcode_for_full_address.rb +++ b/app/models/form/lettings/questions/postcode_for_full_address.rb @@ -17,10 +17,9 @@ class Form::Lettings::Questions::PostcodeForFullAddress < ::Form::Question }, } @plain_label = true + @check_answer_label = "Postcode" @disable_clearing_if_not_routed_or_dynamic_answer_options = true - end - - def hidden_in_check_answers?(_log = nil, _current_user = nil) - true + @question_number = 12 + @hide_question_number_on_page = true end end diff --git a/app/models/form/lettings/questions/town_or_city.rb b/app/models/form/lettings/questions/town_or_city.rb index 501e9bec4..43aa48625 100644 --- a/app/models/form/lettings/questions/town_or_city.rb +++ b/app/models/form/lettings/questions/town_or_city.rb @@ -5,10 +5,9 @@ class Form::Lettings::Questions::TownOrCity < ::Form::Question @header = "Town or city" @type = "text" @plain_label = true + @check_answer_label = "Town or city" @disable_clearing_if_not_routed_or_dynamic_answer_options = true - end - - def hidden_in_check_answers?(_log = nil, _current_user = nil) - true + @question_number = 12 + @hide_question_number_on_page = true end end diff --git a/app/models/form/question.rb b/app/models/form/question.rb index 82d3a09b5..2d6b3aa58 100644 --- a/app/models/form/question.rb +++ b/app/models/form/question.rb @@ -4,7 +4,7 @@ class Form::Question :conditional_for, :readonly, :answer_options, :page, :check_answer_label, :inferred_answers, :hidden_in_check_answers, :inferred_check_answers_value, :guidance_partial, :prefix, :suffix, :requires_js, :fields_added, :derived, - :check_answers_card_number, :unresolved_hint_text, :question_number, :plain_label + :check_answers_card_number, :unresolved_hint_text, :question_number, :hide_question_number_on_page, :plain_label, :error_label module GuidancePosition TOP = 1 @@ -41,7 +41,9 @@ class Form::Question @check_answers_card_number = hsh["check_answers_card_number"] || 0 @unresolved_hint_text = hsh["unresolved_hint_text"] @question_number = hsh["question_number"] + @hide_question_number_on_page = hsh["hide_question_number_on_page"] || false @plain_label = hsh["plain_label"] + @error_label = hsh["error_label"] @disable_clearing_if_not_routed_or_dynamic_answer_options = hsh["disable_clearing_if_not_routed_or_dynamic_answer_options"] end end @@ -194,15 +196,15 @@ class Form::Question type == "radio" && RADIO_REFUSED_VALUE[id.to_sym]&.include?(value) end - def display_label - check_answer_label || header || id.humanize + def error_display_label + error_label || check_answer_label || header || id.humanize end def unanswered_error_message return I18n.t("validations.declaration.missing") if id == "declaration" return I18n.t("validations.privacynotice.missing") if id == "privacynotice" - I18n.t("validations.not_answered", question: display_label.downcase) + I18n.t("validations.not_answered", question: error_display_label.downcase) end def suffix_label(log) @@ -241,8 +243,8 @@ class Form::Question selected_answer_option_is_derived?(log) || has_inferred_check_answers_value?(log) end - def question_number_string(conditional: false) - if @question_number && !conditional && form.start_date.year >= 2023 + def question_number_string(hidden: false) + if @question_number && !hidden && form.start_date.year >= 2023 "Q#{@question_number}" end end diff --git a/app/models/form/sales/questions/address_line1.rb b/app/models/form/sales/questions/address_line1.rb index edee2e7ee..fcf31a082 100644 --- a/app/models/form/sales/questions/address_line1.rb +++ b/app/models/form/sales/questions/address_line1.rb @@ -2,29 +2,20 @@ class Form::Sales::Questions::AddressLine1 < ::Form::Question def initialize(id, hsh, page) super @id = "address_line1" - @check_answer_label = "Address" @header = "Address line 1" + @error_label = "Address line 1" @type = "text" @plain_label = true - @check_answer_label = "Q15 - Address" + @check_answer_label = "Address lines 1 and 2" @disable_clearing_if_not_routed_or_dynamic_answer_options = true + @question_number = 15 + @hide_question_number_on_page = true end def answer_label(log, _current_user = nil) [ log.address_line1, log.address_line2, - log.postcode_full, - log.town_or_city, - log.county, ].select(&:present?).join("\n") end - - def get_extra_check_answer_value(log) - return unless log.is_la_inferred? - - la = LocalAuthority.find_by(code: log.la)&.name - - la.presence - end end diff --git a/app/models/form/sales/questions/county.rb b/app/models/form/sales/questions/county.rb index 6586cb9e6..9bd68d350 100644 --- a/app/models/form/sales/questions/county.rb +++ b/app/models/form/sales/questions/county.rb @@ -5,10 +5,9 @@ class Form::Sales::Questions::County < ::Form::Question @header = "County (optional)" @type = "text" @plain_label = true + @check_answer_label = "County" @disable_clearing_if_not_routed_or_dynamic_answer_options = true - end - - def hidden_in_check_answers?(_log = nil, _current_user = nil) - true + @question_number = 15 + @hide_question_number_on_page = true end end diff --git a/app/models/form/sales/questions/postcode_for_full_address.rb b/app/models/form/sales/questions/postcode_for_full_address.rb index 5d3b9f122..f4a21d0e3 100644 --- a/app/models/form/sales/questions/postcode_for_full_address.rb +++ b/app/models/form/sales/questions/postcode_for_full_address.rb @@ -17,10 +17,9 @@ class Form::Sales::Questions::PostcodeForFullAddress < ::Form::Question }, } @plain_label = true + @check_answer_label = "Postcode" @disable_clearing_if_not_routed_or_dynamic_answer_options = true - end - - def hidden_in_check_answers?(_log = nil, _current_user = nil) - true + @question_number = 15 + @hide_question_number_on_page = true end end diff --git a/app/models/form/sales/questions/town_or_city.rb b/app/models/form/sales/questions/town_or_city.rb index 25acfe036..b136d186c 100644 --- a/app/models/form/sales/questions/town_or_city.rb +++ b/app/models/form/sales/questions/town_or_city.rb @@ -5,10 +5,9 @@ class Form::Sales::Questions::TownOrCity < ::Form::Question @header = "Town or city" @type = "text" @plain_label = true + @check_answer_label = "Town or city" @disable_clearing_if_not_routed_or_dynamic_answer_options = true - end - - def hidden_in_check_answers?(_log = nil, _current_user = nil) - true + @question_number = 15 + @hide_question_number_on_page = true end end diff --git a/app/services/bulk_upload/lettings/year2022/row_parser.rb b/app/services/bulk_upload/lettings/year2022/row_parser.rb index b79447b67..c6251c81f 100644 --- a/app/services/bulk_upload/lettings/year2022/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2022/row_parser.rb @@ -774,13 +774,13 @@ private if setup_question?(question) fields.each do |field| if errors.select { |e| fields.include?(e.attribute) }.none? - errors.add(field, I18n.t("validations.not_answered", question: question.check_answer_label&.downcase), category: :setup) + errors.add(field, I18n.t("validations.not_answered", question: question.error_display_label&.downcase), category: :setup) end end else fields.each do |field| unless errors.any? { |e| fields.include?(e.attribute) } - errors.add(field, I18n.t("validations.not_answered", question: question.check_answer_label&.downcase)) + errors.add(field, I18n.t("validations.not_answered", question: question.error_display_label&.downcase)) end end end diff --git a/app/services/bulk_upload/lettings/year2023/row_parser.rb b/app/services/bulk_upload/lettings/year2023/row_parser.rb index 0912b02e4..9c3c76fab 100644 --- a/app/services/bulk_upload/lettings/year2023/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2023/row_parser.rb @@ -681,14 +681,14 @@ private if setup_question?(question) fields.each do |field| if errors.select { |e| fields.include?(e.attribute) }.none? - question_text = question.check_answer_label.presence || question.header.presence || "this question" + question_text = question.error_display_label.presence || "this question" errors.add(field, I18n.t("validations.not_answered", question: question_text.downcase), category: :setup) end end else fields.each do |field| unless errors.any? { |e| fields.include?(e.attribute) } - question_text = question.check_answer_label.presence || question.header.presence || "this question" + question_text = question.error_display_label.presence || "this question" errors.add(field, I18n.t("validations.not_answered", question: question_text.downcase)) end end diff --git a/app/services/bulk_upload/sales/year2022/row_parser.rb b/app/services/bulk_upload/sales/year2022/row_parser.rb index ed51a0601..1ac6258fd 100644 --- a/app/services/bulk_upload/sales/year2022/row_parser.rb +++ b/app/services/bulk_upload/sales/year2022/row_parser.rb @@ -1057,9 +1057,9 @@ private fields.each do |field| unless errors.any? { |e| fields.include?(e.attribute) } if setup_question?(question) - errors.add(field, I18n.t("validations.not_answered", question: question.check_answer_label&.downcase), category: :setup) + errors.add(field, I18n.t("validations.not_answered", question: question.error_display_label&.downcase), category: :setup) else - errors.add(field, I18n.t("validations.not_answered", question: question.check_answer_label&.downcase)) + errors.add(field, I18n.t("validations.not_answered", question: question.error_display_label&.downcase)) end end end diff --git a/app/services/bulk_upload/sales/year2023/row_parser.rb b/app/services/bulk_upload/sales/year2023/row_parser.rb index e0f2b727f..17ec939d4 100644 --- a/app/services/bulk_upload/sales/year2023/row_parser.rb +++ b/app/services/bulk_upload/sales/year2023/row_parser.rb @@ -1210,13 +1210,13 @@ private if setup_question?(question) fields.each do |field| unless errors.any? { |e| fields.include?(e.attribute) } - errors.add(field, I18n.t("validations.not_answered", question: question.check_answer_label&.downcase), category: :setup) + errors.add(field, I18n.t("validations.not_answered", question: question.error_display_label&.downcase), category: :setup) end end else fields.each do |field| unless errors.any? { |e| fields.include?(e.attribute) } - errors.add(field, I18n.t("validations.not_answered", question: question.check_answer_label&.downcase)) + errors.add(field, I18n.t("validations.not_answered", question: question.error_display_label&.downcase)) end end end diff --git a/spec/helpers/question_view_helper_spec.rb b/spec/helpers/question_view_helper_spec.rb index c24540709..2f7e82285 100644 --- a/spec/helpers/question_view_helper_spec.rb +++ b/spec/helpers/question_view_helper_spec.rb @@ -53,6 +53,10 @@ RSpec.describe QuestionViewHelper do def plain_label nil end + + def hide_question_number_on_page + false + end end end @@ -98,6 +102,10 @@ RSpec.describe QuestionViewHelper do def plain_label true end + + def hide_question_number_on_page + false + end end end diff --git a/spec/models/form/lettings/questions/address_line1_spec.rb b/spec/models/form/lettings/questions/address_line1_spec.rb index 2c13aef48..0fc91a586 100644 --- a/spec/models/form/lettings/questions/address_line1_spec.rb +++ b/spec/models/form/lettings/questions/address_line1_spec.rb @@ -19,12 +19,16 @@ RSpec.describe Form::Lettings::Questions::AddressLine1, type: :model do expect(question.header).to eq("Address line 1") end - it "has the correct question_number" do - expect(question.question_number).to be_nil + it "has the correct error label" do + expect(question.error_label).to eq("Address line 1") end it "has the correct check_answer_label" do - expect(question.check_answer_label).to eq("Q12 - Address") + expect(question.check_answer_label).to eq("Address lines 1 and 2") + end + + it "has the correct question_number" do + expect(question.question_number).to eq(12) end it "has the correct type" do @@ -46,34 +50,4 @@ RSpec.describe Form::Lettings::Questions::AddressLine1, type: :model do it "has the correct check_answers_card_number" do expect(question.check_answers_card_number).to be_nil end - - describe "has the correct get_extra_check_answer_value" do - context "when la is not present" do - let(:log) { create(:lettings_log, la: nil) } - - it "returns nil" do - expect(question.get_extra_check_answer_value(log)).to be_nil - end - end - - context "when la is present but not inferred" do - let(:log) { create(:lettings_log, la: "E09000003", is_la_inferred: false) } - - it "returns nil" do - expect(question.get_extra_check_answer_value(log)).to be_nil - end - end - - context "when la is present and inferred" do - let(:log) { create(:lettings_log, la: "E09000003") } - - before do - allow(log).to receive(:is_la_inferred?).and_return(true) - end - - it "returns the la" do - expect(question.get_extra_check_answer_value(log)).to eq("Barnet") - end - end - end end diff --git a/spec/models/form/lettings/questions/county_spec.rb b/spec/models/form/lettings/questions/county_spec.rb index cf8f814e4..1955dad8f 100644 --- a/spec/models/form/lettings/questions/county_spec.rb +++ b/spec/models/form/lettings/questions/county_spec.rb @@ -20,7 +20,11 @@ RSpec.describe Form::Lettings::Questions::County, type: :model do end it "has the correct check_answer_label" do - expect(question.check_answer_label).to be_nil + expect(question.check_answer_label).to eq("County") + end + + it "has the correct question_number" do + expect(question.question_number).to eq(12) end it "has the correct type" do @@ -42,8 +46,4 @@ RSpec.describe Form::Lettings::Questions::County, type: :model do it "has the correct check_answers_card_number" do expect(question.check_answers_card_number).to be_nil end - - it "has the correct hidden_in_check_answers" do - expect(question.hidden_in_check_answers?).to eq(true) - end end diff --git a/spec/models/form/lettings/questions/postcode_for_full_address_spec.rb b/spec/models/form/lettings/questions/postcode_for_full_address_spec.rb index ccb02ef07..337d1e6fe 100644 --- a/spec/models/form/lettings/questions/postcode_for_full_address_spec.rb +++ b/spec/models/form/lettings/questions/postcode_for_full_address_spec.rb @@ -20,7 +20,11 @@ RSpec.describe Form::Lettings::Questions::PostcodeForFullAddress, type: :model d end it "has the correct check_answer_label" do - expect(question.check_answer_label).to be_nil + expect(question.check_answer_label).to eq("Postcode") + end + + it "has the correct question_number" do + expect(question.question_number).to eq(12) end it "has the correct type" do @@ -55,8 +59,4 @@ RSpec.describe Form::Lettings::Questions::PostcodeForFullAddress, type: :model d "value" => "Not known", }]) end - - it "has the correct hidden_in_check_answers" do - expect(question.hidden_in_check_answers?).to eq(true) - end end diff --git a/spec/models/form/lettings/questions/town_or_city_spec.rb b/spec/models/form/lettings/questions/town_or_city_spec.rb index 8741fb058..a18d63c04 100644 --- a/spec/models/form/lettings/questions/town_or_city_spec.rb +++ b/spec/models/form/lettings/questions/town_or_city_spec.rb @@ -20,7 +20,11 @@ RSpec.describe Form::Lettings::Questions::TownOrCity, type: :model do end it "has the correct check_answer_label" do - expect(question.check_answer_label).to be_nil + expect(question.check_answer_label).to eq("Town or city") + end + + it "has the correct question_number" do + expect(question.question_number).to eq(12) end it "has the correct type" do @@ -42,8 +46,4 @@ RSpec.describe Form::Lettings::Questions::TownOrCity, type: :model do it "has the correct check_answers_card_number" do expect(question.check_answers_card_number).to be_nil end - - it "has the correct hidden_in_check_answers" do - expect(question.hidden_in_check_answers?).to eq(true) - end end diff --git a/spec/models/form/sales/questions/address_line1_spec.rb b/spec/models/form/sales/questions/address_line1_spec.rb index 8c73feef2..12643a27d 100644 --- a/spec/models/form/sales/questions/address_line1_spec.rb +++ b/spec/models/form/sales/questions/address_line1_spec.rb @@ -11,10 +11,6 @@ RSpec.describe Form::Sales::Questions::AddressLine1, type: :model do expect(question.page).to eq(page) end - it "has the correct question_number" do - expect(question.question_number).to be_nil - end - it "has the correct id" do expect(question.id).to eq("address_line1") end @@ -23,8 +19,16 @@ RSpec.describe Form::Sales::Questions::AddressLine1, type: :model do expect(question.header).to eq("Address line 1") end + it "has the correct error label" do + expect(question.error_label).to eq("Address line 1") + end + it "has the correct check_answer_label" do - expect(question.check_answer_label).to eq("Q15 - Address") + expect(question.check_answer_label).to eq("Address lines 1 and 2") + end + + it "has the correct question_number" do + expect(question.question_number).to eq(15) end it "has the correct type" do @@ -46,34 +50,4 @@ RSpec.describe Form::Sales::Questions::AddressLine1, type: :model do it "has the correct check_answers_card_number" do expect(question.check_answers_card_number).to be_nil end - - describe "has the correct get_extra_check_answer_value" do - context "when la is not present" do - let(:log) { create(:sales_log, la: nil) } - - it "returns nil" do - expect(question.get_extra_check_answer_value(log)).to be_nil - end - end - - context "when la is present but not inferred" do - let(:log) { create(:sales_log, la: "E09000003", is_la_inferred: false) } - - it "returns nil" do - expect(question.get_extra_check_answer_value(log)).to be_nil - end - end - - context "when la is present and inferred" do - let(:log) { create(:sales_log, la: "E09000003") } - - before do - allow(log).to receive(:is_la_inferred?).and_return(true) - end - - it "returns the la" do - expect(question.get_extra_check_answer_value(log)).to eq("Barnet") - end - end - end end diff --git a/spec/models/form/sales/questions/county_spec.rb b/spec/models/form/sales/questions/county_spec.rb index d5800b260..19bb59eb4 100644 --- a/spec/models/form/sales/questions/county_spec.rb +++ b/spec/models/form/sales/questions/county_spec.rb @@ -20,7 +20,11 @@ RSpec.describe Form::Sales::Questions::County, type: :model do end it "has the correct check_answer_label" do - expect(question.check_answer_label).to be_nil + expect(question.check_answer_label).to eq("County") + end + + it "has the correct question_number" do + expect(question.question_number).to eq(15) end it "has the correct type" do @@ -42,8 +46,4 @@ RSpec.describe Form::Sales::Questions::County, type: :model do it "has the correct check_answers_card_number" do expect(question.check_answers_card_number).to be_nil end - - it "has the correct hidden_in_check_answers" do - expect(question.hidden_in_check_answers?).to eq(true) - end end diff --git a/spec/models/form/sales/questions/postcode_for_full_address_spec.rb b/spec/models/form/sales/questions/postcode_for_full_address_spec.rb index a655172ec..a3c8ddfb8 100644 --- a/spec/models/form/sales/questions/postcode_for_full_address_spec.rb +++ b/spec/models/form/sales/questions/postcode_for_full_address_spec.rb @@ -20,7 +20,11 @@ RSpec.describe Form::Sales::Questions::PostcodeForFullAddress, type: :model do end it "has the correct check_answer_label" do - expect(question.check_answer_label).to be_nil + expect(question.check_answer_label).to eq("Postcode") + end + + it "has the correct question_number" do + expect(question.question_number).to eq(15) end it "has the correct type" do @@ -55,8 +59,4 @@ RSpec.describe Form::Sales::Questions::PostcodeForFullAddress, type: :model do "value" => "Not known", }]) end - - it "has the correct hidden_in_check_answers" do - expect(question.hidden_in_check_answers?).to eq(true) - end end diff --git a/spec/models/form/sales/questions/town_or_city_spec.rb b/spec/models/form/sales/questions/town_or_city_spec.rb index d66315864..df1c905b4 100644 --- a/spec/models/form/sales/questions/town_or_city_spec.rb +++ b/spec/models/form/sales/questions/town_or_city_spec.rb @@ -20,7 +20,11 @@ RSpec.describe Form::Sales::Questions::TownOrCity, type: :model do end it "has the correct check_answer_label" do - expect(question.check_answer_label).to be_nil + expect(question.check_answer_label).to eq("Town or city") + end + + it "has the correct question_number" do + expect(question.question_number).to eq(15) end it "has the correct type" do @@ -42,8 +46,4 @@ RSpec.describe Form::Sales::Questions::TownOrCity, type: :model do it "has the correct check_answers_card_number" do expect(question.check_answers_card_number).to be_nil end - - it "has the correct hidden_in_check_answers" do - expect(question.hidden_in_check_answers?).to eq(true) - 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 5bb6e8958..f7e5527a5 100644 --- a/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2023/row_parser_spec.rb @@ -340,7 +340,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do it "fetches the question's check_answer_label if it exists, otherwise it gets the question's header" do parser.valid? - expect(parser.errors[:field_19]).to eql(["You must answer q12 - address"]) + expect(parser.errors[:field_19]).to eql(["You must answer address line 1"]) expect(parser.errors[:field_21]).to eql(["You must answer town or city"]) end end @@ -937,7 +937,7 @@ RSpec.describe BulkUpload::Lettings::Year2023::RowParser do it "adds appropriate errors" do expect(parser.errors[:field_18]).to eql(["You must answer UPRN"]) - expect(parser.errors[:field_19]).to eql(["You must answer q12 - address"]) + expect(parser.errors[:field_19]).to eql(["You must answer address line 1"]) expect(parser.errors[:field_21]).to eql(["You must answer town or city"]) end end From b63a0693117bf356691406bb0f335df06830a498 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Mon, 19 Jun 2023 09:00:02 +0100 Subject: [PATCH 13/19] CLDC-2374 Update bulk upload guidance (#1703) * Update 23/24 lettings prepare your file guidance * Separate and update bulk upload sales guidance * Update shared guidance and sales file path * Rename old_template_path to legacy_template_path * Remove wrong bullet point * Update legacy template path based on year --- .../forms/bulk_upload_lettings/guidance.rb | 4 +-- .../bulk_upload_lettings/prepare_your_file.rb | 2 +- .../forms/bulk_upload_sales/guidance.rb | 4 +-- .../bulk_upload_sales/prepare_your_file.rb | 16 +++++++-- .../forms/prepare_your_file_2023.html.erb | 5 +-- ...ml.erb => prepare_your_file_2022.html.erb} | 3 +- .../forms/prepare_your_file_2023.html.erb | 36 +++++++++++++++++++ .../bulk_upload_shared/guidance.html.erb | 9 ++++- 8 files changed, 66 insertions(+), 13 deletions(-) rename app/views/bulk_upload_sales_logs/forms/{prepare_your_file.html.erb => prepare_your_file_2022.html.erb} (79%) create mode 100644 app/views/bulk_upload_sales_logs/forms/prepare_your_file_2023.html.erb diff --git a/app/models/forms/bulk_upload_lettings/guidance.rb b/app/models/forms/bulk_upload_lettings/guidance.rb index 55db10718..862a8af51 100644 --- a/app/models/forms/bulk_upload_lettings/guidance.rb +++ b/app/models/forms/bulk_upload_lettings/guidance.rb @@ -15,8 +15,8 @@ module Forms bulk_upload_lettings_log_path(id: "prepare-your-file", form: { year: }) end - def old_template_path - Forms::BulkUploadLettings::PrepareYourFile.new.old_template_path + def legacy_template_path + Forms::BulkUploadLettings::PrepareYourFile.new.legacy_template_path end def template_path 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 26032e614..601f09848 100644 --- a/app/models/forms/bulk_upload_lettings/prepare_your_file.rb +++ b/app/models/forms/bulk_upload_lettings/prepare_your_file.rb @@ -30,7 +30,7 @@ module Forms bulk_upload_lettings_log_path(id: page_id, form: { year:, needstype: }) end - def old_template_path + def legacy_template_path case year when 2022 "/files/bulk-upload-lettings-template-2022-23.xlsx" diff --git a/app/models/forms/bulk_upload_sales/guidance.rb b/app/models/forms/bulk_upload_sales/guidance.rb index c9869ee14..eb472ad3a 100644 --- a/app/models/forms/bulk_upload_sales/guidance.rb +++ b/app/models/forms/bulk_upload_sales/guidance.rb @@ -15,8 +15,8 @@ module Forms bulk_upload_sales_log_path(id: "prepare-your-file", form: { year: }) end - def old_template_path - Forms::BulkUploadLettings::PrepareYourFile.new.old_template_path + def legacy_template_path + Forms::BulkUploadSales::PrepareYourFile.new.legacy_template_path end def template_path 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 710cdbef3..9425a6662 100644 --- a/app/models/forms/bulk_upload_sales/prepare_your_file.rb +++ b/app/models/forms/bulk_upload_sales/prepare_your_file.rb @@ -8,7 +8,12 @@ module Forms attribute :year, :integer def view_path - "bulk_upload_sales_logs/forms/prepare_your_file" + case year + when 2022 + "bulk_upload_sales_logs/forms/prepare_your_file_2022" + else + "bulk_upload_sales_logs/forms/prepare_your_file_2023" + end end def back_path @@ -23,8 +28,13 @@ module Forms bulk_upload_sales_log_path(id: "upload-your-file", form: { year: }) end - def old_template_path - "/files/bulk-upload-sales-template-2022-23.xlsx" + def legacy_template_path + case year + when 2022 + "/files/bulk-upload-sales-template-2022-23.xlsx" + else + "/files/bulk-upload-sales-legacy-template-2023-24.xlsx" + end end def template_path diff --git a/app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2023.html.erb b/app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2023.html.erb index a1d25397b..f15972491 100644 --- a/app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2023.html.erb +++ b/app/views/bulk_upload_lettings_logs/forms/prepare_your_file_2023.html.erb @@ -12,13 +12,14 @@

Download template

+

Use one of these templates to upload logs for 2023/24:

diff --git a/app/views/bulk_upload_sales_logs/forms/prepare_your_file.html.erb b/app/views/bulk_upload_sales_logs/forms/prepare_your_file_2022.html.erb similarity index 79% rename from app/views/bulk_upload_sales_logs/forms/prepare_your_file.html.erb rename to app/views/bulk_upload_sales_logs/forms/prepare_your_file_2022.html.erb index 71e759306..624b3fe8b 100644 --- a/app/views/bulk_upload_sales_logs/forms/prepare_your_file.html.erb +++ b/app/views/bulk_upload_sales_logs/forms/prepare_your_file_2022.html.erb @@ -12,8 +12,7 @@

Download template

Create your file

diff --git a/app/views/bulk_upload_sales_logs/forms/prepare_your_file_2023.html.erb b/app/views/bulk_upload_sales_logs/forms/prepare_your_file_2023.html.erb new file mode 100644 index 000000000..7fc522e88 --- /dev/null +++ b/app/views/bulk_upload_sales_logs/forms/prepare_your_file_2023.html.erb @@ -0,0 +1,36 @@ +<% content_for :before_content do %> + <%= govuk_back_link href: @form.back_path %> +<% end %> + +
+
+ <%= form_with model: @form, scope: :form, url: bulk_upload_sales_log_path(id: "prepare-your-file"), method: :patch do |f| %> + <%= f.hidden_field :year %> + + Upload sales logs in bulk (<%= @form.year_combo %>) +

Prepare your file

+ +

Download template

+ + +

Create your file

+ + +

Save your file

+ + + <%= f.govuk_submit %> + <% end %> +
+
diff --git a/app/views/bulk_upload_shared/guidance.html.erb b/app/views/bulk_upload_shared/guidance.html.erb index 9d2147105..d2fdc9230 100644 --- a/app/views/bulk_upload_shared/guidance.html.erb +++ b/app/views/bulk_upload_shared/guidance.html.erb @@ -54,7 +54,14 @@

Getting help

-

There is no step-by-step bulk upload guide like there is with single log upload. However, you can download <%= govuk_link_to "our template", @form.template_path %>, which you can copy-paste data into from your systems column-by-column. You can also view a post-upload report showing any data errors, and our <%= govuk_link_to "data specification", @form.specification_path, target: "_blank" %> can help fix these.

+

There is no step-by-step bulk upload guide like there is with single log upload. However, you can download <%= @form.year == 2022 ? govuk_link_to("our template", @form.template_path) : "one of our templates" %>, which you can copy-paste data into from your systems column-by-column. You can also view a post-upload report showing any data errors, and our <%= govuk_link_to "data specification", @form.specification_path, target: "_blank" %> can help fix these.

+ <% if @form.year == 2023 %> + <%= govuk_details(summary_text: "How to choose the right template") do %> +

Use one of these templates to upload logs for 2023/24:

+

<%= govuk_link_to "New template", @form.template_path %>: In this template, the questions are in the same order as the 2023/24 paper form and web form. Use this template if your organisation is new to bulk upload or if your housing management system matches the new column ordering.

+

<%= govuk_link_to "Legacy template", @form.legacy_template_path %>: In this template, the questions are in the same order as the 2022/23 template, with new questions added on to the end. Use this template if you have not updated your system to match the new template yet.

+ <% end %> + <% end %>

If you still need support mapping data in the way we need, DLUHC’s helpdesk can help. If your data is across multiple systems, or is hard to export as a single file in the correct format, you could try different exports, or copy-pasting data by hand.

From 8c23cf6550225ab0149ef41bf104603b2c4654f2 Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Mon, 19 Jun 2023 09:52:10 +0100 Subject: [PATCH 14/19] CLDC-2420 Update deactivating soon label (#1701) * Display active status is location/scheme deactivation is in more than 6 months * Override existing location deactivation period with new deactivation * Override existing scheme deactivation period with new deactivation * add reactivate to policy * Change status tag method * Update instance double in test * Update deactivates_in_a_long_time? method * Uncoment a test --- app/controllers/locations_controller.rb | 8 ++- app/controllers/schemes_controller.rb | 8 ++- app/helpers/locations_helper.rb | 2 +- app/helpers/schemes_helper.rb | 4 +- app/helpers/tag_helper.rb | 6 ++ app/models/location.rb | 4 ++ app/models/location_deactivation_period.rb | 2 +- app/models/scheme.rb | 4 ++ app/models/scheme_deactivation_period.rb | 2 +- app/policies/scheme_policy.rb | 2 + app/views/locations/index.html.erb | 2 +- app/views/locations/show.html.erb | 2 +- app/views/schemes/_scheme_list.html.erb | 2 +- spec/requests/locations_controller_spec.rb | 80 ++++++++++++++++++++-- spec/requests/schemes_controller_spec.rb | 73 ++++++++++++++++++++ spec/views/locations/show.html.erb_spec.rb | 1 + 16 files changed, 186 insertions(+), 16 deletions(-) diff --git a/app/controllers/locations_controller.rb b/app/controllers/locations_controller.rb index 7ba4a1115..199c295dd 100644 --- a/app/controllers/locations_controller.rb +++ b/app/controllers/locations_controller.rb @@ -149,7 +149,11 @@ class LocationsController < ApplicationController def show; end def new_deactivation - @location_deactivation_period = LocationDeactivationPeriod.new + @location_deactivation_period = if @location.deactivates_in_a_long_time? + @location.open_deactivation || LocationDeactivationPeriod.new + else + LocationDeactivationPeriod.new + end if params[:location_deactivation_period].blank? render "toggle_active", locals: { action: "deactivate" } @@ -176,7 +180,7 @@ class LocationsController < ApplicationController end def deactivate - if @location.location_deactivation_periods.create!(deactivation_date: params[:deactivation_date]) + if @location.open_deactivation&.update!(deactivation_date: params[:deactivation_date]) || @location.location_deactivation_periods.create!(deactivation_date: params[:deactivation_date]) logs = reset_location_and_scheme_for_logs! flash[:notice] = deactivate_success_notice diff --git a/app/controllers/schemes_controller.rb b/app/controllers/schemes_controller.rb index 3a2be3909..5c2fe355f 100644 --- a/app/controllers/schemes_controller.rb +++ b/app/controllers/schemes_controller.rb @@ -27,7 +27,11 @@ class SchemesController < ApplicationController end def new_deactivation - @scheme_deactivation_period = SchemeDeactivationPeriod.new + @scheme_deactivation_period = if @scheme.deactivates_in_a_long_time? + @scheme.open_deactivation || SchemeDeactivationPeriod.new + else + SchemeDeactivationPeriod.new + end if params[:scheme_deactivation_period].blank? render "toggle_active", locals: { action: "deactivate" } @@ -54,7 +58,7 @@ class SchemesController < ApplicationController end def deactivate - if @scheme.scheme_deactivation_periods.create!(deactivation_date: params[:deactivation_date]) + if @scheme.open_deactivation&.update!(deactivation_date: params[:deactivation_date]) || @scheme.scheme_deactivation_periods.create!(deactivation_date: params[:deactivation_date]) logs = reset_location_and_scheme_for_logs! flash[:notice] = deactivate_success_notice diff --git a/app/helpers/locations_helper.rb b/app/helpers/locations_helper.rb index 58fffd2e0..619bf20d5 100644 --- a/app/helpers/locations_helper.rb +++ b/app/helpers/locations_helper.rb @@ -69,7 +69,7 @@ module LocationsHelper end def toggle_location_link(location) - return govuk_button_link_to "Deactivate this location", scheme_location_new_deactivation_path(location.scheme, location), warning: true if location.active? + return govuk_button_link_to "Deactivate this location", scheme_location_new_deactivation_path(location.scheme, location), warning: true if location.active? || location.deactivates_in_a_long_time? return govuk_button_link_to "Reactivate this location", scheme_location_new_reactivation_path(location.scheme, location) if location.deactivated? end diff --git a/app/helpers/schemes_helper.rb b/app/helpers/schemes_helper.rb index 50bd4efb2..7f801d8a6 100644 --- a/app/helpers/schemes_helper.rb +++ b/app/helpers/schemes_helper.rb @@ -14,7 +14,7 @@ module SchemesHelper { name: "Level of support given", value: scheme.support_type }, { name: "Intended length of stay", value: scheme.intended_stay }, { name: "Availability", value: scheme_availability(scheme) }, - { name: "Status", value: status_tag(scheme.status) }, + { name: "Status", value: status_tag_from_resource(scheme) }, ] if user.data_coordinator? @@ -40,7 +40,7 @@ module SchemesHelper end def toggle_scheme_link(scheme) - return govuk_button_link_to "Deactivate this scheme", scheme_new_deactivation_path(scheme), warning: true if scheme.active? + return govuk_button_link_to "Deactivate this scheme", scheme_new_deactivation_path(scheme), warning: true if scheme.active? || scheme.deactivates_in_a_long_time? return govuk_button_link_to "Reactivate this scheme", scheme_new_reactivation_path(scheme) if scheme.deactivated? end diff --git a/app/helpers/tag_helper.rb b/app/helpers/tag_helper.rb index f8b19dd83..074ef21ed 100644 --- a/app/helpers/tag_helper.rb +++ b/app/helpers/tag_helper.rb @@ -34,4 +34,10 @@ module TagHelper text: TEXT[status.to_sym], ) end + + def status_tag_from_resource(resource, classes = []) + status = resource.status + status = :active if resource.deactivates_in_a_long_time? + status_tag(status, classes) + end end diff --git a/app/models/location.rb b/app/models/location.rb index 57393d9fb..818ea5700 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -108,6 +108,10 @@ class Location < ApplicationRecord status == :reactivating_soon end + def deactivates_in_a_long_time? + status_at(6.months.from_now) == :deactivating_soon + end + def validate_postcode if !postcode&.match(POSTCODE_REGEXP) error_message = I18n.t("validations.postcode") diff --git a/app/models/location_deactivation_period.rb b/app/models/location_deactivation_period.rb index c9a24bdc9..be635a975 100644 --- a/app/models/location_deactivation_period.rb +++ b/app/models/location_deactivation_period.rb @@ -4,7 +4,7 @@ class LocationDeactivationPeriodValidator < ActiveModel::Validator def validate(record) location = record.location recent_deactivation = location.location_deactivation_periods.deactivations_without_reactivation.first - if recent_deactivation.present? + if recent_deactivation.present? && recent_deactivation.deactivation_date <= 6.months.from_now validate_reactivation(record, recent_deactivation, location) else validate_deactivation(record, location) diff --git a/app/models/scheme.rb b/app/models/scheme.rb index 75b37dec6..fdb5cf394 100644 --- a/app/models/scheme.rb +++ b/app/models/scheme.rb @@ -242,4 +242,8 @@ class Scheme < ApplicationRecord def deactivated? status == :deactivated end + + def deactivates_in_a_long_time? + status_at(6.months.from_now) == :deactivating_soon + end end diff --git a/app/models/scheme_deactivation_period.rb b/app/models/scheme_deactivation_period.rb index 01aafbcb4..e413bb6a9 100644 --- a/app/models/scheme_deactivation_period.rb +++ b/app/models/scheme_deactivation_period.rb @@ -4,7 +4,7 @@ class SchemeDeactivationPeriodValidator < ActiveModel::Validator def validate(record) scheme = record.scheme recent_deactivation = scheme.scheme_deactivation_periods.deactivations_without_reactivation.first - if recent_deactivation.present? + if recent_deactivation.present? && recent_deactivation.deactivation_date <= 6.months.from_now validate_reactivation(record, recent_deactivation, scheme) else validate_deactivation(record, scheme) diff --git a/app/policies/scheme_policy.rb b/app/policies/scheme_policy.rb index 58a4efb11..39842a160 100644 --- a/app/policies/scheme_policy.rb +++ b/app/policies/scheme_policy.rb @@ -47,7 +47,9 @@ class SchemePolicy confirm_secondary_client_group? secondary_client_group? new_deactivation? + new_reactivation? deactivate? + reactivate? details? support? deactivate_confirm? diff --git a/app/views/locations/index.html.erb b/app/views/locations/index.html.erb index e974d7dfb..7641bbd48 100644 --- a/app/views/locations/index.html.erb +++ b/app/views/locations/index.html.erb @@ -54,7 +54,7 @@ end), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %> <% row.cell(text: location.name) %> <% row.cell(text: location.id) %> - <% row.cell(text: status_tag(location.status)) %> + <% row.cell(text: status_tag_from_resource(location)) %> <% end %> <% end %> <% end %> diff --git a/app/views/locations/show.html.erb b/app/views/locations/show.html.erb index dd819b2fb..fcee0a175 100644 --- a/app/views/locations/show.html.erb +++ b/app/views/locations/show.html.erb @@ -15,7 +15,7 @@ <% display_location_attributes(@location).each do |attr| %> <%= summary_list.row do |row| %> <% row.key { attr[:name] } %> - <% row.value { attr[:attribute].eql?("status") ? status_tag(attr[:value]) : details_html(attr) } %> + <% row.value { attr[:attribute].eql?("status") ? status_tag_from_resource(@location) : details_html(attr) } %> <% if LocationPolicy.new(current_user, @location).update? %> <% row.action(text: "Change", href: scheme_location_name_path(@scheme, @location, referrer: "details")) if attr[:attribute] == "name" %> <% end %> diff --git a/app/views/schemes/_scheme_list.html.erb b/app/views/schemes/_scheme_list.html.erb index 4f3b06454..43e03cd65 100644 --- a/app/views/schemes/_scheme_list.html.erb +++ b/app/views/schemes/_scheme_list.html.erb @@ -17,7 +17,7 @@ <% row.cell(text: simple_format(scheme_cell(scheme), { class: "govuk-!-font-weight-bold" }, wrapper_tag: "div")) %> <% row.cell(text: scheme.id_to_display) %> <% row.cell(text: scheme.locations&.count) %> - <% row.cell(text: status_tag(scheme.status)) %> + <% row.cell(text: status_tag_from_resource(scheme)) %> <% end %> <% end %> <% end %> diff --git a/spec/requests/locations_controller_spec.rb b/spec/requests/locations_controller_spec.rb index 81b2e56b0..edc5dc452 100644 --- a/spec/requests/locations_controller_spec.rb +++ b/spec/requests/locations_controller_spec.rb @@ -1557,6 +1557,37 @@ RSpec.describe LocationsController, type: :request do expect(lettings_log.unresolved).to eq(nil) end end + + context "and there already is a deactivation period" do + let(:add_deactivations) { create(:location_deactivation_period, deactivation_date: Time.zone.local(2023, 6, 5), reactivation_date: nil, location:) } + + before do + patch "/schemes/#{scheme.id}/locations/#{location.id}/deactivate", params: + end + + it "updates existing location with valid deactivation date and renders location page" do + follow_redirect! + expect(response).to have_http_status(:ok) + expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success") + location.reload + expect(location.location_deactivation_periods.count).to eq(1) + expect(location.location_deactivation_periods.first.deactivation_date).to eq(deactivation_date) + end + + it "clears the location and scheme answers" do + expect(lettings_log.location).to eq(location) + expect(lettings_log.scheme).to eq(scheme) + lettings_log.reload + expect(lettings_log.location).to eq(nil) + expect(lettings_log.scheme).to eq(nil) + end + + it "marks log as needing attention" do + expect(lettings_log.unresolved).to eq(nil) + lettings_log.reload + expect(lettings_log.unresolved).to eq(true) + end + end end context "when the date is not selected" do @@ -1623,6 +1654,34 @@ RSpec.describe LocationsController, type: :request do expect(page).to have_content(I18n.t("validations.location.deactivation.during_deactivated_period")) end end + + context "when there is an earlier open deactivation" do + let(:deactivation_date) { Time.zone.local(2022, 10, 10) } + let(:params) { { location_deactivation_period: { deactivation_date_type: "other", "deactivation_date(3i)": "8", "deactivation_date(2i)": "9", "deactivation_date(1i)": "2023" } } } + let(:add_deactivations) { create(:location_deactivation_period, deactivation_date: Time.zone.local(2023, 6, 5), reactivation_date: nil, location:) } + + it "redirects to the location page and updates the existing deactivation period" do + follow_redirect! + follow_redirect! + expect(response).to have_http_status(:ok) + expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success") + location.reload + expect(location.location_deactivation_periods.count).to eq(1) + expect(location.location_deactivation_periods.first.deactivation_date).to eq(Time.zone.local(2023, 9, 8)) + end + end + + context "when there is a later open deactivation" do + let(:deactivation_date) { Time.zone.local(2022, 10, 10) } + let(:params) { { location_deactivation_period: { deactivation_date_type: "other", "deactivation_date(3i)": "8", "deactivation_date(2i)": "9", "deactivation_date(1i)": "2022" } } } + let(:add_deactivations) { create(:location_deactivation_period, deactivation_date: Time.zone.local(2023, 6, 5), reactivation_date: nil, location:) } + + it "redirects to the confirmation page" do + follow_redirect! + expect(response).to have_http_status(:ok) + expect(page).to have_content("This change will affect 1 logs") + end + end end end @@ -1692,6 +1751,19 @@ RSpec.describe LocationsController, type: :request do expect(response).to have_http_status(:ok) expect(page).not_to have_link("Reactivate this location") expect(page).not_to have_link("Deactivate this location") + expect(page).to have_content("Deactivating soon") + end + end + + context "with location that's deactivating in more than 6 months" do + let(:location_deactivation_period) { create(:location_deactivation_period, deactivation_date: Time.zone.local(2023, 6, 12), location:) } + + it "does render toggle location link" do + expect(response).to have_http_status(:ok) + expect(page).not_to have_link("Reactivate this location") + expect(page).to have_link("Deactivate this location") + expect(response.body).not_to include("Deactivating soon") + expect(response.body).to include("Active") end end @@ -1758,10 +1830,10 @@ RSpec.describe LocationsController, type: :request do let(:scheme) { create(:scheme, owning_organisation: user.organisation) } let(:location) { create(:location, scheme:) } let(:deactivation_date) { Time.zone.local(2022, 4, 1) } - let(:startdate) { Time.utc(2022, 10, 11) } + let(:startdate) { Time.utc(2022, 9, 11) } before do - Timecop.freeze(Time.utc(2022, 10, 10)) + Timecop.freeze(Time.utc(2022, 9, 10)) sign_in user create(:location_deactivation_period, deactivation_date:, location:) location.save! @@ -1792,7 +1864,7 @@ RSpec.describe LocationsController, type: :request do end context "with other date" do - let(:params) { { location_deactivation_period: { reactivation_date_type: "other", "reactivation_date(3i)": "10", "reactivation_date(2i)": "10", "reactivation_date(1i)": "2022" } } } + let(:params) { { location_deactivation_period: { reactivation_date_type: "other", "reactivation_date(3i)": "10", "reactivation_date(2i)": "9", "reactivation_date(1i)": "2022" } } } it "redirects to the location page and displays a success banner" do expect(response).to redirect_to("/schemes/#{scheme.id}/locations/#{location.id}") @@ -1805,7 +1877,7 @@ RSpec.describe LocationsController, type: :request do follow_redirect! location.reload expect(location.location_deactivation_periods.count).to eq(1) - expect(location.location_deactivation_periods.first.reactivation_date).to eq(Time.zone.local(2022, 10, 10)) + expect(location.location_deactivation_periods.first.reactivation_date).to eq(Time.zone.local(2022, 9, 10)) end end diff --git a/spec/requests/schemes_controller_spec.rb b/spec/requests/schemes_controller_spec.rb index 18ee51756..b68e31b06 100644 --- a/spec/requests/schemes_controller_spec.rb +++ b/spec/requests/schemes_controller_spec.rb @@ -330,6 +330,18 @@ RSpec.describe SchemesController, type: :request do expect(page).not_to have_link("Deactivate this scheme") end end + + context "with scheme that's deactivating in more than 6 months" do + let(:scheme_deactivation_period) { create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2023, 5, 12), scheme:) } + + it "does not render toggle scheme link" do + expect(response).to have_http_status(:ok) + expect(page).not_to have_link("Reactivate this scheme") + expect(page).to have_link("Deactivate this scheme") + expect(response.body).not_to include("Deactivating soon") + expect(response.body).to include("Active") + end + end end context "when coordinator attempts to see scheme belonging to a parent organisation" do @@ -1999,6 +2011,38 @@ RSpec.describe SchemesController, type: :request do end end + context "and there already is a deactivation period" do + let(:add_deactivations) { create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2023, 6, 5), reactivation_date: nil, scheme:) } + + before do + create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2023, 6, 5), reactivation_date: nil, scheme:) + patch "/schemes/#{scheme.id}/deactivate", params: + end + + it "updates existing scheme with valid deactivation date and renders scheme page" do + follow_redirect! + follow_redirect! + expect(response).to have_http_status(:ok) + expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success") + scheme.reload + expect(scheme.scheme_deactivation_periods.count).to eq(1) + expect(scheme.scheme_deactivation_periods.first.deactivation_date).to eq(deactivation_date) + end + + it "clears the scheme and scheme answers" do + expect(lettings_log.scheme).to eq(scheme) + lettings_log.reload + expect(lettings_log.scheme).to eq(nil) + expect(lettings_log.scheme).to eq(nil) + end + + it "marks log as needing attention" do + expect(lettings_log.unresolved).to eq(nil) + lettings_log.reload + expect(lettings_log.unresolved).to eq(true) + end + end + context "and the users need to be notified" do it "sends E-mails to the creators of affected logs with counts" do expect { @@ -2061,6 +2105,35 @@ RSpec.describe SchemesController, type: :request do expect(page).to have_content(I18n.t("validations.scheme.toggle_date.invalid")) end end + + context "when there is an earlier open deactivation" do + let(:deactivation_date) { Time.zone.local(2022, 10, 10) } + let(:params) { { scheme_deactivation_period: { deactivation_date_type: "other", "deactivation_date(3i)": "8", "deactivation_date(2i)": "9", "deactivation_date(1i)": "2023" } } } + let(:add_deactivations) { create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2023, 6, 5), reactivation_date: nil, scheme:) } + + it "redirects to the scheme page and updates the existing deactivation period" do + follow_redirect! + follow_redirect! + follow_redirect! + expect(response).to have_http_status(:ok) + expect(page).to have_css(".govuk-notification-banner.govuk-notification-banner--success") + scheme.reload + expect(scheme.scheme_deactivation_periods.count).to eq(1) + expect(scheme.scheme_deactivation_periods.first.deactivation_date).to eq(Time.zone.local(2023, 9, 8)) + end + end + + context "when there is a later open deactivation" do + let(:deactivation_date) { Time.zone.local(2022, 10, 10) } + let(:params) { { scheme_deactivation_period: { deactivation_date_type: "other", "deactivation_date(3i)": "8", "deactivation_date(2i)": "9", "deactivation_date(1i)": "2022" } } } + let(:add_deactivations) { create(:scheme_deactivation_period, deactivation_date: Time.zone.local(2023, 6, 5), reactivation_date: nil, scheme:) } + + it "redirects to the confirmation page" do + follow_redirect! + expect(response).to have_http_status(:ok) + expect(page).to have_content("This change will affect 1 logs") + end + end end end end diff --git a/spec/views/locations/show.html.erb_spec.rb b/spec/views/locations/show.html.erb_spec.rb index ad8540eeb..65c6c07e2 100644 --- a/spec/views/locations/show.html.erb_spec.rb +++ b/spec/views/locations/show.html.erb_spec.rb @@ -40,6 +40,7 @@ RSpec.describe "locations/show.html.erb" do status: :active, active?: true, scheme:, + deactivates_in_a_long_time?: false, ) end From 963a09f469c50c7409fbabada82112e5d629ba9a Mon Sep 17 00:00:00 2001 From: Arthur Campbell <51094020+arfacamble@users.noreply.github.com> Date: Mon, 19 Jun 2023 11:18:59 +0100 Subject: [PATCH 15/19] CLDC-2421 explain why schemes without locations are incomplete (#1704) * amend ordering in scheme and location show pages to raise status towards top implement a muted explanation text on schemes when they have complete details but no active locations to explain why they are incomplete write tests to ensure this text is shown under the right conditions * update ordering in spec files * correct rebase change --- app/helpers/locations_helper.rb | 2 +- app/helpers/schemes_helper.rb | 2 +- app/views/schemes/show.html.erb | 7 ++++++- spec/helpers/locations_helper_spec.rb | 6 +++--- spec/helpers/schemes_helper_spec.rb | 8 ++++---- spec/requests/schemes_controller_spec.rb | 17 +++++++++++++++++ 6 files changed, 32 insertions(+), 10 deletions(-) diff --git a/app/helpers/locations_helper.rb b/app/helpers/locations_helper.rb index 619bf20d5..235029e9d 100644 --- a/app/helpers/locations_helper.rb +++ b/app/helpers/locations_helper.rb @@ -27,13 +27,13 @@ module LocationsHelper [ { name: "Postcode", value: location.postcode, attribute: "postcode" }, { name: "Location name", value: location.name, attribute: "name" }, + { name: "Status", value: location.status, attribute: "status" }, { name: "Local authority", value: formatted_local_authority_timeline(location, "name"), attribute: "local_authority" }, { name: "Number of units", value: location.units, attribute: "units" }, { name: "Most common unit", value: location.type_of_unit, attribute: "type_of_unit" }, { name: "Mobility standards", value: location.mobility_type, attribute: "mobility_standards" }, { name: "Location code", value: formatted_local_authority_timeline(location, "code"), attribute: "location_code" }, { name: "Availability", value: location_availability(location), attribute: "availability" }, - { name: "Status", value: location.status, attribute: "status" }, ] end diff --git a/app/helpers/schemes_helper.rb b/app/helpers/schemes_helper.rb index 7f801d8a6..f7da6d3f7 100644 --- a/app/helpers/schemes_helper.rb +++ b/app/helpers/schemes_helper.rb @@ -3,6 +3,7 @@ module SchemesHelper base_attributes = [ { name: "Scheme code", value: scheme.id_to_display }, { name: "Name", value: scheme.service_name, edit: true }, + { name: "Status", value: status_tag_from_resource(scheme) }, { name: "Confidential information", value: scheme.sensitive, edit: true }, { name: "Type of scheme", value: scheme.scheme_type }, { name: "Registered under Care Standards Act 2000", value: scheme.registered_under_care_act }, @@ -14,7 +15,6 @@ module SchemesHelper { name: "Level of support given", value: scheme.support_type }, { name: "Intended length of stay", value: scheme.intended_stay }, { name: "Availability", value: scheme_availability(scheme) }, - { name: "Status", value: status_tag_from_resource(scheme) }, ] if user.data_coordinator? diff --git a/app/views/schemes/show.html.erb b/app/views/schemes/show.html.erb index 4fe3a65de..66209fa28 100644 --- a/app/views/schemes/show.html.erb +++ b/app/views/schemes/show.html.erb @@ -19,7 +19,12 @@ <% display_scheme_attributes(@scheme, current_user).each do |attr| %> <%= summary_list.row do |row| %> <% row.key { attr[:name] } %> - <% row.value { details_html(attr) } %> + <% row.value do %> + <%= details_html(attr) %> + <% if attr[:name] == "Status" && @scheme.confirmed? && @scheme.locations.confirmed.none? %> + Add a location to complete this scheme + <% end %> + <% end %> <% if SchemePolicy.new(current_user, @scheme).update? %> <% row.action(text: "Change", href: scheme_edit_name_path(scheme_id: @scheme.id)) if attr[:edit] %> <% end %> diff --git a/spec/helpers/locations_helper_spec.rb b/spec/helpers/locations_helper_spec.rb index 354b948d4..368833bd2 100644 --- a/spec/helpers/locations_helper_spec.rb +++ b/spec/helpers/locations_helper_spec.rb @@ -140,13 +140,13 @@ RSpec.describe LocationsHelper do attributes = [ { attribute: "postcode", name: "Postcode", value: location.postcode }, { attribute: "name", name: "Location name", value: location.name }, + { attribute: "status", name: "Status", value: :active }, { attribute: "local_authority", name: "Local authority", value: location.location_admin_district }, { attribute: "units", name: "Number of units", value: location.units }, { attribute: "type_of_unit", name: "Most common unit", value: location.type_of_unit }, { attribute: "mobility_standards", name: "Mobility standards", value: location.mobility_type }, { attribute: "location_code", name: "Location code", value: location.location_code }, { attribute: "availability", name: "Availability", value: "Active from 1 April 2022" }, - { attribute: "status", name: "Status", value: :active }, ] expect(display_location_attributes(location)).to eq(attributes) @@ -162,13 +162,13 @@ RSpec.describe LocationsHelper do attributes = [ { attribute: "postcode", name: "Postcode", value: location.postcode }, { attribute: "name", name: "Location name", value: location.name }, + { attribute: "status", name: "Status", value: :active }, { attribute: "local_authority", name: "Local authority", value: "Eden (until 31 March 2023)\nCumberland (1 April 2023 - present)" }, { attribute: "units", name: "Number of units", value: location.units }, { attribute: "type_of_unit", name: "Most common unit", value: location.type_of_unit }, { attribute: "mobility_standards", name: "Mobility standards", value: location.mobility_type }, { attribute: "location_code", name: "Location code", value: "E07000030 (until 31 March 2023)\nE06000063 (1 April 2023 - present)" }, { attribute: "availability", name: "Availability", value: "Active from 1 April 2022" }, - { attribute: "status", name: "Status", value: :active }, ] expect(display_location_attributes(location)).to eq(attributes) @@ -185,13 +185,13 @@ RSpec.describe LocationsHelper do attributes = [ { attribute: "postcode", name: "Postcode", value: location.postcode }, { attribute: "name", name: "Location name", value: location.name }, + { attribute: "status", name: "Status", value: :incomplete }, { attribute: "local_authority", name: "Local authority", value: "" }, { attribute: "units", name: "Number of units", value: location.units }, { attribute: "type_of_unit", name: "Most common unit", value: location.type_of_unit }, { attribute: "mobility_standards", name: "Mobility standards", value: location.mobility_type }, { attribute: "location_code", name: "Location code", value: "" }, { attribute: "availability", name: "Availability", value: "Active from 1 April 2022" }, - { attribute: "status", name: "Status", value: :incomplete }, ] expect(display_location_attributes(location)).to eq(attributes) diff --git a/spec/helpers/schemes_helper_spec.rb b/spec/helpers/schemes_helper_spec.rb index 905494ac8..c2350d428 100644 --- a/spec/helpers/schemes_helper_spec.rb +++ b/spec/helpers/schemes_helper_spec.rb @@ -115,6 +115,7 @@ RSpec.describe SchemesHelper do attributes = [ { name: "Scheme code", value: "S#{scheme.id}" }, { name: "Name", value: "Test service_name", edit: true }, + { name: "Status", value: status_tag(:incomplete) }, { name: "Confidential information", value: "No", edit: true }, { name: "Type of scheme", value: "Housing for older people" }, { name: "Registered under Care Standards Act 2000", value: "Yes – registered care home providing personal care" }, @@ -126,7 +127,6 @@ RSpec.describe SchemesHelper do { name: "Level of support given", value: "High level" }, { name: "Intended length of stay", value: "Permanent" }, { name: "Availability", value: "Active from 1 April 2021" }, - { name: "Status", value: status_tag(:incomplete) }, ] expect(display_scheme_attributes(scheme, support_user)).to eq(attributes) end @@ -135,6 +135,7 @@ RSpec.describe SchemesHelper do attributes = [ { name: "Scheme code", value: "S#{scheme.id}" }, { name: "Name", value: "Test service_name", edit: true }, + { name: "Status", value: status_tag(:incomplete) }, { name: "Confidential information", value: "No", edit: true }, { name: "Type of scheme", value: "Housing for older people" }, { name: "Registered under Care Standards Act 2000", value: "Yes – registered care home providing personal care" }, @@ -145,7 +146,6 @@ RSpec.describe SchemesHelper do { name: "Level of support given", value: "High level" }, { name: "Intended length of stay", value: "Permanent" }, { name: "Availability", value: "Active from 1 April 2021" }, - { name: "Status", value: status_tag(:incomplete) }, ] expect(display_scheme_attributes(scheme, coordinator_user)).to eq(attributes) end @@ -160,6 +160,7 @@ RSpec.describe SchemesHelper do attributes = [ { name: "Scheme code", value: "S#{scheme.id}" }, { name: "Name", value: "Test service_name", edit: true }, + { name: "Status", value: status_tag(:active) }, { name: "Confidential information", value: "No", edit: true }, { name: "Type of scheme", value: "Housing for older people" }, { name: "Registered under Care Standards Act 2000", value: "Yes – registered care home providing personal care" }, @@ -171,7 +172,6 @@ RSpec.describe SchemesHelper do { name: "Level of support given", value: "High level" }, { name: "Intended length of stay", value: "Permanent" }, { name: "Availability", value: "Active from 1 April 2021" }, - { name: "Status", value: status_tag(:active) }, ] expect(display_scheme_attributes(scheme, support_user)).to eq(attributes) end @@ -180,6 +180,7 @@ RSpec.describe SchemesHelper do attributes = [ { name: "Scheme code", value: "S#{scheme.id}" }, { name: "Name", value: "Test service_name", edit: true }, + { name: "Status", value: status_tag(:active) }, { name: "Confidential information", value: "No", edit: true }, { name: "Type of scheme", value: "Housing for older people" }, { name: "Registered under Care Standards Act 2000", value: "Yes – registered care home providing personal care" }, @@ -190,7 +191,6 @@ RSpec.describe SchemesHelper do { name: "Level of support given", value: "High level" }, { name: "Intended length of stay", value: "Permanent" }, { name: "Availability", value: "Active from 1 April 2021" }, - { name: "Status", value: status_tag(:active) }, ] expect(display_scheme_attributes(scheme, coordinator_user)).to eq(attributes) end diff --git a/spec/requests/schemes_controller_spec.rb b/spec/requests/schemes_controller_spec.rb index b68e31b06..754d8cb22 100644 --- a/spec/requests/schemes_controller_spec.rb +++ b/spec/requests/schemes_controller_spec.rb @@ -364,6 +364,23 @@ RSpec.describe SchemesController, type: :request do expect(page).not_to have_content("Deactivate this scheme") end end + + context "when the scheme has all details but no confirmed locations" do + it "shows the scheme as incomplete with text to explain" do + get scheme_path(specific_scheme) + expect(page).to have_content "Incomplete" + expect(page).to have_content "Add a location to complete this scheme" + end + end + + context "when the scheme has all details and confirmed locations" do + it "shows the scheme as complete" do + create(:location, scheme: specific_scheme) + get scheme_path(specific_scheme) + expect(page).to have_content "Active" + expect(page).not_to have_content "Add a location to complete this scheme" + end + end end context "when signed in as a support user" do From d0ab30ce8b637a9d26e03c868cfc29ce59752b1e Mon Sep 17 00:00:00 2001 From: kosiakkatrina <54268893+kosiakkatrina@users.noreply.github.com> Date: Mon, 19 Jun 2023 14:45:18 +0100 Subject: [PATCH 16/19] CLDC-1783 Add telephone number question to the user form (#1706) * Add telephone number question to the user form * Extract user policy --- app/controllers/users_controller.rb | 16 +++- app/helpers/user_helper.rb | 24 ------ app/policies/user_policy.rb | 36 +++++++++ app/views/users/edit.html.erb | 5 ++ app/views/users/new.html.erb | 6 ++ app/views/users/show.html.erb | 22 ++++-- config/locales/en.yml | 2 + spec/features/user_spec.rb | 26 +++++++ spec/helpers/user_helper_spec.rb | 99 ------------------------ spec/policies/user_policy_spec.rb | 103 +++++++++++++++++++++++++ spec/requests/users_controller_spec.rb | 7 ++ 11 files changed, 213 insertions(+), 133 deletions(-) create mode 100644 app/policies/user_policy.rb create mode 100644 spec/policies/user_policy_spec.rb diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 9e6491554..3f91fa659 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -120,6 +120,14 @@ private if user_params[:role].present? && !current_user.assignable_roles.key?(user_params[:role].to_sym) @resource.errors.add :role, I18n.t("validations.role.invalid") end + + if user_params[:phone].present? && !valid_phone_number?(user_params[:phone]) + @resource.errors.add :phone + end + end + + def valid_phone_number?(number) + number.to_i.to_s == number && number.length >= 11 end def format_error_messages @@ -151,14 +159,14 @@ private def user_params if @user == current_user if current_user.data_coordinator? || current_user.support? - params.require(:user).permit(:email, :name, :password, :password_confirmation, :role, :is_dpo, :is_key_contact, :initial_confirmation_sent) + params.require(:user).permit(:email, :phone, :name, :password, :password_confirmation, :role, :is_dpo, :is_key_contact, :initial_confirmation_sent) else - params.require(:user).permit(:email, :name, :password, :password_confirmation, :initial_confirmation_sent) + params.require(:user).permit(:email, :phone, :name, :password, :password_confirmation, :initial_confirmation_sent) end elsif current_user.data_coordinator? - params.require(:user).permit(:email, :name, :role, :is_dpo, :is_key_contact, :active, :initial_confirmation_sent) + params.require(:user).permit(:email, :phone, :name, :role, :is_dpo, :is_key_contact, :active, :initial_confirmation_sent) elsif current_user.support? - params.require(:user).permit(:email, :name, :role, :is_dpo, :is_key_contact, :organisation_id, :active, :initial_confirmation_sent) + params.require(:user).permit(:email, :phone, :name, :role, :is_dpo, :is_key_contact, :organisation_id, :active, :initial_confirmation_sent) end end diff --git a/app/helpers/user_helper.rb b/app/helpers/user_helper.rb index 957e2dc2c..76fb78f57 100644 --- a/app/helpers/user_helper.rb +++ b/app/helpers/user_helper.rb @@ -7,30 +7,6 @@ module UserHelper current_user == user ? "Are you" : "Is this person" end - def can_edit_names?(user, current_user) - (current_user == user || current_user.data_coordinator? || current_user.support?) && user.active? - end - - def can_edit_emails?(user, current_user) - (current_user == user || current_user.data_coordinator? || current_user.support?) && user.active? - end - - def can_edit_password?(user, current_user) - current_user == user - end - - def can_edit_roles?(user, current_user) - (current_user.data_coordinator? || current_user.support?) && user.active? - end - - def can_edit_dpo?(user, current_user) - (current_user.data_coordinator? || current_user.support?) && user.active? - end - - def can_edit_key_contact?(user, current_user) - (current_user.data_coordinator? || current_user.support?) && user.active? - end - def can_edit_org?(current_user) current_user.data_coordinator? || current_user.support? end diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb new file mode 100644 index 000000000..a4b1a3d5c --- /dev/null +++ b/app/policies/user_policy.rb @@ -0,0 +1,36 @@ +class UserPolicy + attr_reader :current_user, :user + + def initialize(current_user, user) + @current_user = current_user + @user = user + end + + def edit_password? + @current_user == @user + end + + def edit_roles? + (@current_user.data_coordinator? || @current_user.support?) && @user.active? + end + + %w[ + edit_roles? + edit_dpo? + edit_key_contact? + ].each do |method_name| + define_method method_name do + (@current_user.data_coordinator? || @current_user.support?) && @user.active? + end + end + + %w[ + edit_emails? + edit_telephone_numbers? + edit_names? + ].each do |method_name| + define_method method_name do + (@current_user == @user || @current_user.data_coordinator? || @current_user.support?) && @user.active? + end + end +end diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb index fb7b10755..3f7259bc3 100644 --- a/app/views/users/edit.html.erb +++ b/app/views/users/edit.html.erb @@ -22,6 +22,11 @@ autocomplete: "email", spellcheck: "false" %> + <%= f.govuk_phone_field :phone, + label: { text: "Telephone number", size: "m" }, + autocomplete: "phone", + spellcheck: "false" %> + <% if current_user.data_coordinator? || current_user.support? %> <% roles = current_user.assignable_roles.map { |key, _| OpenStruct.new(id: key, name: key.to_s.humanize) } %> diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb index b3311bcfb..fc66bcad8 100644 --- a/app/views/users/new.html.erb +++ b/app/views/users/new.html.erb @@ -23,6 +23,12 @@ spellcheck: "false", value: @resource.email %> + <%= f.govuk_phone_field :phone, + label: { text: "Telephone number", size: "m" }, + autocomplete: "phone", + spellcheck: "false", + value: @resource.phone %> + <% if current_user.support? %> <% null_option = [OpenStruct.new(id: "", name: "Select an option")] %> <% organisations = Organisation.all.map { |org| OpenStruct.new(id: org.id, name: org.name) } %> diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 845e6ebba..844a0c965 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -13,7 +13,7 @@ <%= summary_list.row do |row| row.key { "Name" } row.value { @user.name } - if can_edit_names?(@user, current_user) + if UserPolicy.new(current_user, @user).edit_names? row.action(visually_hidden_text: "name", href: aliased_user_edit(@user, current_user), html_attributes: { "data-qa": "change-name" }) else row.action @@ -23,17 +23,27 @@ <%= summary_list.row do |row| row.key { "Email address" } row.value { @user.email } - if can_edit_emails?(@user, current_user) + if UserPolicy.new(current_user, @user).edit_emails? row.action(visually_hidden_text: "email address", href: aliased_user_edit(@user, current_user), html_attributes: { "data-qa": "change-email-address" }) else row.action end end %> + <%= summary_list.row do |row| + row.key { "Telephone number" } + row.value { @user.phone } + if UserPolicy.new(current_user, @user).edit_telephone_numbers? + row.action(visually_hidden_text: "telephone number", href: aliased_user_edit(@user, current_user), html_attributes: { "data-qa": "change-telephone-number" }) + else + row.action + end + end %> + <%= summary_list.row do |row| row.key { "Password" } row.value { "••••••••" } - if can_edit_password?(@user, current_user) + if UserPolicy.new(current_user, @user).edit_password? row.action( visually_hidden_text: "password", href: edit_password_account_path, @@ -53,7 +63,7 @@ <%= summary_list.row do |row| row.key { "Role" } row.value { @user.role&.humanize } - if can_edit_roles?(@user, current_user) + if UserPolicy.new(current_user, @user).edit_roles? row.action( visually_hidden_text: "role", href: aliased_user_edit(@user, current_user), @@ -67,7 +77,7 @@ <%= summary_list.row do |row| row.key { "Data protection officer" } row.value { @user.is_data_protection_officer? ? "Yes" : "No" } - if can_edit_dpo?(@user, current_user) + if UserPolicy.new(current_user, @user).edit_dpo? row.action( visually_hidden_text: "if data protection officer", href: user_edit_dpo_path(@user), @@ -81,7 +91,7 @@ <%= summary_list.row do |row| row.key { "Key contact" } row.value { @user.is_key_contact? ? "Yes" : "No" } - if can_edit_key_contact?(@user, current_user) + if UserPolicy.new(current_user, @user).edit_key_contact? row.action( visually_hidden_text: "if a key contact", href: user_edit_key_contact_path(@user), diff --git a/config/locales/en.yml b/config/locales/en.yml index 59fa8ef32..73cf91d0a 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -154,6 +154,8 @@ en: 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" role: invalid: "Role must be data accessor, data provider or data coordinator" blank: "Select role" diff --git a/spec/features/user_spec.rb b/spec/features/user_spec.rb index d4e1a6052..222d2ff04 100644 --- a/spec/features/user_spec.rb +++ b/spec/features/user_spec.rb @@ -322,16 +322,42 @@ RSpec.describe "User Features" do expect(page).to have_title("Error") end + it "validates telephone number is numeric" do + visit("users/new") + fill_in("user[name]", with: "New User") + fill_in("user[email]", with: "newuser@example.com") + fill_in("user[phone]", with: "randomstring") + click_button("Continue") + expect(page).to have_selector("#error-summary-title") + expect(page).to have_selector("#user-phone-field-error") + expect(page).to have_content(/Enter a telephone number in the correct format/) + expect(page).to have_title("Error") + end + + it "validates telephone number is longer than 11 digits" do + visit("users/new") + fill_in("user[name]", with: "New User") + fill_in("user[email]", with: "newuser@example.com") + fill_in("user[phone]", with: "123") + click_button("Continue") + expect(page).to have_selector("#error-summary-title") + expect(page).to have_selector("#user-phone-field-error") + expect(page).to have_content(/Enter a telephone number in the correct format/) + expect(page).to have_title("Error") + end + it "sets name, email, role, is_dpo and is_key_contact fields" do visit("users/new") fill_in("user[name]", with: "New User") fill_in("user[email]", with: "newuser@example.com") + fill_in("user[phone]", with: "12345678910") choose("user-role-data-provider-field") click_button("Continue") expect(User.find_by( name: "New User", email: "newuser@example.com", role: "data_provider", + phone: "12345678910", is_dpo: false, is_key_contact: false, )).to be_a(User) diff --git a/spec/helpers/user_helper_spec.rb b/spec/helpers/user_helper_spec.rb index e49c7d6a2..829195f6c 100644 --- a/spec/helpers/user_helper_spec.rb +++ b/spec/helpers/user_helper_spec.rb @@ -37,105 +37,6 @@ RSpec.describe UserHelper do end describe "change button permissions" do - context "when the user is a data provider viewing their own details" do - let(:current_user) { FactoryBot.create(:user, :data_provider) } - let(:user) { current_user } - - it "allows changing name" do - expect(can_edit_names?(user, current_user)).to be true - end - - it "allows changing email" do - expect(can_edit_emails?(user, current_user)).to be true - end - - it "allows changing password" do - expect(can_edit_password?(user, current_user)).to be true - end - - it "does not allow changing roles" do - expect(can_edit_roles?(user, current_user)).to be false - end - - it "does not allow changing dpo" do - expect(can_edit_dpo?(user, current_user)).to be false - end - - it "does not allow changing key contact" do - expect(can_edit_key_contact?(user, current_user)).to be false - end - end - - context "when the user is a data coordinator viewing another user's details" do - it "allows changing name" do - expect(can_edit_names?(user, current_user)).to be true - end - - it "allows changing email" do - expect(can_edit_emails?(user, current_user)).to be true - end - - it "allows changing password" do - expect(can_edit_password?(user, current_user)).to be false - end - - it "does not allow changing roles" do - expect(can_edit_roles?(user, current_user)).to be true - end - - it "does not allow changing dpo" do - expect(can_edit_dpo?(user, current_user)).to be true - end - - it "does not allow changing key contact" do - expect(can_edit_key_contact?(user, current_user)).to be true - end - - context "when the user is a data coordinator viewing their own details" do - let(:user) { current_user } - - it "allows changing password" do - expect(can_edit_password?(user, current_user)).to be true - end - end - end - - context "when the user is a support user viewing another user's details" do - let(:current_user) { FactoryBot.create(:user, :support) } - - it "allows changing name" do - expect(can_edit_names?(user, current_user)).to be true - end - - it "allows changing email" do - expect(can_edit_emails?(user, current_user)).to be true - end - - it "allows changing password" do - expect(can_edit_password?(user, current_user)).to be false - end - - it "does not allow changing roles" do - expect(can_edit_roles?(user, current_user)).to be true - end - - it "does not allow changing dpo" do - expect(can_edit_dpo?(user, current_user)).to be true - end - - it "does not allow changing key contact" do - expect(can_edit_key_contact?(user, current_user)).to be true - end - - context "when the user is a support user viewing their own details" do - let(:user) { current_user } - - it "allows changing password" do - expect(can_edit_password?(user, current_user)).to be true - end - end - end - context "when the user is a data provider viewing organisation details" do let(:current_user) { FactoryBot.create(:user, :data_provider) } diff --git a/spec/policies/user_policy_spec.rb b/spec/policies/user_policy_spec.rb new file mode 100644 index 000000000..ec84fbceb --- /dev/null +++ b/spec/policies/user_policy_spec.rb @@ -0,0 +1,103 @@ +require "rails_helper" +# rubocop:disable RSpec/RepeatedExample + +RSpec.describe UserPolicy do + subject(:policy) { described_class } + + let(:data_provider) { FactoryBot.create(:user, :data_provider) } + let(:data_coordinator) { FactoryBot.create(:user, :data_coordinator) } + let(:support) { FactoryBot.create(:user, :support) } + + permissions :edit_names? do + it "allows changing their own name" do + expect(policy).to permit(data_provider, data_provider) + end + + it "as a coordinator it allows changing other user's name" do + expect(policy).to permit(data_coordinator, data_provider) + end + + it "as a support user it allows changing other user's name" do + expect(policy).to permit(support, data_provider) + end + end + + permissions :edit_emails? do + it "allows changing their own email" do + expect(policy).to permit(data_provider, data_provider) + end + + it "as a coordinator it allows changing other user's email" do + expect(policy).to permit(data_coordinator, data_provider) + end + + it "as a support user it allows changing other user's email" do + expect(policy).to permit(support, data_provider) + end + end + + permissions :edit_password? do + it "as a provider it allows changing their own password" do + expect(policy).to permit(data_provider, data_provider) + end + + it "as a coordinator it allows changing their own password" do + expect(policy).to permit(data_coordinator, data_coordinator) + end + + it "as a support user it allows changing their own password" do + expect(policy).to permit(support, support) + end + + it "as a coordinator it does not allow changing other user's password" do + expect(policy).not_to permit(data_coordinator, data_provider) + end + + it "as a support user it does not allow changing other user's password" do + expect(policy).not_to permit(support, data_provider) + end + end + + permissions :edit_roles? do + it "as a provider it does not allow changing roles" do + expect(policy).not_to permit(data_provider, data_provider) + end + + it "as a coordinator allows changing other user's roles" do + expect(policy).to permit(data_coordinator, data_provider) + end + + it "as a support user allows changing other user's roles" do + expect(policy).to permit(support, data_provider) + end + end + + permissions :edit_dpo? do + it "as a provider it does not allow changing dpo" do + expect(policy).not_to permit(data_provider, data_provider) + end + + it "as a coordinator allows changing other user's dpo" do + expect(policy).to permit(data_coordinator, data_provider) + end + + it "as a support user allows changing other user's dpo" do + expect(policy).to permit(support, data_provider) + end + end + + permissions :edit_key_contact? do + it "as a provider it does not allow changing key_contact" do + expect(policy).not_to permit(data_provider, data_provider) + end + + it "as a coordinator allows changing other user's key_contact" do + expect(policy).to permit(data_coordinator, data_provider) + end + + it "as a support user allows changing other user's key_contact" do + expect(policy).to permit(support, data_provider) + end + end +end +# rubocop:enable RSpec/RepeatedExample diff --git a/spec/requests/users_controller_spec.rb b/spec/requests/users_controller_spec.rb index ecba12933..564167354 100644 --- a/spec/requests/users_controller_spec.rb +++ b/spec/requests/users_controller_spec.rb @@ -120,6 +120,7 @@ RSpec.describe UsersController, type: :request do it "allows changing name, email and password" do expect(page).to have_link("Change", text: "name") expect(page).to have_link("Change", text: "email address") + expect(page).to have_link("Change", text: "telephone number") expect(page).to have_link("Change", text: "password") expect(page).not_to have_link("Change", text: "role") expect(page).not_to have_link("Change", text: "if data protection officer") @@ -180,6 +181,7 @@ RSpec.describe UsersController, type: :request do it "does not have edit links" do expect(page).not_to have_link("Change", text: "name") expect(page).not_to have_link("Change", text: "email address") + expect(page).not_to have_link("Change", text: "telephone number") expect(page).not_to have_link("Change", text: "password") expect(page).not_to have_link("Change", text: "role") expect(page).not_to have_link("Change", text: "if data protection officer") @@ -499,6 +501,7 @@ RSpec.describe UsersController, type: :request do it "allows changing name, email, password, role, dpo and key contact" do expect(page).to have_link("Change", text: "name") expect(page).to have_link("Change", text: "email address") + expect(page).to have_link("Change", text: "telephone number") expect(page).to have_link("Change", text: "password") expect(page).to have_link("Change", text: "role") expect(page).to have_link("Change", text: "if data protection officer") @@ -543,6 +546,7 @@ RSpec.describe UsersController, type: :request do it "allows changing name, email, role, dpo and key contact" do expect(page).to have_link("Change", text: "name") expect(page).to have_link("Change", text: "email address") + expect(page).to have_link("Change", text: "telephone number") expect(page).not_to have_link("Change", text: "password") expect(page).to have_link("Change", text: "role") expect(page).to have_link("Change", text: "if data protection officer") @@ -1169,6 +1173,7 @@ RSpec.describe UsersController, type: :request do it "allows changing name, email, password, role, dpo and key contact" do expect(page).to have_link("Change", text: "name") expect(page).to have_link("Change", text: "email address") + expect(page).to have_link("Change", text: "telephone number") expect(page).to have_link("Change", text: "password") expect(page).to have_link("Change", text: "role") expect(page).to have_link("Change", text: "if data protection officer") @@ -1198,6 +1203,7 @@ RSpec.describe UsersController, type: :request do it "allows changing name, email, role, dpo and key contact" do expect(page).to have_link("Change", text: "name") expect(page).to have_link("Change", text: "email address") + expect(page).to have_link("Change", text: "telephone number") expect(page).not_to have_link("Change", text: "password") expect(page).to have_link("Change", text: "role") expect(page).to have_link("Change", text: "if data protection officer") @@ -1242,6 +1248,7 @@ RSpec.describe UsersController, type: :request do it "allows changing name, email, role, dpo and key contact" do expect(page).to have_link("Change", text: "name") expect(page).to have_link("Change", text: "email address") + expect(page).to have_link("Change", text: "telephone number") expect(page).not_to have_link("Change", text: "password") expect(page).to have_link("Change", text: "role") expect(page).to have_link("Change", text: "if data protection officer") From a0b708b21eb75b1e58aadac4c93ab16c6a04475b Mon Sep 17 00:00:00 2001 From: Phil Lee Date: Mon, 19 Jun 2023 15:07:17 +0100 Subject: [PATCH 17/19] check bulk upload template against date (#1681) --- .../bulk_upload/lettings/validator.rb | 10 ++++-- .../lettings/year2022/csv_parser.rb | 11 ++++++- .../lettings/year2022/row_parser.rb | 12 +++---- .../bulk_upload/sales/year2022/csv_parser.rb | 11 ++++++- .../bulk_upload/sales/year2022/row_parser.rb | 12 +++---- .../bulk_upload/lettings/validator_spec.rb | 4 ++- .../lettings/year2022/csv_parser_spec.rb | 28 +++++++++++++++++ .../sales/year2022/csv_parser_spec.rb | 31 +++++++++++++++++++ 8 files changed, 102 insertions(+), 17 deletions(-) diff --git a/app/services/bulk_upload/lettings/validator.rb b/app/services/bulk_upload/lettings/validator.rb index 47600978b..14c31b6d7 100644 --- a/app/services/bulk_upload/lettings/validator.rb +++ b/app/services/bulk_upload/lettings/validator.rb @@ -150,13 +150,19 @@ private def validate_field_numbers_count return if halt_validations? - errors.add(:base, :wrong_field_numbers_count) unless csv_parser.correct_field_count? + unless csv_parser.correct_field_count? + errors.add(:base, :wrong_field_numbers_count) + halt_validations! + end end def validate_max_columns_count_if_no_headers return if halt_validations? - errors.add(:base, :over_max_column_count) if csv_parser.too_many_columns? + if csv_parser.too_many_columns? + errors.add(:base, :over_max_column_count) + halt_validations! + end end def validate_correct_template diff --git a/app/services/bulk_upload/lettings/year2022/csv_parser.rb b/app/services/bulk_upload/lettings/year2022/csv_parser.rb index f5bba15bf..f21e92a24 100644 --- a/app/services/bulk_upload/lettings/year2022/csv_parser.rb +++ b/app/services/bulk_upload/lettings/year2022/csv_parser.rb @@ -3,6 +3,7 @@ require "csv" class BulkUpload::Lettings::Year2022::CsvParser FIELDS = 134 MAX_COLUMNS = 135 + FORM_YEAR = 2022 attr_reader :path @@ -62,11 +63,19 @@ class BulkUpload::Lettings::Year2022::CsvParser end def wrong_template_for_year? - false + !(first_record_start_date >= form.start_date && first_record_start_date <= form.end_date) end private + def form + @form ||= FormHandler.instance.lettings_form_for_start_year(FORM_YEAR) + end + + def first_record_start_date + @first_record_start_date ||= row_parsers.first.startdate || Date.new + end + def default_field_numbers ("field_1".."field_#{FIELDS}").to_a end diff --git a/app/services/bulk_upload/lettings/year2022/row_parser.rb b/app/services/bulk_upload/lettings/year2022/row_parser.rb index c6251c81f..83ed20457 100644 --- a/app/services/bulk_upload/lettings/year2022/row_parser.rb +++ b/app/services/bulk_upload/lettings/year2022/row_parser.rb @@ -461,6 +461,12 @@ class BulkUpload::Lettings::Year2022::RowParser end end + def startdate + Date.new(field_98 + 2000, field_97, field_96) if field_98.present? && field_97.present? && field_96.present? + rescue Date::Error + Date.new + end + private def validate_declaration_acceptance @@ -983,12 +989,6 @@ private } end - def startdate - Date.new(field_98 + 2000, field_97, field_96) if field_98.present? && field_97.present? && field_96.present? - rescue Date::Error - Date.new - end - def renttype case field_1 when 1, 2, 3, 4 diff --git a/app/services/bulk_upload/sales/year2022/csv_parser.rb b/app/services/bulk_upload/sales/year2022/csv_parser.rb index 4bae79d33..939ff5689 100644 --- a/app/services/bulk_upload/sales/year2022/csv_parser.rb +++ b/app/services/bulk_upload/sales/year2022/csv_parser.rb @@ -2,6 +2,7 @@ require "csv" class BulkUpload::Sales::Year2022::CsvParser MAX_COLUMNS = 126 + FORM_YEAR = 2022 attr_reader :path @@ -44,11 +45,19 @@ class BulkUpload::Sales::Year2022::CsvParser end def wrong_template_for_year? - false + !(first_record_sale_date >= form.start_date && first_record_sale_date <= form.end_date) end private + def form + @form ||= FormHandler.instance.sales_form_for_start_year(FORM_YEAR) + end + + def first_record_sale_date + @first_record_sale_date ||= row_parsers.first.saledate || Date.new + end + def headers @headers ||= ("field_1".."field_125").to_a end diff --git a/app/services/bulk_upload/sales/year2022/row_parser.rb b/app/services/bulk_upload/sales/year2022/row_parser.rb index 1ac6258fd..40860c0d0 100644 --- a/app/services/bulk_upload/sales/year2022/row_parser.rb +++ b/app/services/bulk_upload/sales/year2022/row_parser.rb @@ -453,6 +453,12 @@ class BulkUpload::Sales::Year2022::RowParser end end + def saledate + Date.new(field_4 + 2000, field_3, field_2) if field_2.present? && field_3.present? && field_4.present? + rescue Date::Error + Date.new + end + private def validate_data_protection_answered @@ -762,12 +768,6 @@ private end end - def saledate - Date.new(field_4 + 2000, field_3, field_2) if field_2.present? && field_3.present? && field_4.present? - rescue Date::Error - Date.new - end - def hodate Date.new(field_61 + 2000, field_60, field_59) if field_59.present? && field_60.present? && field_61.present? rescue Date::Error diff --git a/spec/services/bulk_upload/lettings/validator_spec.rb b/spec/services/bulk_upload/lettings/validator_spec.rb index 27d018229..9f202e2f9 100644 --- a/spec/services/bulk_upload/lettings/validator_spec.rb +++ b/spec/services/bulk_upload/lettings/validator_spec.rb @@ -36,7 +36,9 @@ RSpec.describe BulkUpload::Lettings::Validator do context "and doesn't have too many columns" do before do - file.write(("a" * 135).chars.join(",")) + file.write(("a" * 95).chars.join(",")) + file.write(",1,10,22,") + file.write(("a" * 37).chars.join(",")) file.write("\n") file.rewind end diff --git a/spec/services/bulk_upload/lettings/year2022/csv_parser_spec.rb b/spec/services/bulk_upload/lettings/year2022/csv_parser_spec.rb index 5ae063567..15d20e9b2 100644 --- a/spec/services/bulk_upload/lettings/year2022/csv_parser_spec.rb +++ b/spec/services/bulk_upload/lettings/year2022/csv_parser_spec.rb @@ -187,4 +187,32 @@ RSpec.describe BulkUpload::Lettings::Year2022::CsvParser do expect(service.row_parsers[0].field_12.to_i).to eq(35) end end + + describe "#wrong_template_for_year?" do + context "when 23/24 file with 23/24 data" do + let(:log) { build(:lettings_log, :completed, startdate: Date.new(2023, 10, 1)) } + + before do + file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2023_csv_row) + file.rewind + end + + it "returns true" do + expect(service).to be_wrong_template_for_year + end + end + + context "when 22/23 file with 22/23 data" do + let(:log) { build(:lettings_log, :completed, startdate: Date.new(2022, 10, 1)) } + + before do + file.write(BulkUpload::LettingsLogToCsv.new(log:, col_offset: 0).to_2022_csv_row) + file.rewind + end + + it "returns false" do + expect(service).not_to be_wrong_template_for_year + end + end + end end diff --git a/spec/services/bulk_upload/sales/year2022/csv_parser_spec.rb b/spec/services/bulk_upload/sales/year2022/csv_parser_spec.rb index 76504b974..c33f6cf2f 100644 --- a/spec/services/bulk_upload/sales/year2022/csv_parser_spec.rb +++ b/spec/services/bulk_upload/sales/year2022/csv_parser_spec.rb @@ -116,4 +116,35 @@ RSpec.describe BulkUpload::Sales::Year2022::CsvParser do expect(service.column_for_field("field_125")).to eql("DU") end end + + describe "#wrong_template_for_year?" do + let(:file) { Tempfile.new } + let(:path) { file.path } + + context "when 23/24 file with 23/24 data" do + let(:log) { build(:sales_log, :completed, saledate: Date.new(2023, 10, 1)) } + + before do + file.write(BulkUpload::SalesLogToCsv.new(log:, col_offset: 0).to_2023_csv_row) + file.rewind + end + + it "returns true" do + expect(service).to be_wrong_template_for_year + end + end + + context "when 22/23 file with 22/23 data" do + let(:log) { build(:sales_log, :completed, saledate: Date.new(2022, 10, 1)) } + + before do + file.write(BulkUpload::SalesLogToCsv.new(log:, col_offset: 0).to_2022_csv_row) + file.rewind + end + + it "returns false" do + expect(service).not_to be_wrong_template_for_year + end + end + end end From 08348973082289e176d28d699ec97e65c5bc0d41 Mon Sep 17 00:00:00 2001 From: Phil Lee Date: Mon, 19 Jun 2023 15:52:21 +0100 Subject: [PATCH 18/19] CLDC-2330 Fix again (#1670) * persist how to fix choice * fix choice decision persisted * set no cache headers * add chosen to sales journey * set no cache headers for sales * fix linting * Add chosen journey for sales soft validations * Add chosen journey for lettings soft validations * Update copy * Also redirect fix inline and confirm if soft validations are chosen to be bulk fixed --------- Co-authored-by: Kat --- .../bulk_upload_lettings_resume_controller.rb | 9 +++ ...tings_soft_validations_check_controller.rb | 9 +++ .../bulk_upload_sales_resume_controller.rb | 9 +++ ...sales_soft_validations_check_controller.rb | 9 +++ .../bulk_upload_lettings_resume/chosen.rb | 31 ++++++++++ .../bulk_upload_lettings_resume/confirm.rb | 21 ++++++- .../bulk_upload_lettings_resume/fix_choice.rb | 15 +++++ .../chosen.rb | 31 ++++++++++ .../confirm.rb | 21 ++++++- .../confirm_soft_errors.rb | 13 +++++ .../forms/bulk_upload_sales_resume/chosen.rb | 31 ++++++++++ .../forms/bulk_upload_sales_resume/confirm.rb | 21 ++++++- .../bulk_upload_sales_resume/fix_choice.rb | 15 +++++ .../chosen.rb | 31 ++++++++++ .../confirm.rb | 21 ++++++- .../confirm_soft_errors.rb | 13 +++++ .../chosen.html.erb | 14 +++++ .../chosen.html.erb | 14 +++++ .../bulk_upload_sales_resume/chosen.html.erb | 14 +++++ .../chosen.html.erb | 14 +++++ ...0230525090508_add_choice_to_bulk_upload.rb | 5 ++ db/schema.rb | 1 + ..._upload_lettings_resume_controller_spec.rb | 58 +++++++++++++++++++ ..._soft_validations_check_controller_spec.rb | 34 +++++++++++ ...ulk_upload_sales_resume_controller_spec.rb | 56 ++++++++++++++++++ ..._soft_validations_check_controller_spec.rb | 34 +++++++++++ 26 files changed, 536 insertions(+), 8 deletions(-) create mode 100644 app/models/forms/bulk_upload_lettings_resume/chosen.rb create mode 100644 app/models/forms/bulk_upload_lettings_soft_validations_check/chosen.rb create mode 100644 app/models/forms/bulk_upload_sales_resume/chosen.rb create mode 100644 app/models/forms/bulk_upload_sales_soft_validations_check/chosen.rb create mode 100644 app/views/bulk_upload_lettings_resume/chosen.html.erb create mode 100644 app/views/bulk_upload_lettings_soft_validations_check/chosen.html.erb create mode 100644 app/views/bulk_upload_sales_resume/chosen.html.erb create mode 100644 app/views/bulk_upload_sales_soft_validations_check/chosen.html.erb create mode 100644 db/migrate/20230525090508_add_choice_to_bulk_upload.rb diff --git a/app/controllers/bulk_upload_lettings_resume_controller.rb b/app/controllers/bulk_upload_lettings_resume_controller.rb index 7041051ba..132ca095b 100644 --- a/app/controllers/bulk_upload_lettings_resume_controller.rb +++ b/app/controllers/bulk_upload_lettings_resume_controller.rb @@ -1,5 +1,10 @@ class BulkUploadLettingsResumeController < ApplicationController before_action :authenticate_user! + before_action :set_no_cache_headers + + def set_no_cache_headers + response.set_header("Cache-Control", "no-store") + end def start @bulk_upload = current_user.bulk_uploads.find(params[:id]) @@ -11,6 +16,8 @@ class BulkUploadLettingsResumeController < ApplicationController @bulk_upload = current_user.bulk_uploads.find(params[:id]) @soft_errors_only = params[:soft_errors_only] == "true" + return redirect_to form.preflight_redirect unless form.preflight_valid? + render form.view_path end @@ -30,6 +37,8 @@ private @form ||= case params[:page] when "fix-choice" Forms::BulkUploadLettingsResume::FixChoice.new(form_params.merge(bulk_upload: @bulk_upload)) + when "chosen" + Forms::BulkUploadLettingsResume::Chosen.new(form_params.merge(bulk_upload: @bulk_upload)) when "confirm" Forms::BulkUploadLettingsResume::Confirm.new(form_params.merge(bulk_upload: @bulk_upload)) else diff --git a/app/controllers/bulk_upload_lettings_soft_validations_check_controller.rb b/app/controllers/bulk_upload_lettings_soft_validations_check_controller.rb index 5f9140452..a70af3c4b 100644 --- a/app/controllers/bulk_upload_lettings_soft_validations_check_controller.rb +++ b/app/controllers/bulk_upload_lettings_soft_validations_check_controller.rb @@ -2,10 +2,17 @@ class BulkUploadLettingsSoftValidationsCheckController < ApplicationController include ActionView::Helpers::TextHelper before_action :authenticate_user! + before_action :set_no_cache_headers + + def set_no_cache_headers + response.set_header("Cache-Control", "no-store") + end def show @bulk_upload = current_user.bulk_uploads.find(params[:id]) + return redirect_to form.preflight_redirect unless form.preflight_valid? + render form.view_path end @@ -30,6 +37,8 @@ private @form ||= case params[:page] when "confirm-soft-errors" Forms::BulkUploadLettingsSoftValidationsCheck::ConfirmSoftErrors.new(form_params.merge(bulk_upload: @bulk_upload)) + when "chosen" + Forms::BulkUploadLettingsSoftValidationsCheck::Chosen.new(form_params.merge(bulk_upload: @bulk_upload)) when "confirm" Forms::BulkUploadLettingsSoftValidationsCheck::Confirm.new(form_params.merge(bulk_upload: @bulk_upload)) else diff --git a/app/controllers/bulk_upload_sales_resume_controller.rb b/app/controllers/bulk_upload_sales_resume_controller.rb index e120620a2..9f937630d 100644 --- a/app/controllers/bulk_upload_sales_resume_controller.rb +++ b/app/controllers/bulk_upload_sales_resume_controller.rb @@ -1,5 +1,10 @@ class BulkUploadSalesResumeController < ApplicationController before_action :authenticate_user! + before_action :set_no_cache_headers + + def set_no_cache_headers + response.set_header("Cache-Control", "no-store") + end def start @bulk_upload = current_user.bulk_uploads.find(params[:id]) @@ -11,6 +16,8 @@ class BulkUploadSalesResumeController < ApplicationController @bulk_upload = current_user.bulk_uploads.find(params[:id]) @soft_errors_only = params[:soft_errors_only] == "true" + return redirect_to form.preflight_redirect unless form.preflight_valid? + render form.view_path end @@ -30,6 +37,8 @@ private @form ||= case params[:page] when "fix-choice" Forms::BulkUploadSalesResume::FixChoice.new(form_params.merge(bulk_upload: @bulk_upload)) + when "chosen" + Forms::BulkUploadSalesResume::Chosen.new(form_params.merge(bulk_upload: @bulk_upload)) when "confirm" Forms::BulkUploadSalesResume::Confirm.new(form_params.merge(bulk_upload: @bulk_upload)) else diff --git a/app/controllers/bulk_upload_sales_soft_validations_check_controller.rb b/app/controllers/bulk_upload_sales_soft_validations_check_controller.rb index 6daf167f8..5b71b2c40 100644 --- a/app/controllers/bulk_upload_sales_soft_validations_check_controller.rb +++ b/app/controllers/bulk_upload_sales_soft_validations_check_controller.rb @@ -2,10 +2,17 @@ class BulkUploadSalesSoftValidationsCheckController < ApplicationController include ActionView::Helpers::TextHelper before_action :authenticate_user! + before_action :set_no_cache_headers + + def set_no_cache_headers + response.set_header("Cache-Control", "no-store") + end def show @bulk_upload = current_user.bulk_uploads.find(params[:id]) + return redirect_to form.preflight_redirect unless form.preflight_valid? + render form.view_path end @@ -30,6 +37,8 @@ private @form ||= case params[:page] when "confirm-soft-errors" Forms::BulkUploadSalesSoftValidationsCheck::ConfirmSoftErrors.new(form_params.merge(bulk_upload: @bulk_upload)) + when "chosen" + Forms::BulkUploadSalesSoftValidationsCheck::Chosen.new(form_params.merge(bulk_upload: @bulk_upload)) when "confirm" Forms::BulkUploadSalesSoftValidationsCheck::Confirm.new(form_params.merge(bulk_upload: @bulk_upload)) else diff --git a/app/models/forms/bulk_upload_lettings_resume/chosen.rb b/app/models/forms/bulk_upload_lettings_resume/chosen.rb new file mode 100644 index 000000000..6a6f670c4 --- /dev/null +++ b/app/models/forms/bulk_upload_lettings_resume/chosen.rb @@ -0,0 +1,31 @@ +module Forms + module BulkUploadLettingsResume + class Chosen + include ActiveModel::Model + include ActiveModel::Attributes + include Rails.application.routes.url_helpers + + attribute :bulk_upload + + def view_path + "bulk_upload_lettings_resume/chosen" + end + + def back_path + lettings_logs_path + end + + def next_path + lettings_logs_path + end + + def save! + true + end + + def preflight_valid? + true + end + end + end +end diff --git a/app/models/forms/bulk_upload_lettings_resume/confirm.rb b/app/models/forms/bulk_upload_lettings_resume/confirm.rb index 7760ab2e8..c109cd1b1 100644 --- a/app/models/forms/bulk_upload_lettings_resume/confirm.rb +++ b/app/models/forms/bulk_upload_lettings_resume/confirm.rb @@ -20,11 +20,28 @@ module Forms end def save! - processor = BulkUpload::Processor.new(bulk_upload:) - processor.approve + ApplicationRecord.transaction do + processor = BulkUpload::Processor.new(bulk_upload:) + processor.approve + + bulk_upload.update!(choice: "create-fix-inline") + end true end + + def preflight_valid? + bulk_upload.choice != "create-fix-inline" && bulk_upload.choice != "bulk-confirm-soft-validations" + end + + def preflight_redirect + case bulk_upload.choice + when "create-fix-inline" + page_bulk_upload_lettings_resume_path(bulk_upload, :chosen) + when "bulk-confirm-soft-validations" + page_bulk_upload_lettings_soft_validations_check_path(bulk_upload, :chosen) + end + end end end end diff --git a/app/models/forms/bulk_upload_lettings_resume/fix_choice.rb b/app/models/forms/bulk_upload_lettings_resume/fix_choice.rb index 5513434de..76ee10d17 100644 --- a/app/models/forms/bulk_upload_lettings_resume/fix_choice.rb +++ b/app/models/forms/bulk_upload_lettings_resume/fix_choice.rb @@ -46,8 +46,23 @@ module Forms end def save! + bulk_upload.update!(choice:) if choice == "upload-again" + true end + + def preflight_valid? + bulk_upload.choice != "create-fix-inline" && bulk_upload.choice != "bulk-confirm-soft-validations" + end + + def preflight_redirect + case bulk_upload.choice + when "create-fix-inline" + page_bulk_upload_lettings_resume_path(bulk_upload, :chosen) + when "bulk-confirm-soft-validations" + page_bulk_upload_lettings_soft_validations_check_path(bulk_upload, :chosen) + end + end end end end diff --git a/app/models/forms/bulk_upload_lettings_soft_validations_check/chosen.rb b/app/models/forms/bulk_upload_lettings_soft_validations_check/chosen.rb new file mode 100644 index 000000000..b3091bc51 --- /dev/null +++ b/app/models/forms/bulk_upload_lettings_soft_validations_check/chosen.rb @@ -0,0 +1,31 @@ +module Forms + module BulkUploadLettingsSoftValidationsCheck + class Chosen + include ActiveModel::Model + include ActiveModel::Attributes + include Rails.application.routes.url_helpers + + attribute :bulk_upload + + def view_path + "bulk_upload_lettings_soft_validations_check/chosen" + end + + def back_path + lettings_logs_path + end + + def next_path + lettings_logs_path + end + + def save! + true + end + + def preflight_valid? + true + end + end + end +end diff --git a/app/models/forms/bulk_upload_lettings_soft_validations_check/confirm.rb b/app/models/forms/bulk_upload_lettings_soft_validations_check/confirm.rb index b804d7767..aba75791e 100644 --- a/app/models/forms/bulk_upload_lettings_soft_validations_check/confirm.rb +++ b/app/models/forms/bulk_upload_lettings_soft_validations_check/confirm.rb @@ -20,11 +20,28 @@ module Forms end def save! - processor = BulkUpload::Processor.new(bulk_upload:) - processor.approve_and_confirm_soft_validations + ApplicationRecord.transaction do + processor = BulkUpload::Processor.new(bulk_upload:) + processor.approve_and_confirm_soft_validations + + bulk_upload.update!(choice: "bulk-confirm-soft-validations") + end true end + + def preflight_valid? + bulk_upload.choice != "bulk-confirm-soft-validations" && bulk_upload.choice != "create-fix-inline" + end + + def preflight_redirect + case bulk_upload.choice + when "bulk-confirm-soft-validations" + page_bulk_upload_lettings_soft_validations_check_path(bulk_upload, :chosen) + when "create-fix-inline" + page_bulk_upload_lettings_resume_path(bulk_upload, :chosen) + end + end end end end diff --git a/app/models/forms/bulk_upload_lettings_soft_validations_check/confirm_soft_errors.rb b/app/models/forms/bulk_upload_lettings_soft_validations_check/confirm_soft_errors.rb index cbf486a4d..34b4b97f3 100644 --- a/app/models/forms/bulk_upload_lettings_soft_validations_check/confirm_soft_errors.rb +++ b/app/models/forms/bulk_upload_lettings_soft_validations_check/confirm_soft_errors.rb @@ -35,6 +35,19 @@ module Forms def save! true end + + def preflight_valid? + bulk_upload.choice != "bulk-confirm-soft-validations" && bulk_upload.choice != "create-fix-inline" + end + + def preflight_redirect + case bulk_upload.choice + when "bulk-confirm-soft-validations" + page_bulk_upload_lettings_soft_validations_check_path(bulk_upload, :chosen) + when "create-fix-inline" + page_bulk_upload_lettings_resume_path(bulk_upload, :chosen) + end + end end end end diff --git a/app/models/forms/bulk_upload_sales_resume/chosen.rb b/app/models/forms/bulk_upload_sales_resume/chosen.rb new file mode 100644 index 000000000..2fa85c6c9 --- /dev/null +++ b/app/models/forms/bulk_upload_sales_resume/chosen.rb @@ -0,0 +1,31 @@ +module Forms + module BulkUploadSalesResume + class Chosen + include ActiveModel::Model + include ActiveModel::Attributes + include Rails.application.routes.url_helpers + + attribute :bulk_upload + + def view_path + "bulk_upload_sales_resume/chosen" + end + + def back_path + sales_logs_path + end + + def next_path + sales_logs_path + end + + def save! + true + end + + def preflight_valid? + true + end + end + end +end diff --git a/app/models/forms/bulk_upload_sales_resume/confirm.rb b/app/models/forms/bulk_upload_sales_resume/confirm.rb index 4ce50fb55..1211ef3f0 100644 --- a/app/models/forms/bulk_upload_sales_resume/confirm.rb +++ b/app/models/forms/bulk_upload_sales_resume/confirm.rb @@ -20,11 +20,28 @@ module Forms end def save! - processor = BulkUpload::Processor.new(bulk_upload:) - processor.approve + ApplicationRecord.transaction do + processor = BulkUpload::Processor.new(bulk_upload:) + processor.approve + + bulk_upload.update!(choice: "create-fix-inline") + end true end + + def preflight_valid? + bulk_upload.choice != "create-fix-inline" && bulk_upload.choice != "bulk-confirm-soft-validations" + end + + def preflight_redirect + case bulk_upload.choice + when "create-fix-inline" + page_bulk_upload_sales_resume_path(bulk_upload, :chosen) + when "bulk-confirm-soft-validations" + page_bulk_upload_sales_soft_validations_check_path(bulk_upload, :chosen) + end + end end end end diff --git a/app/models/forms/bulk_upload_sales_resume/fix_choice.rb b/app/models/forms/bulk_upload_sales_resume/fix_choice.rb index 671891429..fc565e2f6 100644 --- a/app/models/forms/bulk_upload_sales_resume/fix_choice.rb +++ b/app/models/forms/bulk_upload_sales_resume/fix_choice.rb @@ -46,8 +46,23 @@ module Forms end def save! + bulk_upload.update!(choice:) if choice == "upload-again" + true end + + def preflight_valid? + bulk_upload.choice != "create-fix-inline" && bulk_upload.choice != "bulk-confirm-soft-validations" + end + + def preflight_redirect + case bulk_upload.choice + when "create-fix-inline" + page_bulk_upload_sales_resume_path(bulk_upload, :chosen) + when "bulk-confirm-soft-validations" + page_bulk_upload_sales_soft_validations_check_path(bulk_upload, :chosen) + end + end end end end diff --git a/app/models/forms/bulk_upload_sales_soft_validations_check/chosen.rb b/app/models/forms/bulk_upload_sales_soft_validations_check/chosen.rb new file mode 100644 index 000000000..2286d3b39 --- /dev/null +++ b/app/models/forms/bulk_upload_sales_soft_validations_check/chosen.rb @@ -0,0 +1,31 @@ +module Forms + module BulkUploadSalesSoftValidationsCheck + class Chosen + include ActiveModel::Model + include ActiveModel::Attributes + include Rails.application.routes.url_helpers + + attribute :bulk_upload + + def view_path + "bulk_upload_sales_soft_validations_check/chosen" + end + + def back_path + sales_logs_path + end + + def next_path + sales_logs_path + end + + def save! + true + end + + def preflight_valid? + true + end + end + end +end diff --git a/app/models/forms/bulk_upload_sales_soft_validations_check/confirm.rb b/app/models/forms/bulk_upload_sales_soft_validations_check/confirm.rb index 579af7e84..894f55123 100644 --- a/app/models/forms/bulk_upload_sales_soft_validations_check/confirm.rb +++ b/app/models/forms/bulk_upload_sales_soft_validations_check/confirm.rb @@ -20,11 +20,28 @@ module Forms end def save! - processor = BulkUpload::Processor.new(bulk_upload:) - processor.approve_and_confirm_soft_validations + ApplicationRecord.transaction do + processor = BulkUpload::Processor.new(bulk_upload:) + processor.approve_and_confirm_soft_validations + + bulk_upload.update!(choice: "bulk-confirm-soft-validations") + end true end + + def preflight_valid? + bulk_upload.choice != "bulk-confirm-soft-validations" && bulk_upload.choice != "create-fix-inline" + end + + def preflight_redirect + case bulk_upload.choice + when "bulk-confirm-soft-validations" + page_bulk_upload_sales_soft_validations_check_path(bulk_upload, :chosen) + when "create-fix-inline" + page_bulk_upload_sales_resume_path(bulk_upload, :chosen) + end + end end end end diff --git a/app/models/forms/bulk_upload_sales_soft_validations_check/confirm_soft_errors.rb b/app/models/forms/bulk_upload_sales_soft_validations_check/confirm_soft_errors.rb index e6fe00495..041647cf0 100644 --- a/app/models/forms/bulk_upload_sales_soft_validations_check/confirm_soft_errors.rb +++ b/app/models/forms/bulk_upload_sales_soft_validations_check/confirm_soft_errors.rb @@ -35,6 +35,19 @@ module Forms def save! true end + + def preflight_valid? + bulk_upload.choice != "bulk-confirm-soft-validations" && bulk_upload.choice != "create-fix-inline" + end + + def preflight_redirect + case bulk_upload.choice + when "bulk-confirm-soft-validations" + page_bulk_upload_sales_soft_validations_check_path(bulk_upload, :chosen) + when "create-fix-inline" + page_bulk_upload_sales_resume_path(bulk_upload, :chosen) + end + end end end end diff --git a/app/views/bulk_upload_lettings_resume/chosen.html.erb b/app/views/bulk_upload_lettings_resume/chosen.html.erb new file mode 100644 index 000000000..47ed9bbfd --- /dev/null +++ b/app/views/bulk_upload_lettings_resume/chosen.html.erb @@ -0,0 +1,14 @@ +<% content_for :before_content do %> + <%= govuk_back_link href: @form.back_path %> +<% end %> + +
+
+ Bulk upload for lettings (<%= @bulk_upload.year_combo %>) +

You need to fix logs from your bulk upload

+ +

You have chosen to create logs from your recent bulk upload. To view and complete these logs, return to the list of lettings logs.

+ + <%= govuk_button_link_to "Return to lettings logs", lettings_logs_path %> +
+
diff --git a/app/views/bulk_upload_lettings_soft_validations_check/chosen.html.erb b/app/views/bulk_upload_lettings_soft_validations_check/chosen.html.erb new file mode 100644 index 000000000..418387fc9 --- /dev/null +++ b/app/views/bulk_upload_lettings_soft_validations_check/chosen.html.erb @@ -0,0 +1,14 @@ +<% content_for :before_content do %> + <%= govuk_back_link href: @form.back_path %> +<% end %> + +
+
+ Bulk upload for lettings (<%= @bulk_upload.year_combo %>) +

These logs have been created

+ +

You have created logs from your bulk upload. Return to lettings logs to view them.

+ + <%= govuk_button_link_to "Return to lettings logs", lettings_logs_path %> +
+
diff --git a/app/views/bulk_upload_sales_resume/chosen.html.erb b/app/views/bulk_upload_sales_resume/chosen.html.erb new file mode 100644 index 000000000..0d381e6a6 --- /dev/null +++ b/app/views/bulk_upload_sales_resume/chosen.html.erb @@ -0,0 +1,14 @@ +<% content_for :before_content do %> + <%= govuk_back_link href: @form.back_path %> +<% end %> + +
+
+ Bulk upload for sales (<%= @bulk_upload.year_combo %>) +

You need to fix logs from your bulk upload

+ +

You have chosen to create logs from your recent bulk upload. To view and complete these logs, return to the list of sales logs.

+ + <%= govuk_button_link_to "Return to sales logs", sales_logs_path %> +
+
diff --git a/app/views/bulk_upload_sales_soft_validations_check/chosen.html.erb b/app/views/bulk_upload_sales_soft_validations_check/chosen.html.erb new file mode 100644 index 000000000..211f9c03c --- /dev/null +++ b/app/views/bulk_upload_sales_soft_validations_check/chosen.html.erb @@ -0,0 +1,14 @@ +<% content_for :before_content do %> + <%= govuk_back_link href: @form.back_path %> +<% end %> + +
+
+ Bulk upload for sales (<%= @bulk_upload.year_combo %>) +

These logs have been created

+ +

You have created logs from your bulk upload. Return to sales logs to view them.

+ + <%= govuk_button_link_to "Return to sales logs", sales_logs_path %> +
+
diff --git a/db/migrate/20230525090508_add_choice_to_bulk_upload.rb b/db/migrate/20230525090508_add_choice_to_bulk_upload.rb new file mode 100644 index 000000000..e8c299ec6 --- /dev/null +++ b/db/migrate/20230525090508_add_choice_to_bulk_upload.rb @@ -0,0 +1,5 @@ +class AddChoiceToBulkUpload < ActiveRecord::Migration[7.0] + def change + add_column :bulk_uploads, :choice, :text, null: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 8337c5305..1bbbba02a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -39,6 +39,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_06_09_101144) do t.datetime "updated_at", null: false t.text "filename" t.integer "needstype" + t.text "choice" t.index ["identifier"], name: "index_bulk_uploads_on_identifier", unique: true t.index ["user_id"], name: "index_bulk_uploads_on_user_id" end diff --git a/spec/requests/bulk_upload_lettings_resume_controller_spec.rb b/spec/requests/bulk_upload_lettings_resume_controller_spec.rb index 3666bc777..7ba8bbc85 100644 --- a/spec/requests/bulk_upload_lettings_resume_controller_spec.rb +++ b/spec/requests/bulk_upload_lettings_resume_controller_spec.rb @@ -29,6 +29,32 @@ RSpec.describe BulkUploadLettingsResumeController, type: :request do expect(response.body).to include(bulk_upload.filename) expect(response.body).not_to include("Cancel") end + + it "sets no cache headers" do + get "/lettings-logs/bulk-upload-resume/#{bulk_upload.id}/fix-choice" + + expect(response.headers["Cache-Control"]).to eql("no-store") + end + + context "and previously told us to fix inline" do + let(:bulk_upload) { create(:bulk_upload, :lettings, user:, bulk_upload_errors:, choice: "create-fix-inline") } + + it "redirects to chosen" do + get "/lettings-logs/bulk-upload-resume/#{bulk_upload.id}/fix-choice" + + expect(response).to redirect_to("/lettings-logs/bulk-upload-resume/#{bulk_upload.id}/chosen") + end + end + + context "and previously told us to bulk confirm soft validations" do + let(:bulk_upload) { create(:bulk_upload, :lettings, user:, bulk_upload_errors:, choice: "bulk-confirm-soft-validations") } + + it "redirects to soft validations check chosen" do + get "/lettings-logs/bulk-upload-resume/#{bulk_upload.id}/fix-choice" + + expect(response).to redirect_to("/lettings-logs/bulk-upload-soft-validations-check/#{bulk_upload.id}/chosen") + end + end end describe "GET /lettings-logs/bulk-upload-resume/:ID/fix-choice?soft_errors_only=true" do @@ -58,6 +84,8 @@ RSpec.describe BulkUploadLettingsResumeController, type: :request do patch "/lettings-logs/bulk-upload-resume/#{bulk_upload.id}/fix-choice", params: { form: { choice: "upload-again" } } expect(response).to redirect_to("/lettings-logs/bulk-upload-results/#{bulk_upload.id}") + + expect(bulk_upload.reload.choice).to eql("upload-again") end end @@ -66,6 +94,8 @@ RSpec.describe BulkUploadLettingsResumeController, type: :request do patch "/lettings-logs/bulk-upload-resume/#{bulk_upload.id}/fix-choice", params: { form: { choice: "create-fix-inline" } } expect(response).to redirect_to("/lettings-logs/bulk-upload-resume/#{bulk_upload.id}/confirm") + + expect(bulk_upload.reload.choice).to be_blank end end end @@ -78,6 +108,32 @@ RSpec.describe BulkUploadLettingsResumeController, type: :request do expect(response.body).to include("Are you sure") end + + it "sets no cache headers" do + get "/lettings-logs/bulk-upload-resume/#{bulk_upload.id}/confirm" + + expect(response.headers["Cache-Control"]).to eql("no-store") + end + + context "and previously told us to fix inline" do + let(:bulk_upload) { create(:bulk_upload, :lettings, user:, bulk_upload_errors:, choice: "create-fix-inline") } + + it "redirects to chosen" do + get "/lettings-logs/bulk-upload-resume/#{bulk_upload.id}/confirm" + + expect(response).to redirect_to("/lettings-logs/bulk-upload-resume/#{bulk_upload.id}/chosen") + end + end + + context "and previously told us to bulk confirm soft validations" do + let(:bulk_upload) { create(:bulk_upload, :lettings, user:, bulk_upload_errors:, choice: "bulk-confirm-soft-validations") } + + it "redirects to soft validations check chosen" do + get "/lettings-logs/bulk-upload-resume/#{bulk_upload.id}/confirm" + + expect(response).to redirect_to("/lettings-logs/bulk-upload-soft-validations-check/#{bulk_upload.id}/chosen") + end + end end describe "PATCH /lettings-logs/bulk-upload-resume/:ID/confirm" do @@ -90,6 +146,8 @@ RSpec.describe BulkUploadLettingsResumeController, type: :request do expect(mock_processor).to have_received(:approve) + expect(bulk_upload.reload.choice).to eql("create-fix-inline") + expect(response).to redirect_to("/lettings-logs/bulk-upload-results/#{bulk_upload.id}/resume") end end diff --git a/spec/requests/bulk_upload_lettings_soft_validations_check_controller_spec.rb b/spec/requests/bulk_upload_lettings_soft_validations_check_controller_spec.rb index d1252b5e2..0603e3d34 100644 --- a/spec/requests/bulk_upload_lettings_soft_validations_check_controller_spec.rb +++ b/spec/requests/bulk_upload_lettings_soft_validations_check_controller_spec.rb @@ -28,6 +28,32 @@ RSpec.describe BulkUploadLettingsSoftValidationsCheckController, type: :request expect(response.body).to include("Tenant code") expect(response.body).to include("some error") end + + it "sets no cache headers" do + get "/lettings-logs/bulk-upload-soft-validations-check/#{bulk_upload.id}/confirm-soft-errors" + + expect(response.headers["Cache-Control"]).to eql("no-store") + end + + context "and previously told us to fix inline" do + let(:bulk_upload) { create(:bulk_upload, :lettings, user:, bulk_upload_errors:, choice: "create-fix-inline") } + + it "redirects to resume chosen" do + get "/lettings-logs/bulk-upload-soft-validations-check/#{bulk_upload.id}/confirm-soft-errors" + + expect(response).to redirect_to("/lettings-logs/bulk-upload-resume/#{bulk_upload.id}/chosen") + end + end + + context "and previously told us to bulk confirm soft validations" do + let(:bulk_upload) { create(:bulk_upload, :lettings, user:, bulk_upload_errors:, choice: "bulk-confirm-soft-validations") } + + it "redirects to soft validations check chosen" do + get "/lettings-logs/bulk-upload-soft-validations-check/#{bulk_upload.id}/confirm-soft-errors" + + expect(response).to redirect_to("/lettings-logs/bulk-upload-soft-validations-check/#{bulk_upload.id}/chosen") + end + end end describe "PATCH /lettings-logs/bulk-upload-soft-validations-check/:ID/confirm-soft-errors" do @@ -38,6 +64,8 @@ RSpec.describe BulkUploadLettingsSoftValidationsCheckController, type: :request expect(response).to be_successful expect(response.body).to include("You must select if there are errors in these fields") + + expect(bulk_upload.reload.choice).to be_blank end end @@ -46,6 +74,8 @@ RSpec.describe BulkUploadLettingsSoftValidationsCheckController, type: :request patch "/lettings-logs/bulk-upload-soft-validations-check/#{bulk_upload.id}/confirm-soft-errors", params: { form: { confirm_soft_errors: "no" } } expect(response).to redirect_to("/lettings-logs/bulk-upload-resume/#{bulk_upload.id}/fix-choice?soft_errors_only=true") + + expect(bulk_upload.reload.choice).to be_blank end end @@ -56,6 +86,8 @@ RSpec.describe BulkUploadLettingsSoftValidationsCheckController, type: :request expect(response).to redirect_to("/lettings-logs/bulk-upload-soft-validations-check/#{bulk_upload.id}/confirm") follow_redirect! expect(response.body).not_to include("You’ve successfully uploaded") + + expect(bulk_upload.reload.choice).to be_blank end end end @@ -85,6 +117,8 @@ RSpec.describe BulkUploadLettingsSoftValidationsCheckController, type: :request expect(response).to redirect_to("/lettings-logs") follow_redirect! expect(response.body).to include("You’ve successfully uploaded 2 logs") + + expect(bulk_upload.reload.choice).to eql("bulk-confirm-soft-validations") end end end diff --git a/spec/requests/bulk_upload_sales_resume_controller_spec.rb b/spec/requests/bulk_upload_sales_resume_controller_spec.rb index 8dcc0ba00..9c0a7112c 100644 --- a/spec/requests/bulk_upload_sales_resume_controller_spec.rb +++ b/spec/requests/bulk_upload_sales_resume_controller_spec.rb @@ -29,6 +29,32 @@ RSpec.describe BulkUploadSalesResumeController, type: :request do expect(response.body).to include(bulk_upload.filename) expect(response.body).not_to include("Cancel") end + + it "sets no cache headers" do + get "/sales-logs/bulk-upload-resume/#{bulk_upload.id}/fix-choice" + + expect(response.headers["Cache-Control"]).to eql("no-store") + end + + context "and previously told us to fix inline" do + let(:bulk_upload) { create(:bulk_upload, :sales, user:, bulk_upload_errors:, choice: "create-fix-inline") } + + it "redirects to chosen" do + get "/sales-logs/bulk-upload-resume/#{bulk_upload.id}/fix-choice" + + expect(response).to redirect_to("/sales-logs/bulk-upload-resume/#{bulk_upload.id}/chosen") + end + end + + context "and previously told us to bulk confirm soft validations" do + let(:bulk_upload) { create(:bulk_upload, :sales, user:, bulk_upload_errors:, choice: "bulk-confirm-soft-validations") } + + it "redirects to soft validations check chosen" do + get "/sales-logs/bulk-upload-resume/#{bulk_upload.id}/fix-choice" + + expect(response).to redirect_to("/sales-logs/bulk-upload-soft-validations-check/#{bulk_upload.id}/chosen") + end + end end describe "GET /sales-logs/bulk-upload-resume/:ID/fix-choice?soft_errors_only=true" do @@ -58,6 +84,8 @@ RSpec.describe BulkUploadSalesResumeController, type: :request do patch "/sales-logs/bulk-upload-resume/#{bulk_upload.id}/fix-choice", params: { form: { choice: "upload-again" } } expect(response).to redirect_to("/sales-logs/bulk-upload-results/#{bulk_upload.id}") + + expect(bulk_upload.reload.choice).to eql("upload-again") end end @@ -66,6 +94,8 @@ RSpec.describe BulkUploadSalesResumeController, type: :request do patch "/sales-logs/bulk-upload-resume/#{bulk_upload.id}/fix-choice", params: { form: { choice: "create-fix-inline" } } expect(response).to redirect_to("/sales-logs/bulk-upload-resume/#{bulk_upload.id}/confirm") + + expect(bulk_upload.reload.choice).to be_blank end end end @@ -78,6 +108,22 @@ RSpec.describe BulkUploadSalesResumeController, type: :request do expect(response.body).to include("Are you sure") end + + it "sets no cache headers" do + get "/sales-logs/bulk-upload-resume/#{bulk_upload.id}/confirm" + + expect(response.headers["Cache-Control"]).to eql("no-store") + end + + context "and previously told us to bulk confirm soft validations" do + let(:bulk_upload) { create(:bulk_upload, :sales, user:, bulk_upload_errors:, choice: "bulk-confirm-soft-validations") } + + it "redirects to soft validations check chosen" do + get "/sales-logs/bulk-upload-resume/#{bulk_upload.id}/confirm" + + expect(response).to redirect_to("/sales-logs/bulk-upload-soft-validations-check/#{bulk_upload.id}/chosen") + end + end end describe "PATCH /sales-logs/bulk-upload-resume/:ID/confirm" do @@ -90,7 +136,17 @@ RSpec.describe BulkUploadSalesResumeController, type: :request do expect(mock_processor).to have_received(:approve) + expect(bulk_upload.reload.choice).to eql("create-fix-inline") + expect(response).to redirect_to("/sales-logs/bulk-upload-results/#{bulk_upload.id}/resume") end end + + describe "GET /sales-logs/bulk-upload-resume/:ID/chosen" do + it "displays correct content" do + get "/sales-logs/bulk-upload-resume/#{bulk_upload.id}/chosen" + + expect(response.body).to include("You need to fix logs from your bulk upload") + end + end end diff --git a/spec/requests/bulk_upload_sales_soft_validations_check_controller_spec.rb b/spec/requests/bulk_upload_sales_soft_validations_check_controller_spec.rb index 3aaa433f0..0b496aea4 100644 --- a/spec/requests/bulk_upload_sales_soft_validations_check_controller_spec.rb +++ b/spec/requests/bulk_upload_sales_soft_validations_check_controller_spec.rb @@ -28,6 +28,32 @@ RSpec.describe BulkUploadSalesSoftValidationsCheckController, type: :request do expect(response.body).to include("Purchaser code") expect(response.body).to include("some error") end + + it "sets no cache headers" do + get "/sales-logs/bulk-upload-soft-validations-check/#{bulk_upload.id}/confirm-soft-errors" + + expect(response.headers["Cache-Control"]).to eql("no-store") + end + + context "and previously told us to fix inline" do + let(:bulk_upload) { create(:bulk_upload, :sales, user:, bulk_upload_errors:, choice: "create-fix-inline") } + + it "redirects to resume chosen" do + get "/sales-logs/bulk-upload-soft-validations-check/#{bulk_upload.id}/confirm-soft-errors" + + expect(response).to redirect_to("/sales-logs/bulk-upload-resume/#{bulk_upload.id}/chosen") + end + end + + context "and previously told us to bulk confirm soft validations" do + let(:bulk_upload) { create(:bulk_upload, :sales, user:, bulk_upload_errors:, choice: "bulk-confirm-soft-validations") } + + it "redirects to soft validations check chosen" do + get "/sales-logs/bulk-upload-soft-validations-check/#{bulk_upload.id}/confirm-soft-errors" + + expect(response).to redirect_to("/sales-logs/bulk-upload-soft-validations-check/#{bulk_upload.id}/chosen") + end + end end describe "PATCH /sales-logs/bulk-upload-soft-validations-check/:ID/confirm-soft-errors" do @@ -38,6 +64,8 @@ RSpec.describe BulkUploadSalesSoftValidationsCheckController, type: :request do expect(response).to be_successful expect(response.body).to include("You must select if there are errors in these fields") + + expect(bulk_upload.reload.choice).to be_blank end end @@ -46,6 +74,8 @@ RSpec.describe BulkUploadSalesSoftValidationsCheckController, type: :request do patch "/sales-logs/bulk-upload-soft-validations-check/#{bulk_upload.id}/confirm-soft-errors", params: { form: { confirm_soft_errors: "no" } } expect(response).to redirect_to("/sales-logs/bulk-upload-resume/#{bulk_upload.id}/fix-choice?soft_errors_only=true") + + expect(bulk_upload.reload.choice).to be_blank end end @@ -56,6 +86,8 @@ RSpec.describe BulkUploadSalesSoftValidationsCheckController, type: :request do expect(response).to redirect_to("/sales-logs/bulk-upload-soft-validations-check/#{bulk_upload.id}/confirm") follow_redirect! expect(response.body).not_to include("You’ve successfully uploaded") + + expect(bulk_upload.reload.choice).to be_blank end end end @@ -85,6 +117,8 @@ RSpec.describe BulkUploadSalesSoftValidationsCheckController, type: :request do expect(response).to redirect_to("/sales-logs") follow_redirect! expect(response.body).to include("You’ve successfully uploaded 2 logs") + + expect(bulk_upload.reload.choice).to eql("bulk-confirm-soft-validations") end end end From de3e44459df68b3b7c09ccd29a710775ae686b31 Mon Sep 17 00:00:00 2001 From: Jack <113976590+bibblobcode@users.noreply.github.com> Date: Mon, 19 Jun 2023 16:07:32 +0100 Subject: [PATCH 19/19] Put bulk upload controller checks behind feature flag (#1712) --- .../bulk_upload_lettings_logs_controller.rb | 7 ++++--- app/controllers/bulk_upload_sales_logs_controller.rb | 7 ++++--- .../bulk_upload_lettings_logs_controller_spec.rb | 12 ++++++++++++ .../bulk_upload_sales_logs_controller_spec.rb | 12 ++++++++++++ 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/app/controllers/bulk_upload_lettings_logs_controller.rb b/app/controllers/bulk_upload_lettings_logs_controller.rb index 1d011dcca..035621e41 100644 --- a/app/controllers/bulk_upload_lettings_logs_controller.rb +++ b/app/controllers/bulk_upload_lettings_logs_controller.rb @@ -25,9 +25,10 @@ class BulkUploadLettingsLogsController < ApplicationController private def validate_data_protection_agrement_signed! - unless @current_user.organisation.data_protection_confirmed? - redirect_to lettings_logs_path - end + return unless FeatureToggle.new_data_protection_confirmation? + return if @current_user.organisation.data_protection_confirmed? + + redirect_to lettings_logs_path end def current_year diff --git a/app/controllers/bulk_upload_sales_logs_controller.rb b/app/controllers/bulk_upload_sales_logs_controller.rb index aa865f0c7..7a7a20297 100644 --- a/app/controllers/bulk_upload_sales_logs_controller.rb +++ b/app/controllers/bulk_upload_sales_logs_controller.rb @@ -25,9 +25,10 @@ class BulkUploadSalesLogsController < ApplicationController private def validate_data_protection_agrement_signed! - unless @current_user.organisation.data_protection_confirmed? - redirect_to sales_logs_path - end + return unless FeatureToggle.new_data_protection_confirmation? + return if @current_user.organisation.data_protection_confirmed? + + redirect_to sales_logs_path end def current_year diff --git a/spec/requests/bulk_upload_lettings_logs_controller_spec.rb b/spec/requests/bulk_upload_lettings_logs_controller_spec.rb index f901cdb7e..db5a3c4a6 100644 --- a/spec/requests/bulk_upload_lettings_logs_controller_spec.rb +++ b/spec/requests/bulk_upload_lettings_logs_controller_spec.rb @@ -18,6 +18,18 @@ RSpec.describe BulkUploadLettingsLogsController, type: :request do expect(response).to redirect_to("/lettings-logs") end + + context "when feature flag disabled" do + before do + allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(false) + end + + it "does not redirect to lettings index page" do + get "/lettings-logs/bulk-upload-logs/start", params: {} + + expect(response).not_to redirect_to("/lettings-logs") + end + end end context "when not in crossover period" do diff --git a/spec/requests/bulk_upload_sales_logs_controller_spec.rb b/spec/requests/bulk_upload_sales_logs_controller_spec.rb index 3220ff885..c7de6598e 100644 --- a/spec/requests/bulk_upload_sales_logs_controller_spec.rb +++ b/spec/requests/bulk_upload_sales_logs_controller_spec.rb @@ -18,6 +18,18 @@ RSpec.describe BulkUploadSalesLogsController, type: :request do expect(response).to redirect_to("/sales-logs") end + + context "when feature flag disabled" do + before do + allow(FeatureToggle).to receive(:new_data_protection_confirmation?).and_return(false) + end + + it "does not redirect to lettings index page" do + get "/lettings-logs/bulk-upload-logs/start", params: {} + + expect(response).not_to redirect_to("/sales-logs") + end + end end context "when not in crossover period" do