Class: SupportCasesController

Inherits:
CrmController show all
Includes:
Controllers::Attachable, Controllers::RequireTurboFrame, Controllers::Workflowable, SmartServicesHelper
Defined in:
app/controllers/support_cases_controller.rb

Overview

== Schema Information

Table name: support_cases

id :integer not null, primary key
case_number :string(255)
case_type :string(255)
assigned_to_id :integer
state :string(255)
description :text
priority :string(255)
creator_id :integer
updater_id :integer
created_at :datetime not null
updated_at :datetime not null
opened_at :datetime
closed_at :datetime

Constant Summary collapse

TAB_ACTIONS =

Tab fragments never render standalone — non-frame hits redirect to show ?tab=.

%i[tab_rooms tab_quotes tab_rmas tab_credit_memos tab_support_cases tab_reviews tab_state_history tab_warehouse].freeze

Constants included from Controllers::Attachable

Controllers::Attachable::PUBLICATIONS_PER_PAGE

Constants included from Controllers::ReferenceFindable

Controllers::ReferenceFindable::ID_EMBEDDED_PATTERNS

Constants included from Controllers::AnalyticsEvents

Controllers::AnalyticsEvents::MAX_QUEUED_EVENTS, Controllers::AnalyticsEvents::SESSION_KEY

Constants included from Controllers::ErrorRendering

Controllers::ErrorRendering::NON_CONTENT_PATH_PREFIXES

Instance Method Summary collapse

Methods included from SmartServicesHelper

#build_address, #calculate_distance_from_lz, #check_postal_code, #create_customer, #create_order, #format_service_address

Methods included from Controllers::Workflowable

#render_workflow_error_stream, #render_workflow_success_stream, #workflow_action, #workflow_action_complete

Methods included from Controllers::RequireTurboFrame

require_in_pane_tab_frames, require_turbo_frame

Methods included from Controllers::Attachable

#attach, #publication_modal, #remove_attachment, #search_library

Methods inherited from CrmController

#access_denied, #context_id, #context_object, #crm_home_path, #current_ability, #default_url_options, #download_temp, #get_tempfile_path_for_download, #init_status_job_collector, #initialize_crm_lazy_chunks, #persist_enqueued_status_jobs, #record_not_found, #redirect_to_job_or_fallback, #render_edit_action, #set_context, #set_download_path, #stash_file_for_temp_download, #sync_admin_presence_cookie, #touch_employee_last_seen

Methods inherited from ApplicationController

#account_impersonated?, #add_to_flash, #after_sign_in_path_for, #bypass_forgery_protection?, #chat_enabled?, #cloudflare_cleared?, #default_catalog, #default_url_options, #enable_turbo_frames, #find_publication, #fix_invalid_accept_header, #init_js_utils, #is_globals_call?, #layout_by_resource, #locale_store, #redirect_to, #require_employee_for_crm, #set_base_host, #set_real_ip, #set_report_errors_for, #should_render_layout?, #skip_layout_for_turbo_frame?, #stamp_impersonation_context, #tab_frame_breakout_request?, #warmlyyours_canada_ip?, #warmlyyours_ip?, #y

Methods included from Controllers::ReturnPathHandling

#check_for_return_path, #redirect_to_return_path_or_default

Methods included from Controllers::AnalyticsEvents

#consume_queued_analytics_events, #registration_lead_type, #track_event

Methods included from Controllers::DeviceDetection

#device_detector, #is_ie?

Methods included from Controllers::SubdomainDetection

#is_crm_request?, #is_www_request?, #json_request?

Methods included from Controllers::TurboSafeRedirect

#redirect_to

Methods included from Controllers::TrackingDetection

#bot_request?, #gdpr_country?, #gdpr_country_data, #prevent_bots, #set_tracking_cookie, #track_visitor?

Methods included from Controllers::AcceleratedFileSending

#send_file_accelerated, #send_upload_accelerated

Methods included from Controllers::ErrorRendering

