Class: ContactsController

Inherits:
CrmController show all
Includes:
Controllers::DigitalAssetLinkable, Controllers::RequireTurboFrame
Defined in:
app/controllers/contacts_controller.rb

Overview

Controller: contacts.

Constant Summary collapse

TAB_ACTIONS =

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

%i[tab_main tab_contacts tab_digital_assets tab_merge tab_orders_spiff tab_certifications tab_communications tab_opportunities tab_profiling tab_spiff_orders tab_tickets tab_training].freeze

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 Controllers::RequireTurboFrame

require_in_pane_tab_frames, require_turbo_frame

Methods included from Controllers::DigitalAssetLinkable

#link_images, #link_videos

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

#createObject

PUT /customers/1/contacts



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'app/controllers/contacts_controller.rb', line 120

def create
  contact_params = params[:contact] || {}
  product_line_ids = contact_params.delete(:product_line_ids)
  @contact = Contact.new(contact_params)
  @contact.set_owner(@customer)
  if @contact.save
    @contact.update_product_interests product_line_ids
    PartyRecordTimezone.new.process(@contact)
    attempt_customer_lead_qualification
    redirect_to_return_path_or_default(polymorphic_path(@customer))
  else
    set_edit_fields
    render action: :new, status: :unprocessable_content
  end
end

#create_topic_follow_upvoid

This method returns an undefined value.

POST /contacts/:id/create_topic_follow_up — starts a follow-up
communication draft for a customer topic.



250
251
252
253
# File 'app/controllers/contacts_controller.rb', line 250

def create_topic_follow_up
  com = CustomerTopic.create_communication(@contact, current_user)
  redirect_to edit_communication_path(com)
end

#destroyObject

DELETE /customers/1/contacts/1



210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'app/controllers/contacts_controller.rb', line 210

def destroy
  parent_customer = @contact.customer || @contact.supplier
  @contact.deactivate
  @contact.reload
  if @contact.ok_to_delete?
    res = @contact.destroy
    if res
      flash[:info] = 'Contact was deleted.'
    else
      flash[:error] = "Contact could not be deleted: #{@contact.errors_to_s}"
    end
    if parent_customer.present?
      redirect_to polymorphic_path(parent_customer)
    else
      redirect_to '/'
    end
  else
    flash[:warning] = "Contact was marked inactive but the contact cannot be deleted because it is still referenced: #{@contact.dependents.inspect}"
    redirect_to contact_path(@contact)
  end
end

#do_training_requestvoid

This method returns an undefined value.

POST /contacts/:id/do_training_request — reconciles requested training
topics against the submitted selection (adds new, removes unselected).



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'app/controllers/contacts_controller.rb', line 291

def do_training_request
  topic_ids = params[:topics] || []
  # Unselected/unchecked value which were previously requested are now destroyed
  removable_topics = @contact.contact_training_topics.where(state: 'requested').where.not(topic_id: topic_ids)
  removed_topics_size = removable_topics.size
  removable_topics.destroy_all
  # New topic are linked
  new_topics = []
  ContactTrainingTopic.transaction do
    topic_ids.each do |topic_id|
      new_topic = @contact.contact_training_topics.where(topic_id: topic_id).first_or_create(state: 'requested')
      new_topic.request
      new_topics << new_topic
    end
  end
  flash[:info] =
    "#{new_topics.size} training requests in queue. #{removed_topics_size} removed. "

  redirect_to contact_path(@contact, tab: "training")
end

#editObject

GET /customers/1/contacts/1/edit



112
113
114
115
116
117
# File 'app/controllers/contacts_controller.rb', line 112

def edit
  set_edit_fields
  # @show_spiff_fields = @customer.can_spiff?
  @show_spiff_fields = true
  render_edit_action
end

#indexObject

GET /customers/1/contacts



15
16
17
18
19
20
21
22
# File 'app/controllers/contacts_controller.rb', line 15

def index
  unless @customer
    redirect_to(crm_home_path,
                flash: { error: 'No customer context specified' }) && return
  end

  @contacts = @customer.contacts.active
end

#mergeObject

POST /contacts/1/merge



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'app/controllers/contacts_controller.rb', line 154

