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