#excp_string, #mail_to_for_error_reporting, #render_400, #render_404, #render_406, #render_410, #render_500, #render_invalid_authenticity_token, #render_ip_spoof_error, #render_unpermitted_parameters, #safe_referer_or_fallback

Methods included from Controllers::TurnstileVerification

#load_turnstile_script_tag, #turnstile_lazy_widget, #turnstile_script_tag, #turnstile_widget, #validate_turnstile!

Methods included from Controllers::CloudflareCaching

edge_cached, #edge_cached_action?, #reset_cloudflare_cache, #set_cloudflare_cache, #skip_edge_cache!, #skip_session

Methods included from Controllers::Webpackable

#preload_webpack_fonts, #webpack_css_include, #webpack_css_url, #webpack_js_include, #wpd_is_running?

Methods included from Controllers::Localizable

#cloudflare_country_locale, #determine_request_locale, #geocoder_locale, #guest_user_locale_check, #locale_optional_www_auth_path?, #param_locale, #set_locale, #set_request_locale, #skip_localization?, #warmlyyours_ip_locale

Methods included from Controllers::Authenticable

#access_denied, #authenticate_account, #authenticate_account!, #authenticate_account_from_login_token!, #check_is_a_manager, #check_is_a_sales_manager, #check_is_an_admin, #check_is_an_employee, #check_party, #clear_mismatched_guest_user, #create_guest_user, #credentials?, #current_or_guest_user, #current_or_guest_user_id_read_only, #current_user, #devise_mapping, #fully_logged_in?, #generate_bot_id, #guest_user, #identifiable?, #init_current_user, #initialize_guest, #load_context_user, #logging_in, #resource, #resource_name, #restrict_access_for_non_employees, #scrubbed_request_path, #user_object, #warn_on_session_guest_id_leak

Methods included from UrlsHelper

#catalog_breadcrumb_links, #catalog_link, #catalog_link_for_product_line, #catalog_link_for_sku, #cms_link, #delocalized_path, #path_to_sales_product_sku, #path_to_sales_product_sku_for_product_line, #path_to_sales_product_sku_for_product_line_slug, #product_line_from_catalog_link, #protocol_neutral_url, #sanitize_external_url, #valid_external_url?

Instance Method Details

#add_participantvoid

This method returns an undefined value.

Adds a party as a support case participant, suggesting the customer's
other contacts as follow-up picks.



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'app/controllers/support_cases_controller.rb', line 98

def add_participant
  if (party_id = params.dig(:party_id).presence)
    @party = Party.find(party_id)
    @support_case_participant = SupportCaseParticipant.new(party: @party, preferred_language: @party.preferred_language)

    # After a participant is added we will pre-populate the drop with with all contacts of the customer of that party
    if @party.customer
      @suggested_contacts = @party.customer.contacts.active.where.not(id: @party.id).order(:full_name).to_a.map do |party|
        {
          text: party.to_s,
          id: party.id
        }
      end
    end

    respond_to do |format|
      format.turbo_stream
    end
  else
    head :service_unavailable
  end
end

#check_zip_codevoid

This method returns an undefined value.

Returns the smart services available at the given postal code, as JSON.



419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
# File 'app/controllers/support_cases_controller.rb', line 419

def check_zip_code
  services = check_postal_code(params[:postal_code])

  respond_to do |format|
    format.json do
      if services.any?
        render json: {
          services: services.join(',')
        }
      else
        render json: { error_message: 'No smart services found!' }, status: :internal_server_error
      end
    end
  end
end

#closedvoid

This method returns an undefined value.

Renders the case-closing form (submission is confirm_closed).



296
# File 'app/controllers/support_cases_controller.rb', line 296

def closed; end

#confirm_closedvoid

This method returns an undefined value.

Applies the submitted close reason to the support case, running the
service-specific order-transition logic for service cases.



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'app/controllers/support_cases_controller.rb', line 307