def merge
  survivor = Contact.find(params[:survivor_id])
  cm = Merger::ContactMerger.new(survivor, @contact)
  report = { execution_errors: [], success: true, messages: [] }

  if survivor == @contact
    report[:success] = false
    msg = "Cannot merge #{@contact.full_name} #{@contact.id} into #{survivor.full_name} #{survivor.id} because they are equal."
    report[:execution_errors] << msg
  else
    begin
      cm.perform_merge!
      msg = "Merged #{@contact.full_name} #{@contact.id} into #{survivor.full_name} #{survivor.id}."
      report[:messages] << msg
    rescue StandardError => e
      report[:success] = false
      msg = "Cannot merge #{@contact.full_name} #{@contact.id} into #{survivor.full_name} #{survivor.id} due to errors: #{e}"
      report[:execution_errors] << msg
    end
  end

  if report[:success]
    flash[:info] = "Merge successful! #{report[:messages].join(', ')}"
    redirect_to contact_path(survivor)
  else
    flash[:error] = "Merge not successful! #{report[:execution_errors].join(', ')}"
    redirect_to contact_path(@contact)
  end
end

#merge_addressesvoid

This method returns an undefined value.

POST /contacts/:id/merge_addresses — runs Merger::AddressMerger to
dedupe the contact's addresses.



45
46
47
48
49
50
51
52
53
54
# File 'app/controllers/contacts_controller.rb', line 45

def merge_addresses
  am = Merger::AddressMerger.new(@contact.id)
  begin
    am.perform_merge!
    flash[:info] = 'Merge check completed.'
  rescue StandardError => e
    flash[:error] = "Merge could not be completed. #{e.message}"
  end
  redirect_to contact_addresses_path(@contact)
end

#merge_into_customerObject

POST /contacts/1/merge_into_customer



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'app/controllers/contacts_controller.rb', line 185

def merge_into_customer
  survivor = Customer.find(params[:survivor_id])
  cm = Merger::ContactToCustomerMerger.new(survivor, @contact)
  report = { execution_errors: [], success: true, messages: [] }

  begin
    cm.perform_merge!
    msg = "Merged #{@contact.full_name} #{@contact.id} into #{survivor.full_name} #{survivor.id}."
    report[:messages] << msg
  rescue StandardError => e
    report[:success] = false
    msg = "Cannot merge #{@contact.full_name} #{@contact.id} into #{survivor.full_name} #{survivor.id} due to errors: #{e}"
    report[:execution_errors] << msg
  end

  if report[:success]
    flash[:info] = "Merge successful! #{report[:messages].join(', ')}"
    redirect_to customer_path(survivor)
  else
    flash[:error] = "Merge not successful! #{report[:execution_errors].join(', ')}"
    redirect_to contact_path(@contact)
  end
end

#newObject

GET /customers/1/contacts/new



106
107
108
109
# File 'app/controllers/contacts_controller.rb', line 106

def new
  @contact = @customer.contacts.new
  set_edit_fields
end

#orders_tabvoid

This method returns an undefined value.



69
70
71
72
73
74
75
76
77
78
# File 'app/controllers/contacts_controller.rb', line 69

def orders_tab
  @pagy_orders, @orders = pagy(@contact.orders.non_credit.includes(:payments, :opportunity,
                                                { shipping_address: :state }))
  respond_to do |format|
    format.html { render layout: false }
    format.turbo_stream do
      render turbo_stream: turbo_stream.replace('orders-container', partial: 'orders/index', locals: { orders: @orders, pagy: @pagy_orders, context_object: @contact })
    end
  end
end

#restorevoid

This method returns an undefined value.

PATCH /contacts/:id/restore — reactivates an inactive contact.



235
236
237
238
239
240
241
242
243
244
# File 'app/controllers/contacts_controller.rb', line 235

def restore
  success = @contact.update(inactive: false)
  if success
    flash[:info] = "Contact #{@contact.full_name} was marked active."
  else
    flash[:error] =
      "Contact #{@contact.full_name} could not be restored. #{@contact.errors_to_s}"
  end
  redirect_to contact_path(@contact)
end

#showObject

GET /customers/1/contacts/1



25
26
27
28
29
30
31
32
33
34
# File 'app/controllers/contacts_controller.rb', line 25

def show
  @manager = @contact.manager
  @managed_employees = @contact.managed_employees
  respond_to do |format|
    format.html do
      # Queue profile image lookup if not already set (lazy backfill)
      enqueue_profile_image_lookup(@contact)
    end
  end
end

#tab_certificationsvoid

This method returns an undefined value.



271
272
273
274
275
# File 'app/controllers/contacts_controller.rb', line 271

def tab_certifications
  @certifications = @contact.certifications
  @course_enrollments = @contact.course_enrollments
  render layout: false
end

#tab_communicationsvoid

This method returns an undefined value.



93
94
95
96
97
98
# File 'app/controllers/contacts_controller.rb', line 93

