Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ The format is based on [Keep a Changelog 1.0.0].

## [unreleased]

-
- Ability to merge a NEW email case into an existing case [#ref](https://github.com/DFE-Digital/buy-for-your-school/pull/853)
- On Create a case, allow Category to be 'undefined' [#ref](https://github.com/DFE-Digital/buy-for-your-school/pull/848)
Comment thread
d-a-v-e marked this conversation as resolved.

## [release-019] - 2022-01-31

Expand Down
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ gem "omniauth_openid_connect"
gem "omniauth-rails_csrf_protection"
gem "pandoc-ruby"
gem "pg"
gem "pg_search"
gem "puma", "~> 5.6"
gem "rails", "~> 6.1.4"
gem "redis", "~> 4.5"
Expand Down
10 changes: 7 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ GEM
bindex (0.8.1)
binding_of_caller (1.0.0)
debug_inspector (>= 0.0.1)
bootsnap (1.10.3)
bootsnap (1.10.2)
msgpack (~> 1.2)
brakeman (5.2.1)
brakeman (5.2.0)
builder (3.2.4)
bullet (7.0.1)
activesupport (>= 3.0.0)
Expand Down Expand Up @@ -299,7 +299,10 @@ GEM
parallel (1.21.0)
parser (3.1.0.0)
ast (~> 2.4.1)
pg (1.3.1)
pg (1.3.0)
pg_search (2.3.6)
activerecord (>= 5.2)
activesupport (>= 5.2)
pry (0.13.1)
coderay (~> 1.1)
method_source (~> 1.0)
Expand Down Expand Up @@ -567,6 +570,7 @@ DEPENDENCIES
omniauth_openid_connect
pandoc-ruby
pg
pg_search
pry-byebug
pry-rails
puma (~> 5.6)
Expand Down
83 changes: 83 additions & 0 deletions app/controllers/support/cases/merge_emails_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
module Support
class Cases::MergeEmailsController < Cases::ApplicationController
before_action :from_case
before_action :to_case, except: %i[new]
before_action :stage, only: %i[create]

def new
clear_session
@back_url = support_cases_url
@stage = :search
@merge_emails_form = CaseMergeEmailsForm.new
end

def create
@merge_emails_form = CaseMergeEmailsForm.from_validation(validation)

case stage

when :search
@back_url = support_cases_url

when :preview
@back_url = new_support_case_merge_emails_path

when :merge
MergeCaseEmails.new(
from_case: from_case.__getobj__,
to_case: to_case.__getobj__,
).call
return redirect_to support_case_merge_emails_path(from_case)
end

render :new
rescue MergeCaseEmails::CaseNotNewError
clear_session
redirect_to support_case_path(@current_case), notice: I18n.t("support.case_merge_emails.flash.case_not_new")
end

def show; end

private

def stage
@stage ||= if validation&.success? && merge_emails_params[:confirmation]
:merge
elsif validation&.success?
:preview
else
:search
end
end

def from_case
@from_case ||= CasePresenter.new(current_case)
end

def to_case
@to_case ||= CasePresenter.new(
Case.find_by(ref: to_case_ref),
)
end

def to_case_ref
if params[:merge_emails_form] && merge_emails_params[:merge_into_case_ref].present?
session[:merge_into_case_ref] = merge_emails_params[:merge_into_case_ref]
else
session[:merge_into_case_ref]
end
end

def clear_session
session.delete(:merge_into_case_ref)
end

def validation
@validation ||= CaseMergeEmailsFormSchema.new.call(**merge_emails_params, merge_from_case_ref: from_case.ref)
end

def merge_emails_params
params.require(:merge_emails_form).permit(:merge_into_case_ref, :confirmation)
end
end
end
14 changes: 11 additions & 3 deletions app/controllers/support/cases_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,17 @@ class CasesController < Cases::ApplicationController
include Concerns::HasInteraction

def index
@cases = Case.includes(%i[agent category organisation]).all.map { |c| CasePresenter.new(c) }.sort_by(&:last_updated_at_date).paginate(page: params[:page])
@new_cases = Case.includes(%i[agent category organisation]).initial.map { |c| CasePresenter.new(c) }.sort_by(&:last_updated_at_date).paginate(page: params[:page])
@my_cases = Case.includes(%i[agent category organisation]).by_agent(current_agent&.id).map { |c| CasePresenter.new(c) }.sort_by(&:last_updated_at_date).paginate(page: params[:page])
respond_to do |format|
format.json do
@cases = Case.search(params[:q])
end

format.html do
@cases = Case.includes(%i[agent category organisation]).all.map { |c| CasePresenter.new(c) }.sort_by(&:last_updated_at_date).paginate(page: params[:page])
@new_cases = Case.includes(%i[agent category organisation]).initial.map { |c| CasePresenter.new(c) }.sort_by(&:last_updated_at_date).paginate(page: params[:page])
@my_cases = Case.includes(%i[agent category organisation]).by_agent(current_agent&.id).map { |c| CasePresenter.new(c) }.sort_by(&:last_updated_at_date).paginate(page: params[:page])
end
end
end

def show
Expand Down
13 changes: 13 additions & 0 deletions app/forms/support/case_merge_emails_form.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Support
class CaseMergeEmailsForm
extend Dry::Initializer
include Concerns::ValidatableForm

# @!attribute [r] merge_into_case_ref
# @return [String, nil]
option :merge_into_case_ref, Types::Params::String, optional: true
# @!attribute [r] merge_from_case_ref
# @return [String, nil]
option :merge_from_case_ref, Types::Params::String, optional: true
end
end
20 changes: 20 additions & 0 deletions app/forms/support/case_merge_emails_form_schema.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Support
class CaseMergeEmailsFormSchema < Dry::Validation::Contract
include Concerns::TranslatableFormSchema

params do
required(:merge_into_case_ref).value(:string)
required(:merge_from_case_ref).value(:string)
end

rule(:merge_into_case_ref) do
key.failure(:missing) unless Case.find_by(ref: value)
end

rule do
if values[:merge_into_case_ref] == values[:merge_from_case_ref]
base.failure("You cannot merge into the same case")
end
end
end
end
8 changes: 8 additions & 0 deletions app/models/support/case.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
# frozen_string_literal: true

require "csv"
require "pg_search"

module Support
#
# A case is opened from a "support enquiry" dealing with a "category of spend"
#
class Case < ApplicationRecord
include ::PgSearch::Model

pg_search_scope :search, against: %i[ref], associated_against: {
organisation: %i[name urn],
agent: %i[first_name last_name],
}

belongs_to :category, class_name: "Support::Category", optional: true
belongs_to :agent, class_name: "Support::Agent", optional: true
belongs_to :organisation, class_name: "Support::Organisation", optional: true
Expand Down
39 changes: 39 additions & 0 deletions app/services/support/merge_case_emails.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
require "dry-initializer"

module Support
#
# Merge a new support cases emails into an existing support case
#
# TODO:
# 1. If adding any further merging functionality to this class, consider adding a merged_into_case_id
# to t.support_cases to keep a record of the case a case was merged into.
#
# 2. The inverse should also be applied to t.support_emails and t.support_interactions to keep a record of their
# original case (e.g. original_case_id).
#
class MergeCaseEmails
class CaseNotNewError < StandardError; end

extend Dry::Initializer

# @!attribute from_case
# @return [Support::Case]
option :from_case, Types.Instance(Support::Case), optional: false

# @!attribute to_case
# @return [Support::Case]
option :to_case, Types.Instance(Support::Case), optional: false

# @return [TrueClass]
def call
from_case.transaction do
raise CaseNotNewError unless from_case.initial?

from_case.interactions&.update_all(case_id: to_case.id)
from_case.emails&.update_all(case_id: to_case.id)
from_case.closed!
end
to_case.pending!
end
end
end
13 changes: 13 additions & 0 deletions app/views/support/cases/index.json.jbuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
json.array! @cases do |kase|
# case
json.id kase.id
json.ref kase.ref

# agent
json.agent_name "#{kase.agent&.first_name} #{kase.agent&.last_name}"

# organisation
json.organisation_name kase.organisation&.name
json.organisation_urn kase.organisation&.urn
json.organisation_ukprn kase.organisation&.ukprn
end
56 changes: 56 additions & 0 deletions app/views/support/cases/merge_emails/_preview.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<%= form.hidden_field :confirmation, value: true %>
<%= form.hidden_field :merge_into_case_ref, value: @to_case.ref %>

<h1 class="govuk-heading-l">
<%= I18n.t("support.case_merge_emails.preview.header") %>
</h1>

<!-- FROM CASE -->

<table class="govuk-table">
<caption class="govuk-table__caption govuk-table__caption--m"><%= I18n.t("support.case_merge_emails.preview.from") %></caption>
<thead class="govuk-table__head">
<tr class="govuk-table__row">
<% I18n.t("support.case_merge_emails.preview.table").each do |header| %>
<th scope="col" class="govuk-table__header"> <%= header %> </th>
<% end %>
</tr>
</thead>
<tbody class="govuk-table__body">
<tr class="govuk-table__row" id="merge_emails_from">
<td class="govuk-table__cell"> <%= @from_case.ref %> </td>
<td class="govuk-table__cell"> <%= @from_case.org_name %> </td>
<td class="govuk-table__cell"> <%= @from_case.category&.title %> </td>
<td class="govuk-table__cell"> <%= @from_case.state %> </td>
<td class="govuk-table__cell"> <%= @from_case.agent&.full_name %> </td>
<td class="govuk-table__cell"> <%= @from_case.created_at %> </td>
</tr>
</tbody>
</table>

<!-- TO CASE -->

<table class="govuk-table">
<caption class="govuk-table__caption govuk-table__caption--m"><%= I18n.t("support.case_merge_emails.preview.to") %></caption>
<thead class="govuk-table__head">
<tr class="govuk-table__row">
<% I18n.t("support.case_merge_emails.preview.table").each do |header| %>
<th scope="col" class="govuk-table__header"> <%= header %> </th>
<% end %>
</tr>
</thead>
<tbody class="govuk-table__body">
<tr class="govuk-table__row" id="merge_emails_to">
<td class="govuk-table__cell"> <%= @to_case.ref %> </td>
<td class="govuk-table__cell"> <%= @to_case.org_name %> </td>
<td class="govuk-table__cell"> <%= @to_case.category&.title %> </td>
<td class="govuk-table__cell"> <%= @to_case.state %> </td>
<td class="govuk-table__cell"> <%= @to_case.agent&.full_name %> </td>
<td class="govuk-table__cell"> <%= @to_case.created_at %> </td>
</tr>
</tbody>
</table>

<p class="govuk-body-m">
<%= I18n.t("support.case_merge_emails.preview.reminder", case_ref: @from_case.ref) %>
</p>
17 changes: 17 additions & 0 deletions app/views/support/cases/merge_emails/_search.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<h1 class="govuk-heading-l"> <%= I18n.t("support.case_merge_emails.search.header") %> </h1>

<p class="govuk-body-m"> <%= I18n.t("support.case_merge_emails.search.subtitle") %> </p>

<span class="govuk-caption-m"> <%= I18n.t("support.case_merge_emails.search.hint") %> </span>

<%=
render "components/autocomplete",
container_id: "case-autocomplete-container",
label_class: "nil",
label_text: nil,
element_id: "case-autocomplete",
element_name: "merge_emails_form[merge_into_case_ref]",
template_suggestion: "{{ref}}, <strong>{{organisation_name}}</strong>, {{organisation_urn}}/{{organisation_ukprn}}, {{agent_name}}",
value_field: :ref,
query_url: support_cases_path(format: :json, q: "{{QUERY}}")
%>
25 changes: 25 additions & 0 deletions app/views/support/cases/merge_emails/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<%= form_with model: @merge_emails_form,
scope: :merge_emails_form,
url: support_case_merge_emails_path(case_id: @from_case.id),
method: :post do |form|
%>

<%= form.govuk_error_summary %>

<%#
# 1. Search
# 2. Preview
%>
<%= render "support/cases/merge_emails/#{@stage}", form: form %>

<ul class="govuk-list">
<li class="govuk-!-display-inline">
<%= form.submit I18n.t("support.generic.continue"), class: "govuk-button" %>
</li>
<% if @stage == :preview %>
<li class="govuk-!-display-inline-block govuk-!-padding-left-3 govuk-!-padding-top-1">
<%= link_to I18n.t("support.generic.cancel"), new_support_case_merge_emails_path, class: "govuk-link" %>
</li>
<% end %>
</ul>
<% end %>
Loading