def confirm_closed
  authorize! :update, @support_case
  @support_case.update(params[:support_case])

  # `failed` distinguishes Hotwire validation failures (re-render the close
  # form with flash.now + 422) from successes / partial-successes that should
  # take the user back to the case show page. The hybrid "case closed but
  # order still open" branch stays a redirect because the case did transition.
  failed = false

  if @support_case.is_service?
    if @support_case.activities.where(activity_type_id: [ActivityTypeConstants::ONSITESERVICE,
                                                         ActivityTypeConstants::SSI_INSTALL]).open_activities.present?
      flash.now[:error] = 'Unable to close case. You need to complete or cancel the OnsiteService activity first'
      failed = true
    elsif @support_case.activities.final_service_completed.any? && @support_case.service_duration.blank?
      flash.now[:error] = 'Unable to close case. Please specify service duration, mileage and travel time if applicable.'
      failed = true
    else
      case @support_case.closed_reason
      when 'Service has been completed - warranty claim - free service'
        @support_case.case_closed
        SupportMailer.service_warranty_claim(@support_case).deliver_later
        flash[:info] = 'Case has been closed. Order needs to be manually refunded and completed.'
      when 'Service has not been completed - no show / no refund',
            'Service has not been completed - not able to perform due to comply with T&C',
            'Service has been completed'
        @support_case.orders.in_state('awaiting_deliveries').each do |o|
          o.processing_deliveries!
          o.deliveries.where(state: 'service_ready_to_fulfill').find_each(&:shipped!)
        end
        @support_case.case_closed
        flash[:info] = 'Case has been closed. Order moved to awaiting deliveries.'
      when 'Service has not been completed - cancelled within the 24h grace period', "Customer Doesn't Respond"
        res = @support_case.orders.in_progress.none?
        @support_case.orders.in_progress.each do |o|
          next unless o.deliveries.size == 1 && o.deliveries.where(state: 'service_ready_to_fulfill').any?

          o.cr_hold
          o.cancel
          res = true
        end
        @support_case.case_closed
        if res
          flash[:info] = 'Case has been closed. Order associated with this service has been canceled.'
        else
          flash[:error] =
            'Case has been closed, but order is still open because it has other deliveries no associated with the service. Please close the order manually. '
        end
      when 'Duplicate'
        if @support_case.orders.not_cancelled.present?
          flash.now[:error] = 'Before proceeding, please cancel the orders attached to this case first or migrate the orders to another support case.'
          failed = true
        else
          @support_case.case_closed
          flash[:info] = 'Case has been closed.'
        end
      else
        flash.now[:error] = "Please choose a reason related to the service. i.e. 'Service has been completed'"
        failed = true
      end
    end
  elsif @support_case.case_closed
    flash[:info] = 'Case has been closed'
  else
    flash.now[:error] = @support_case.errors_to_s.presence || 'Unable to close case'
    failed = true
  end

  if failed
    render :closed, status: :unprocessable_content
  else
    redirect_to support_case_path(@support_case)
  end
end

#confirm_reopenedvoid

This method returns an undefined value.

Reopens the support case with the submitted reason.



386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
# File 'app/controllers/support_cases_controller.rb', line 386

def confirm_reopened
  authorize! :update, @support_case

  reason = params[:support_case][:reopened_reason].to_s.strip
  if reason.blank?
    flash.now[:error] = 'Reason to reopen not provided. Please specify a reason.'
    render :reopened, status: :unprocessable_content
    return
  end

  @support_case.reopened_reason = reason
  if @support_case.case_open
    @support_case.create_new_tickout(reason)
    flash[:info] = 'Case has been reopened'
    redirect_to support_case_path(@support_case)
  else
    flash.now[:error] = @support_case.errors_to_s.presence || 'Could not reopen case.'
    render :reopened, status: :unprocessable_content
  end
end

#createObject

POST /support_cases
POST /support_cases.json



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'app/controllers/support_cases_controller.rb', line 174