def tab_communications
  @pagy_communications, @communications = pagy(@contact.inbound_communications)
  render partial: '/communications/index',
         locals: { communications: @communications, ajaxify: true, ajax_controller: 'contacts',
                   ajax_action: 'tab_communications', ajax_id: @contact.id }
end

#tab_contactsvoid

This method returns an undefined value.



322
323
324
325
326
327
328
# File 'app/controllers/contacts_controller.rb', line 322

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

#tab_digital_assetsvoid

This method returns an undefined value.



331
332
333
334
335
336
337
# File 'app/controllers/contacts_controller.rb', line 331

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

#tab_mainObject

Tab actions for turbo-tabs lazy loading



313
314
315
316
317
318
319
# File 'app/controllers/contacts_controller.rb', line 313

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

#tab_mergevoid

This method returns an undefined value.



88
89
90
# File 'app/controllers/contacts_controller.rb', line 88

def tab_merge
  render partial: 'merge_tab'
end

#tab_opportunitiesvoid

This method returns an undefined value.



57
58
59
60
61
62
63
64
65
66
# File 'app/controllers/contacts_controller.rb', line 57

def tab_opportunities
  @pagy_opportunities, @opportunities = pagy(@contact.opportunities.includes([{ quotes: :active_orders },
                                                                              :room_configurations, :orders]), page: params[:opportunities_page])
  render partial: 'opportunities_tab',
         locals: { opportunities: @opportunities,
                   ajaxify: true,
                   ajax_controller: 'contacts',
                   ajax_action: 'tab_opportunities',
                   ajax_id: @contact.id }
end

#tab_orders_spiffvoid

This method returns an undefined value.



348
349
350
351
352
353
354
355
# File 'app/controllers/contacts_controller.rb', line 348

def tab_orders_spiff
  authorize!(:read, @contact)
  @orders = @contact.spiff_orders.limit(20).offset(((params[:orders_page] || 1).to_i - 1) * 20)
  respond_to do |format|
    format.html { render layout: should_render_layout? }
    format.turbo_stream
  end
end

#tab_profilingvoid

This method returns an undefined value.



37
38
39
# File 'app/controllers/contacts_controller.rb', line 37

def tab_profiling
  render layout: false
end

#tab_spiff_ordersvoid

This method returns an undefined value.



81
82
83
84
85
# File 'app/controllers/contacts_controller.rb', line 81

def tab_spiff_orders
  render partial: 'spiff_enrollments/orders_tab',
         locals: { orders: @contact.spiff_orders, ajaxify: true,
                   ajax_controller: 'contacts', ajax_action: 'tab_spiff_orders', ajax_id: @contact.id }
end

#tab_ticketsvoid

This method returns an undefined value.



101
102
103
# File 'app/controllers/contacts_controller.rb', line 101

def tab_tickets
  render layout: false
end

#tab_trainingvoid

This method returns an undefined value.



256
257
258
259
260
261
262
263
264
265
266
267
268
# File 'app/controllers/contacts_controller.rb', line 256

def tab_training
  params[:q] = { state_in: %w[assigned requested certified] } if params[:q].blank?
  @q = @contact.contact_training_topics.joins(topic: :topic_category)
               .merge(Topic.sorted).ransack(params[:q])
  @contact_training_topics = @q.result
  @contact_training_topics_grouped = @contact_training_topics.group_by do |pt|
    pt.topic.topic_category
  end
  respond_to do |format|
    format.html { render layout: false }
    format.turbo_stream
  end
end

#training_requestvoid

This method returns an undefined value.

GET /contacts/:id/training_request — available training topics grouped
by category, for the request form.



281
282
283
284
285
# File 'app/controllers/contacts_controller.rb', line 281

def training_request
  available_topics = Topic.active.where(applies_to_contact: true, training_module: true).sorted
  @topics_indexed = @contact.contact_training_topics.joins(:topic).merge(available_topics).index_by(&:topic_id)
  @topics_grouped = available_topics.group_by(&:topic_category)
end

#updateObject

PUT /customers/1/contacts/1



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'app/controllers/contacts_controller.rb', line 137

def update
  contact_params = params[:contact]
  product_line_ids = contact_params&.delete(:product_line_ids)
  if contact_params.blank? && product_line_ids.blank?
    redirect_to_return_path_or_default contact_path(@contact)
  elsif @contact.update(contact_params)
    @contact.update_product_interests product_line_ids
    PartyRecordTimezone.new.process(@contact)
    attempt_customer_lead_qualification
    redirect_to_return_path_or_default contact_path(@contact)
  else
    set_edit_fields
    render_edit_action(status: :unprocessable_content)
  end
end