def create
  @support_case = SupportCase.new(params[:support_case])
  authorize! :create, @support_case
  @suggested_participants = []
  success = true

  if @support_case.service.present?
    @support_case.description = "#{@support_case.description}<p>Customer prefers to be called in the #{params[:support_case][:service_time]}</p>"
    @support_case.assigned_to_id = Employee::SCOTT
    address = begin
      Address.find(@support_case.service_address_id)
    rescue StandardError
      nil
    end
    services = check_postal_code(address.try(:zip))
    if services.present?
      @order = create_order(@support_case.service, @support_case.service_room_qty, @support_case.customer)
      @order.shipping_address = address
      @order.save
      @order.reload
      @order.pending_payment
    else
      flash[:error] = 'The service you selected is not provided in the customer address. SMARFIT is only provided within 50 miles of WY LZ office.'
      success = false
    end
  end

  # In order for nested contact points to save, the support participants must save
  if success
    case_number_retries = 0
    begin
      SupportCase.transaction do
        success = @support_case.save &&
                  @support_case.support_case_participants.all? do |scp|
                    scp.support_case = @support_case
                    scp.party.save && scp.save
                  end
      end
    rescue ActiveRecord::RecordNotUnique => e
      raise e unless e.message.include?('index_support_cases_on_case_number')
      raise e if (case_number_retries += 1) > 3

      @support_case.case_number = nil
      retry
    end
  end
  respond_to do |format|
    if success
      format.html do
        if @order.present?
          @support_case.order_ids = [@order.id]
          @support_case.save
          redirect_to @order
        elsif params[:commit].to_s.downcase.index('participants').present?
          redirect_to participants_support_case_path(@support_case, return_path: edit_support_case_path(@support_case))
        elsif params[:commit].to_s.downcase.index('rooms').present?
          redirect_to rooms_support_case_path(@support_case, return_path: edit_support_case_path(@support_case))
        elsif params[:commit].to_s.downcase.index('orders').present?
          redirect_to orders_support_case_path(@support_case, return_path: edit_support_case_path(@support_case))
        else
          redirect_to @support_case
        end
      end
      format.json { render json: @support_case, status: :created, location: @support_case }
    else
      format.html { render action: 'new', status: :unprocessable_content }
      format.json { render json: @support_case.errors, status: :unprocessable_content }
    end
  end
end

#destroyObject

DELETE /support_cases/1
DELETE /support_cases/1.json



282
283
284
285
286
287
288
289
290
291
# File 'app/controllers/support_cases_controller.rb', line 282

def destroy
  case_type = @support_case.case_type
  authorize! :destroy, @support_case
  @support_case.destroy

  respond_to do |format|
    format.html { redirect_to support_cases_url(case_type: case_type) }
    format.json { head :no_content }
  end
end

#editObject

GET /support_cases/1/edit



167
168
169
170
# File 'app/controllers/support_cases_controller.rb', line 167

def edit
  authorize! :update, @support_case
  @suggested_participants = []
end

#edit_linked_ordersvoid

This method returns an undefined value.

Renders the order-linking form for the support case.



140
141
142
143
# File 'app/controllers/support_cases_controller.rb', line 140

def edit_linked_orders
  set_support_case
  authorize!(:update, @support_case)
end

#indexObject

GET /support_cases
GET /support_cases.json



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'app/controllers/support_cases_controller.rb', line 36

def index
  authorize! :read, SupportCase
  @case_type = params[:case_type] || 'Tech'
  @states = case @case_type
            when 'Tech'
              %w[new open waiting_on_customer waiting_on_equipment site_visit_scheduled third_party_site_visit_scheduled]
            when 'SmartService'
              %w[new open waiting_on_customer waiting_to_confirm_appointment site_visit_scheduled]
            when 'Accounting'
              %w[new open waiting_on_customer waiting_on_accountant waiting_on_warmlyyours sent_to_collections]
            when 'Ecommerce'
              %w[new open waiting_on_customer]
            else
              %w[new open waiting_on_customer]
            end
  @priorities = %w[High Medium Low]
  @support_cases_grouped = SupportCase.left_outer_joins(:assigned_to).where(case_type: @case_type).where(state: @states).order(assigned_to_id: :desc, coalesce_full_name_unassigned: :asc).group("COALESCE(full_name, 'Unassigned')", :assigned_to_id, :state,
                                                                                                                                                                                          :priority).count
  @assignees = @support_cases_grouped.keys.map { |(name, emp_id)| [name, emp_id] }.uniq
  respond_to do |format|
    format.html # index.html.erb
    format.json { render json: @support_cases }
  end
end

#lookupvoid

This method returns an undefined value.

Returns support case autocomplete/picker results as JSON.



156
157
158
159
160
161
162
163
164
# File 'app/controllers/support_cases_controller.rb', line 156

def lookup
  results = SupportCase.order(created_at: :desc)
  if params[:q].present?
    results = results.lookup(params[:q])
  elsif params[:customer_id].present?
    results = results.joins(:support_case_participants).where(SupportCaseParticipant[:party_id].eq(params[:customer_id])).distinct
  end
  render json: TomSelect.format_json_results(self, results, params[:page], params[:per])
end

#newObject

GET /support_cases/new
GET /support_cases/new.json



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'app/controllers/support_cases_controller.rb', line 76

def new
  @support_case = SupportCase.new(case_type: params[:case_type] || 'Tech')
  @support_case.send(:set_defaults, current_user)
  @support_case.send(:set_case_number)
  @support_case.build_participants_and_rooms(params)
  @support_case.orders << Order.find(params[:order_id]) if params[:order_id].present? && @support_case.orders.none? do |o|
                                                             o.id == params[:order_id].to_i
                                                           end
  @support_case.quotes << Quote.find(params[:quote_id]) if params[:quote_id].present? && @support_case.quotes.none? do |q|
                                                             q.id == params[:quote_id].to_i
                                                           end
  authorize! :create, @support_case
  respond_to do |format|
    format.html # new.html.erb
    format.json { render json: @support_case }
  end
end

#participant_lookupObject

Used by tom-select field on support_cases/participants
if q is specified a global lookup is run
if party_id is specified, then a single party is looked up (used to initialize form on load typically)



438
439
440
441
442
# File 'app/controllers/support_cases_controller.rb', line 438

def participant_lookup
  @support_case = SupportCase.find(params[:support_case_id]) if params[:support_case_id].present?
  results = SupportCaseParticipant.find_participants(@support_case, params)
  render json: results
end

#participantsvoid

This method returns an undefined value.

Renders the participant management form for the support case.



148
149
150
151
# File 'app/controllers/support_cases_controller.rb', line 148

def participants
  set_support_case
  authorize!(:update, @support_case)
end

#quotesvoid

This method returns an undefined value.

Renders the quote-linking form for the support case.



132
133
134
135
# File 'app/controllers/support_cases_controller.rb', line 132

def quotes
  set_support_case
  authorize!(:update, @support_case)
end

#reopenedvoid

This method returns an undefined value.

Renders the case-reopening form (submission is confirm_reopened).



301
# File 'app/controllers/support_cases_controller.rb', line 301

def reopened; end

#roomsvoid

This method returns an undefined value.

Renders the room-linking form for the support case.



124
125
126
127
# File 'app/controllers/support_cases_controller.rb', line 124

def rooms
  set_support_case
  authorize!(:update, @support_case)
end

#showObject

GET /support_cases/1
GET /support_cases/1.json



63
64
65
66
67
68
69
70
71
72
# File 'app/controllers/support_cases_controller.rb', line 63

def show
  authorize! :read, @support_case
  set_context(@support_case)
  @pagy_activities, @activities = pagy(@support_case.activities.natural_order)

  respond_to do |format|
    format.html # show.html.erb
    format.json { render json: @support_case }
  end
end

#smart_servicesvoid

This method returns an undefined value.

Lists open smart-service support cases and the orders awaiting service delivery.



410
411
412
413
414
# File 'app/controllers/support_cases_controller.rb', line 410

def smart_services
  authorize! :read, SupportCase
  @pagy, @smart_services = pagy(SupportCase.where(case_type: 'SmartService').where.not(state: 'closed').order('support_cases.case_number desc'))
  @services = Order.contains_service_items.all_awaiting_deliveries
end

#tab_credit_memosvoid

This method returns an undefined value.

Renders the credit memos tab fragment.



478
479
480
481
482
483
484
# File 'app/controllers/support_cases_controller.rb', line 478

def tab_credit_memos
  authorize!(:read, @support_case)
  respond_to do |format|
    format.html { render layout: should_render_layout? }
    format.turbo_stream
  end
end

#tab_quotesvoid

This method returns an undefined value.

Renders the quotes tab fragment.



456
457
458
459
460
461
462
# File 'app/controllers/support_cases_controller.rb', line 456

def tab_quotes
  authorize!(:read, @support_case)
  respond_to do |format|
    format.html { render layout: should_render_layout? }
    format.turbo_stream
  end
end

#tab_reviewsvoid

This method returns an undefined value.

Renders the reviews tab fragment.



500
501
502
503
504
505
506
507
508
# File 'app/controllers/support_cases_controller.rb', line 500

def tab_reviews
  authorize!(:read, @support_case)
  @reviews_io_reviews = @support_case.reviews_io_reviews.to_a
  @company_review_url = @support_case.company_review_url
  respond_to do |format|
    format.html { render layout: should_render_layout? }
    format.turbo_stream
  end
end

#tab_rmasvoid

This method returns an undefined value.

Renders the RMAs tab fragment.



467
468
469
470
471
472
473
# File 'app/controllers/support_cases_controller.rb', line 467

def tab_rmas
  authorize!(:read, @support_case)
  respond_to do |format|
    format.html { render layout: should_render_layout? }
    format.turbo_stream
  end
end

#tab_roomsObject

Tab actions for turbo-tabs lazy loading



445
446
447
448
449
450
451
# File 'app/controllers/support_cases_controller.rb', line 445

def tab_rooms
  authorize!(:read, @support_case)
  respond_to do |format|
    format.html { render layout: should_render_layout? }
    format.turbo_stream
  end
end

#tab_state_historyvoid

This method returns an undefined value.

Renders the state history tab fragment.



513
514
515
516
517
518
519
# File 'app/controllers/support_cases_controller.rb', line 513

def tab_state_history
  authorize!(:read, @support_case)
  respond_to do |format|
    format.html { render layout: should_render_layout? }
    format.turbo_stream
  end
end

#tab_support_casesvoid

This method returns an undefined value.

Renders the related support cases tab fragment.



489
490
491
492
493
494
495
# File 'app/controllers/support_cases_controller.rb', line 489

def tab_support_cases
  authorize!(:read, @support_case)
  respond_to do |format|
    format.html { render layout: should_render_layout? }
    format.turbo_stream
  end
end

#tab_warehousevoid

This method returns an undefined value.

Renders the warehouse tab fragment.



524
525
526
527
528
529
530
# File 'app/controllers/support_cases_controller.rb', line 524

def tab_warehouse
  authorize!(:read, @support_case)
  respond_to do |format|
    format.html { render layout: should_render_layout? }
    format.turbo_stream
  end
end

#updateObject

PUT /support_cases/1
PUT /support_cases/1.json



247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
# File 'app/controllers/support_cases_controller.rb', line 247

def update
  authorize! :update, @support_case

  params[:support_case] ||= {}
  case params[:edit_action]
  when 'rooms'
    params[:support_case][:room_configuration_ids] ||= []
  when 'orders'
    params[:support_case][:order_ids] ||= []
  end
  @support_case.assign_attributes(params[:support_case])
  @support_case.support_case_participants.each(&:valid?)
  begin
    if @support_case.save
      case params[:commit]
      when /participants/i
        redirect_to participants_support_case_path(@support_case, return_path: edit_support_case_path(@support_case))
      when /rooms/i
        redirect_to rooms_support_case_path(@support_case, return_path: edit_support_case_path(@support_case))
      when /orders/i
        redirect_to orders_support_case_path(@support_case, return_path: edit_support_case_path(@support_case))
      else
        redirect_to_return_path_or_default(support_case_path(@support_case))
      end
    else
      render_action(params[:edit_action])
    end
  rescue ActiveRecord::RecordNotUnique
    @support_case.errors.add(:base, 'Same participant has been listed twice')
    render_action(params[:edit_action])
  end
end