Class: CustomersController

Overview

Controller: customers.

Constant Summary

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

Constants included from Www::SeoHelper

Www::SeoHelper::AWARDS, Www::SeoHelper::CA_ADDRESS, Www::SeoHelper::CA_BUSINESS_HOURS, Www::SeoHelper::CA_CONTACT_POINT, Www::SeoHelper::CA_CURRENCIES, Www::SeoHelper::CA_DESCRIPTION, Www::SeoHelper::CA_FOUNDING_DATE, Www::SeoHelper::CA_GLOBAL_LOCATION_NUMBER, Www::SeoHelper::CA_LEGAL_NAME, Www::SeoHelper::CA_LOCAL_BUSINESS, Www::SeoHelper::CA_ONLINE_STORE, Www::SeoHelper::CA_RETURN_POLICY, Www::SeoHelper::CA_SALES_DEPARTMENT, Www::SeoHelper::CA_SERVICE_AREA, Www::SeoHelper::CA_URL, Www::SeoHelper::CA_VAT_ID, Www::SeoHelper::CA_WAREHOUSE_DEPARTMENT, Www::SeoHelper::CA_WAREHOUSE_HOURS, Www::SeoHelper::COMPANY_EMAIL, Www::SeoHelper::COMPANY_LOGO, Www::SeoHelper::COMPANY_NAME, Www::SeoHelper::COMPANY_SLOGAN, Www::SeoHelper::EXPERTISE, Www::SeoHelper::FAX_NUMBER, Www::SeoHelper::GS1_COMPANY_PREFIX, Www::SeoHelper::ISO6523_CODE, Www::SeoHelper::PAYMENT_METHODS, Www::SeoHelper::PHONE_NUMBER, Www::SeoHelper::PRIMARY_NAICS, Www::SeoHelper::REFUND_TYPE, Www::SeoHelper::RETURN_FEES, Www::SeoHelper::RETURN_METHOD, Www::SeoHelper::RETURN_POLICY_CATEGORY, Www::SeoHelper::SECONDARY_NAICS, Www::SeoHelper::SOCIAL_PROFILES, Www::SeoHelper::US_ADDRESS, Www::SeoHelper::US_BUSINESS_HOURS, Www::SeoHelper::US_CONTACT_POINT, Www::SeoHelper::US_CURRENCIES, Www::SeoHelper::US_DESCRIPTION, Www::SeoHelper::US_FOUNDING_DATE, Www::SeoHelper::US_GLOBAL_LOCATION_NUMBER, Www::SeoHelper::US_IMAGE, Www::SeoHelper::US_LEGAL_NAME, Www::SeoHelper::US_LOCAL_BUSINESS, Www::SeoHelper::US_ONLINE_STORE, Www::SeoHelper::US_RETURN_POLICY, Www::SeoHelper::US_SALES_DEPARTMENT, Www::SeoHelper::US_SERVICE_AREA, Www::SeoHelper::US_TAX_ID, Www::SeoHelper::US_URL, Www::SeoHelper::US_WAREHOUSE_DEPARTMENT, Www::SeoHelper::US_WAREHOUSE_HOURS

Constants included from IconHelper

IconHelper::CUSTOM_ICON_MAP, IconHelper::CUSTOM_SVG_DIR, IconHelper::DEFAULT_FAMILY

Instance Method Summary collapse

Methods included from Controllers::DigitalAssetLinkable

#link_images, #link_videos

Methods included from Controllers::Workflowable

#render_workflow_error_stream, #render_workflow_success_stream, #workflow_action, #workflow_action_complete

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

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?, #stamp_impersonation_context, #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, #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 ApplicationHelper

#better_number_to_currency, #check_force_logout, #check_or_cross, #check_or_times, #embedded_tab_frame_id, #error_messages, #general_disclaimer_on_product_installation_and_local_codes, #gridjs_from_html_table, #gridjs_table, #is_wy_ip, #line_break, #parent_layout, #pass_or_fail, #render_error_messages_list, #render_video_card, #resolved_auth_form_turbo_frame, #return_path_or, #safe_css_color, #set_return_path_if_present, #set_section_if_present, #tab_frame_id, #to_underscore, #track_page?, #turbo_section_wrapper, #turbo_tabs_request?, #url_on_same_domain_as_request, #widget_index_daily_focus_index_path, #working_hours?, #yes_or_no, #yes_or_no_highlighted, #yes_or_no_with_check_or_cross, #youtube_video

Methods included from UppyUploaderHelper

#file_uploader, #image_uploader, #large_file_uploader_s3, #lead_sketch_uploader, #rma_image_uploader, #rma_image_uploader_s3, #uppy_uploader, #video_uploader

Methods included from Www::ImagesHelper

#image_asset_tag, #image_asset_url

Methods included from Www::SeoHelper

#add_page_schema, #add_webpage_schema, #canada?, #company_social_links, #ensure_context_json, #json_ld_script_tag, #local_business_schema, #online_store_id, #online_store_schema, #page_main_entity, #page_main_entity_json, #render_auto_collection_page_schema, #render_collection_page_schema, #render_local_business_schema, #render_online_store_schema, #render_page_schemas, #render_page_video_schemas, #render_webpage_schema, #render_webpage_schema_with_collections, #usa?

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?

Methods included from IconHelper

#account_nav_icon, #fa_icon, #star_rating_html

Instance Method Details

#accounting_actionObject



525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
# File 'app/controllers/customers_controller.rb', line 525

def accounting_action
  form_action, template_id = params[:transmit_instructions]&.split('|')
  case form_action
  when 'receipt'
    redirect_to new_receipt_path('credit_memos' => params['credit_memos'], 'invoices' => params['invoices'])
  when 'payment'
    redirect_to new_payment_receipts_path('customer_id' => @customer.id, 'invoices' => params['invoices'], 'credit_memos' => params['credit_memos'], :return_path => customer_path(@customer))
  when 'transmit'
    if params[:invoices].blank? && params[:credit_memos].blank? && params[:soas].blank?
      flash[:error] = 'Please select documents to transmit'
      redirect_to customer_path(@customer, tab: 'accounting')
    else
      begin
        adt = AccountingDocumentTransmitter.new(
          { INV: params['invoices'], CM: params['credit_memos'], SOA: params['soas'] },
          customer: @customer,
          template_id: template_id
        )
        co = adt.build_communication
        if co.save
          redirect_to edit_communication_path(co.id)
        else
          flash[:error] = "Problem creating communication. #{co.errors_to_s}"
          redirect_to customer_path(@customer, tab: 'accounting')
        end
      rescue Dragonfly::Job::Fetch::NotFound => e
        flash[:error] = "One or more document PDFs could not be found. The invoice PDF may need to be regenerated. (#{e.message})"
        redirect_to customer_path(@customer, tab: 'accounting')
      end
    end
  else
    flash[:error] = 'Unrecognized or unspecified action'
    redirect_to customer_path(@customer, tab: 'accounting')
  end
end

#add_to_campaignObject



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'app/controllers/customers_controller.rb', line 139

def add_to_campaign
  campaign_id = params.dig(:new_campaign, :campaign_id)
  if campaign_id.blank?
    flash[:error] = 'You must select a campaign'
  else
    campaign = Campaign.find(campaign_id)
    res = campaign.add_customer(@customer)
    if res.subscribed?
      flash[:info] = "Customer added to #{campaign.name}"
    else
      flash[:error] = "Customer not added to #{campaign.name}, error: #{res.message}"
    end
  end
  redirect_to marketing_customer_path(@customer)
end

#auto_increase_available_creditObject



665
666
667
668
669
670
671
672
673
674
675
# File 'app/controllers/customers_controller.rb', line 665

def auto_increase_available_credit
  order = params[:order_id].nil? ? nil : Order.find(params[:order_id])
  if !@customer.has_terms?
    flash[:error] = 'Credit limit increase only available to customers with terms'
  elsif @customer.auto_increase_available_credit(params[:amount].to_d, order)
    flash[:info] = "Request approved. Available credit increased to #{number_to_currency(@customer.available_credit)}"
  else
    flash[:info] = 'Unable to automatically increase customer credit as some criteria were not met. Request has been sent to Accounting for approval.'
  end
  redirect_to_return_path_or_default(customer_path(@customer))
end

#available_creditObject



252
# File 'app/controllers/customers_controller.rb', line 252

def available_credit; end

#check_credit_limitObject



677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
# File 'app/controllers/customers_controller.rb', line 677

def check_credit_limit
  params[:check_credit_limit_params] ||= { order_amount: '' }
  @check_credit_limit_params = OpenStruct.new(params[:check_credit_limit_params])
  @customer = Customer.find(params[:id])

  quote = Quote.find(params[:check_credit_limit_params][:quote_id]) if params[:check_credit_limit_params][:quote_id].present?
  order_amount = params[:check_credit_limit_params][:order_amount]
  @amount = if quote.present?
              quote.total
            else
              (order_amount.present? ? order_amount.to_f : 0)
            end

  @result = {}
  @result = @customer.request_credit(@amount) if @amount.positive?
end

#closedObject



656
657
658
659
660
661
662
663
# File 'app/controllers/customers_controller.rb', line 656

def closed
  if @customer.sales_reps_ids.present? || @customer.can_closed?
    redirect_to new_customer_customer_drop_event_path(customer_id: @customer.id)
  else
    flash[:info] = 'Customer is not in a state that can be closed'
    redirect_to customer_path(@customer)
  end
end

#confirm_account_teamObject



601
602
603
604
605
606
607
608
# File 'app/controllers/customers_controller.rb', line 601

def 
  rep_assigner = Customer::SalesRepAssigner.new(@customer)
  if rep_assigner.assign_reps()
    redirect_to @customer, notice: 'Account Team Saved'
  else
    render :edit_account_team, status: :unprocessable_content
  end
end

#confirm_lead_protectionObject



635
636
637
638
639
640
641
# File 'app/controllers/customers_controller.rb', line 635

def confirm_lead_protection
  if @customer.update(params[:customer] || {})
    redirect_to @customer, notice: 'Lead Protection Saved'
  else
    render :edit_lead_protection, status: :unprocessable_content
  end
end

#confirm_notification_channelsObject



622
623
624
625
626
627
628
629
630
631
# File 'app/controllers/customers_controller.rb', line 622

def confirm_notification_channels
  if @customer.update(params[:customer] || {})
    redirect_to @customer, notice: 'Notification Channels Saved'
  else
    render :edit_notification_channels, status: :unprocessable_content
  end
rescue ActiveRecord::RecordNotUnique
  @customer.errors.add(:base, 'A duplicate notification channel already exists. Please refresh and try again.')
  render :edit_notification_channels, status: :unprocessable_content
end

#confirm_pricingObject



590
591
592
593
594
595
596
597
# File 'app/controllers/customers_controller.rb', line 590

def confirm_pricing
  if @customer.update(customer_pricing_params)
    redirect_to @customer, notice: 'Pricing Selection Saved'
  else
    set_program_pricings
    render :edit_pricing, status: :unprocessable_content
  end
end

#confirm_profileObject



573
574
575
576
577
578
579
580
581
582
583
584
# File 'app/controllers/customers_controller.rb', line 573

def confirm_profile
  res = Customer::UpdateProfile.new.process(@customer, customer_profile_params)
  if res.success
    if res.profile_changed
      Customer::PricingProgramAssigner.new(customer_id: @customer.id).update_customer
      # redirect_to questions_customer_pricing_program_assigner_path(@customer), notice: 'Profile updated, please confirm pricing'
    end
    redirect_to @customer
  else
    render :edit_profile, status: :unprocessable_content
  end
end

#confirm_structureObject



612
613
614
615
616
617
618
# File 'app/controllers/customers_controller.rb', line 612

def confirm_structure
  if @customer.update(params[:customer] || {})
    redirect_to @customer, notice: 'Parent Org Saved'
  else
    render :edit_parent_org, status: :unprocessable_content
  end
end

#createObject



68
69
70
# File 'app/controllers/customers_controller.rb', line 68

def create
  raise 'Legacy Method, should not be used'
end

#create_iq_accessory_preferenceObject



317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'app/controllers/customers_controller.rb', line 317

def create_iq_accessory_preference
  item = exclusive_item_group = nil
  item = Item.find(params[:iq_accessory_filter][:item_id]) if params[:iq_accessory_filter][:item_id].present?
  exclusive_item_group = ExclusiveItemGroup.find(params[:iq_accessory_filter][:exclusive_item_group_id]) if params[:iq_accessory_filter][:exclusive_item_group_id].present?
  item_or_exclusive_item_group = item || exclusive_item_group
  @iq_accessory_filter = IqAccessoryFilter.create_single_customer_iq_accessory_filter(@customer, item_or_exclusive_item_group, params[:iq_accessory_filter][:comment]) if item_or_exclusive_item_group
  if @iq_accessory_filter
    redirect_to(iq_accessory_preferences_customer_path(@customer))
  else
    @iq_accessory_filter ||= IqAccessoryFilter.new(params[:iq_accessory_filter])
    render action: 'new_iq_accessory_preference', status: :unprocessable_content
  end
end

#create_topic_follow_upObject



561
562
563
564
565
566
567
568
569
# File 'app/controllers/customers_controller.rb', line 561

def create_topic_follow_up
  com = CustomerTopic.create_communication(@customer, current_user)
  if com.persisted?
    redirect_to edit_communication_path(com)
  else
    flash[:error] = "There was a problem creating the communication. #{com.errors_to_s}"
    redirect_to customer_path(@customer, tab: 'topics')
  end
end

#delete_iq_accessory_preferenceObject



344
345
346
347
348
# File 'app/controllers/customers_controller.rb', line 344

def delete_iq_accessory_preference
  @iq_accessory_filter = IqAccessoryFilter.for_customer(@customer).is_auto_generated_only.find(params[:iq_accessory_filter_id])
  @iq_accessory_filter&.destroy
  redirect_to(iq_accessory_preferences_customer_path(@customer))
end

#destroyObject

DELETE /customers/1



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'app/controllers/customers_controller.rb', line 93

def destroy
  if @customer.ok_to_delete?
    @customer.destroy
    redirect_to(customers_url)
  else
    deps = +''
    @customer.opportunities.each do |o|
      break if deps.length > 200

      deps << "<a href='#{opportunity_path(o)}'>opportunity: #{o.name}</a> "
    end
    @customer.quotes.each do |q|
      break if deps.length > 200

      deps << "<a href='#{quote_path(q)}'>quote: #{q.reference_number}</a> "
    end
    @customer.orders.each do |o|
      break if deps.length > 200

      deps << "<a href='#{order_path(o)}'>order: #{o.reference_number}</a> "
    end
    deps << '...' if deps.length > 200
    flash[:error] = "Customer cannot be deleted because it is referenced by: #{deps}"
    redirect_to customer_path(@customer)
  end
end

#do_auto_assign_sales_repObject



643
644
645
646
647
648
649
650
651
652
653
654
# File 'app/controllers/customers_controller.rb', line 643

def do_auto_assign_sales_rep
  # Remove the do not assign flag if this action is explicitely called
  @customer.do_not_auto_assign = false
  rep_assigner = Customer::SalesRepAssigner.new(@customer, force_assign: true)
  if rep_assigner.auto_assign
    flash[:info] = 'Account team assigned'
    redirect_to customer_path(@customer)
  else
    flash[:error] = 'Could not assign sales rep to account, contact system administrator'
    redirect_to (@customer)
  end
end

#do_mergeObject



196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'app/controllers/customers_controller.rb', line 196

def do_merge
  load_customers || return
  job_options = { customer_ids: [@survivor.id, @duplicate.id], notify_email: current_user.email }.stringify_keys
  job_id = CustomerMergeWorker.perform_async(job_options)
  if job_id.blank?
    if (job_id = BackgroundJobStatus.search(worker_klass: 'CustomerMergeWorker', args: job_options).first&.dig(:job_id))
      flash[:warning] = 'A similar job was already enqueued, watching status of that job now'
    end
  end
  if job_id.present?
    redirect_to job_path(job_id)
  else
    flash[:warning] = 'There was a problem queuing the job'
    redirect_to_return_path_or_default customer_path(@survivor.id)
  end
end

#drop_eventsObject



266
# File 'app/controllers/customers_controller.rb', line 266

def drop_events; end

#duplicatesObject Also known as: complete_qualification



495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
# File 'app/controllers/customers_controller.rb', line 495

def duplicates
  if params[:customer_id].present?
    @possible_duplicates = Customer.where(id: params[:customer_id])
    flash[:error] = "Cannot merge into itself.  This is the same customer you're giving me." if @possible_duplicates.first == @customer
  else
    @possible_duplicates = Query::CustomerFinder.new(customer: @customer).find_duplicates
  end

  if @customer.can_complete_qualification? || @customer.primary_sales_rep_id.nil?
    # Preemptive allocation of sales rep, takes a slot but that's how we go
    rep_options = Customer::SalesRepAssigner.new(@customer).determine_sales_rep
    @customer.primary_sales_rep_id = rep_options[:primary_sales_rep_id] if rep_options.present?
  end

  if request.xhr?
    render partial: 'duplicates'
  else
    render :duplicates
  end
end

#editObject

GET /customers/1/edit



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

def edit
  if @customer
    @source_id = @customer.source_id
    set_edit_variables
    render_edit_action(status: :ok)
  else
    flash[:error] = 'Customer does not exist.'
    redirect_to customers_path
  end
end

#edit_account_teamObject



599
# File 'app/controllers/customers_controller.rb', line 599

def ; end

#edit_accounting_informationObject



270
271
272
273
274
275
276
277
278
279
# File 'app/controllers/customers_controller.rb', line 270

def edit_accounting_information
  @tax_exemptions = @customer.tax_exemptions
  respond_to do |format|
    if can? :edit_accounting_info, Customer
      format.html
    else
      format.html { render action: 'show_accounting_information' }
    end
  end
end

#edit_catalogObject



368
369
370
371
# File 'app/controllers/customers_controller.rb', line 368

def edit_catalog
  @new_catalog = Catalog.find(params[:new_catalog_id]) if params[:new_catalog_id]
  @customer.catalog = @new_catalog
end

#edit_default_addressObject



443
444
445
# File 'app/controllers/customers_controller.rb', line 443

def edit_default_address
  authorize!(:update, @customer)
end

#edit_iq_accessory_preferenceObject



331
332
333
# File 'app/controllers/customers_controller.rb', line 331

def edit_iq_accessory_preference
  @iq_accessory_filter = IqAccessoryFilter.for_customer(@customer).is_auto_generated_only.find(params[:iq_accessory_filter_id])
end

#edit_lead_protectionObject



633
# File 'app/controllers/customers_controller.rb', line 633

def edit_lead_protection; end

#edit_notification_channelsObject



620
# File 'app/controllers/customers_controller.rb', line 620

def edit_notification_channels; end

#edit_pricingObject



586
587
588
# File 'app/controllers/customers_controller.rb', line 586

def edit_pricing
  set_program_pricings
end

#edit_profileObject



571
# File 'app/controllers/customers_controller.rb', line 571

def edit_profile; end

#edit_shipping_preferencesObject



299
# File 'app/controllers/customers_controller.rb', line 299

def edit_shipping_preferences; end

#edit_structureObject



610
# File 'app/controllers/customers_controller.rb', line 610

def edit_structure; end

#indexObject

GET /customers



17
18
19
# File 'app/controllers/customers_controller.rb', line 17

def index
  redirect_to employee_dashboard_path(current_user)
end

#iq_accessory_preferencesObject



309
310
311
# File 'app/controllers/customers_controller.rb', line 309

def iq_accessory_preferences
  @iq_accessory_filters = IqAccessoryFilter.for_customer(@customer).is_auto_generated_only
end

#lookup_emailsObject



409
410
411
412
413
# File 'app/controllers/customers_controller.rb', line 409

def lookup_emails
  @customer = Customer.find(params[:customer_id])
  results = @customer.contacts_and_self_contact_points_by_category(ContactPoint::EMAIL).distinct.order(:detail).pluck(:detail)
  render json: TomSelect.format_json_results(self, results, params[:page], params[:per_page])
end

#lookup_faxesObject



421
422
423
424
425
# File 'app/controllers/customers_controller.rb', line 421

def lookup_faxes
  @customer = Customer.find(params[:customer_id])
  results = @customer.contacts_and_self_contact_points_by_category(ContactPoint::FAX).distinct.order(:detail).pluck(:detail)
  render json: TomSelect.format_json_results(self, results, params[:page], params[:per_page])
end

#lookup_phonesObject



415
416
417
418
419
# File 'app/controllers/customers_controller.rb', line 415

def lookup_phones
  @customer = Customer.find(params[:customer_id])
  results = @customer.contacts_and_self_contact_points_by_category([ContactPoint::PHONE, ContactPoint::CELL]).distinct.order(:detail).pluck(:detail)
  render json: TomSelect.format_json_results(self, results, params[:page], params[:per_page])
end

#marketingObject



264
# File 'app/controllers/customers_controller.rb', line 264

def marketing; end

#mergeObject



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
183
184
185
186
187
188
189
190
191
192
193
194
# File 'app/controllers/customers_controller.rb', line 155

def merge
  @error_count = 0
  first_customer_id = params.dig(:merge, :first_customer_id) || params.dig(:first_customer_id)
  second_customer_id = params.dig(:merge, :second_customer_id) || params.dig(:second_customer_id)
  @customer1 = Customer.find(first_customer_id)
  @customer2 = Customer.find(second_customer_id)

  if @customer1.catalog_id == @customer2.catalog_id
    @customer1_inventory = Merger::CustomerMerger.inventory(@customer1)
    @customer1_errors = []
    @customer1.orders.all_awaiting_deliveries.each do |order|
      @customer1_errors << { record_type: order.class.name, record_id: order.id, record_description: order.state, errors: ['Order is under shipping states'], record: order }
    end
    # @customer1_inventory.each do |r|
    #   @customer1_errors += 1 unless r.valid?
    # end

    @customer2_inventory = Merger::CustomerMerger.inventory(@customer2)
    @customer2_errors = []
    @customer2.orders.all_awaiting_deliveries.each do |order|
      @customer2_errors << { record_type: order.class.name, record_id: order.id, record_description: order.state, errors: ['Order is under shipping states'], record: order }
    end
    # @customer2_inventory.each do |r|
    #   @customer2_errors += 1 unless r.valid?
    # end

    @error_count = @customer1_errors.size + @customer2_errors.size
    if params[:survivor_customer_id].present?
      @customer1_selected = params[:survivor_customer_id] == @customer1.id
      @customer2_selected = params[:survivor_customer_id] == @customer2.id
    elsif @customer1.created_at < @customer2.created_at
      @customer1_selected = true
    else
      @customer2_selected = true
    end
  else
    flash[:error] = 'Customer master and duplicate have different catalogs, resolve catalog discrepancy first'
    redirect_to_return_path_or_default customer_path(@customer1)
  end
end

#merge_addressesObject



230
231
232
233
234
235
236
237
238
239
# File 'app/controllers/customers_controller.rb', line 230

def merge_addresses
  am = Merger::AddressMerger.new(@customer.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_return_path_or_default customer_addresses_path(@customer)
end

#merge_contact_pointsObject



241
242
243
244
245
246
247
248
249
250
# File 'app/controllers/customers_controller.rb', line 241

def merge_contact_points
  cpm = Merger::ContactPointMerge.new(@customer.id)
  begin
    cpm.perform_merge!
    flash[:info] = 'Merge check completed.'
  rescue StandardError => e
    flash[:error] = "Merge could not be completed. #{e.message}"
  end
  redirect_to_return_path_or_default customer_contacts_path(@customer)
end

#newObject

GET /customers/new



52
53
54
# File 'app/controllers/customers_controller.rb', line 52

def new
  redirect_to new_customer_identify_path
end

#new_iq_accessory_preferenceObject



313
314
315
# File 'app/controllers/customers_controller.rb', line 313

def new_iq_accessory_preference
  @iq_accessory_filter = IqAccessoryFilter.new
end

#promotionsObject



464
465
466
467
468
# File 'app/controllers/customers_controller.rb', line 464

def promotions
  qb = Coupon::QueryBuilder.new(Coupon.active.tier3)
  @coupons = qb.for_specific_customer(@customer).query.order('coupons.expiration_date desc')
  render layout: false
end

#radiant_rewardsObject



384
# File 'app/controllers/customers_controller.rb', line 384

def radiant_rewards; end

#recalculate_profiling_informationObject



350
351
352
# File 'app/controllers/customers_controller.rb', line 350

def recalculate_profiling_information
  @customer.recalculate_profiling_information
end

#release_from_lead_qualificationObject



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'app/controllers/customers_controller.rb', line 213

def release_from_lead_qualification
  if @customer.can_complete_qualification?
    if (psr_id = params.dig(:customer, :primary_sales_rep_id))
      # preassign rep
      Customer::SalesRepAssigner.new(@customer).assign_reps(primary_sales_rep_id: psr_id)
    end
    res = @customer.complete_qualification # this will allow the updating
    if res
      flash[:info] = +'Customer was released from lead qualification stage. '
      flash[:info] << "#{@customer.orders.held.length} order(s) are currently on hold, please review them for correctness and release." if @customer.orders.held.present?
    else
      flash[:error] = "Customer was not released from lead qualification stage. #{@customer.errors_to_s}"
    end
  end
  redirect_to customer_path(@customer)
end

#sales_priority_index_analysisObject



386
387
388
389
# File 'app/controllers/customers_controller.rb', line 386

def sales_priority_index_analysis
  @results = Customer::SalesPriorityCalculation.score_single_customer(@customer.id)
  render layout: 'crm/crm'
end

#showvoid

This method returns an undefined value.

Shows the customer's details.
For HTML: preloads key associations, loads the customer's active contacts with contact points, and enqueues a profile-image lookup.
For JSON: renders the customer as JSON.

Raises:

  • (ActiveRecord::RecordNotFound)

    if the customer is not found.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'app/controllers/customers_controller.rb', line 27

def show
  raise ActiveRecord::RecordNotFound if @customer.nil?

  respond_to do |format|
    format.html do
      ActiveRecord::Associations::Preloader.new(
        records: [@customer],
        associations: [
          { primary_sales_rep: %i[profile_image employee_phone_status] },
          :profile, :buying_group, :source,
          :profile_image, :parent_organization, :customer_record,
          { catalog: :store },
          { addresses: :country }
        ]
      ).call
      @contacts = @customer.contacts.active.includes(:contact_points).order(Contact[:full_name])
      enqueue_profile_image_lookup(@customer)
    end
    format.json do
      render json: @customer.to_json
    end
  end
end

#show_accounting_informationObject



268
# File 'app/controllers/customers_controller.rb', line 268

def show_accounting_information; end

#statement_pdfObject



354
355
356
357
358
359
360
361
362
363
364
365
366
# File 'app/controllers/customers_controller.rb', line 354

def statement_pdf
  @soa = StatementOfAccount.generate_pdf(@customer, params[:per_currency].present? || false)
  if @soa.nil?
    redirect_to customer_path(@customer.id)
    flash[:info] = 'Unable to generate statement, customer has no open documents.'
  else
    recipient_contact_point_id = @soa.primary_transmission_contact_point_id
    redirect_to new_communication_path(upload_ids: @soa.uploads.map(&:id),
                                       recipient_party_id: @customer.id,
                                       recipient_contact_point_id: recipient_contact_point_id,
                                       template_system_code: 'STATEMENTOFACCOUNT')
  end
end

#sync_creditObject



254
255
256
257
258
259
260
261
262
# File 'app/controllers/customers_controller.rb', line 254

def sync_credit
  res = @customer.sync_credit_limit
  if res[:ok]
    flash[:info] = 'Credit Limits Synchronized'
  else
    flash[:error] = "Error while synchronizing credit limits. #{res[:message]}"
  end
  redirect_to_return_path_or_default available_credit_customer_path(@customer)
end

#tab_accountingObject



391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# File 'app/controllers/customers_controller.rb', line 391

def tab_accounting
  ar_scope = @customer.ar_listings(show_open_only: params[:show_open_only])
  # countless skips the pagy COUNT(*) — view_ar_listings can't prune its correlated
  # subqueries from a COUNT projection across UNION ALL, so the count costs ~100ms
  # for high-AR customers (AppSignal #393). Prev/next nav is sufficient for this listing.
  @pagy_ar, @ar_listings = pagy(:countless, ar_scope, limit: 50, page: params[:page])
  # Calculate currency totals using database-level aggregation instead of Ruby iteration
  # Use reorder(nil) to remove inherited ORDER BY which causes GROUP BY errors
  @grouped_ar_listings = ar_scope.reorder(nil).where.not(currency: [nil, 'ALL']).group(:currency).sum(:open_amount_calc).sort

  respond_to do |format|
    format.html { render layout: false }
    format.turbo_stream do
      render turbo_stream: turbo_stream.replace('ar-listings-frame', partial: 'ar_listings', locals: { show_customer: false, show_check: true })
    end
  end
end

#tab_activitiesObject



729
730
731
732
733
734
735
736
# File 'app/controllers/customers_controller.rb', line 729

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

#tab_addressesObject



439
440
441
# File 'app/controllers/customers_controller.rb', line 439

def tab_addresses
  render layout: false
end

#tab_attachmentsObject



702
703
704
705
706
707
708
709
# File 'app/controllers/customers_controller.rb', line 702

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

#tab_certificationsObject



133
134
135
136
137
# File 'app/controllers/customers_controller.rb', line 133

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

#tab_communicationsObject



711
712
713
714
715
716
717
718
# File 'app/controllers/customers_controller.rb', line 711

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

#tab_contactsObject



427
428
429
430
431
432
433
# File 'app/controllers/customers_controller.rb', line 427

def tab_contacts
  @show_inactive = params[:show_inactive] == 'true'
  respond_to do |format|
    format.html { render layout: false }
    format.turbo_stream
  end
end

#tab_digital_assetsObject



694
695
696
697
698
699
700
# File 'app/controllers/customers_controller.rb', line 694

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

#tab_lead_protectionsObject



479
480
481
482
# File 'app/controllers/customers_controller.rb', line 479

def tab_lead_protections
  @lead_protections = Customer.joins(:customer_record).where('customer_records.lead_protector_id' => @customer.id)
  render layout: false
end

#tab_main_profileObject



470
471
472
# File 'app/controllers/customers_controller.rb', line 470

def tab_main_profile
  render layout: false
end

#tab_marketingObject



460
461
462
# File 'app/controllers/customers_controller.rb', line 460

def tab_marketing
  render layout: false
end

#tab_opportunitiesObject



738
739
740
741
742
743
744
745
# File 'app/controllers/customers_controller.rb', line 738

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

#tab_ordersObject



747
748
749
750
751
752
753
754
# File 'app/controllers/customers_controller.rb', line 747

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

#tab_product_reviewsObject



484
485
486
487
488
# File 'app/controllers/customers_controller.rb', line 484

def tab_product_reviews
  @reviews_io_reviews = fetch_reviews_io_reviews
  @company_review_link_data = prepare_company_review_link
  render layout: false
end

#tab_profilingObject



435
436
437
# File 'app/controllers/customers_controller.rb', line 435

def tab_profiling
  render layout: false
end

#tab_receiptsObject



521
522
523
# File 'app/controllers/customers_controller.rb', line 521

def tab_receipts
  render layout: false
end

#tab_showcasesObject



490
491
492
493
# File 'app/controllers/customers_controller.rb', line 490

def tab_showcases
  # Render showcases tab content only for AJAX tabs to avoid full-page reload loops
  render layout: false
end

#tab_smsObject



720
721
722
723
724
725
726
727
# File 'app/controllers/customers_controller.rb', line 720

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

#tab_statsObject



456
457
458
# File 'app/controllers/customers_controller.rb', line 456

def tab_stats
  render layout: false
end

#tab_subsidiariesObject



474
475
476
477
# File 'app/controllers/customers_controller.rb', line 474

def tab_subsidiaries
  @child_organizations = @customer.child_organizations unless @customer.child_organizations.empty?
  render layout: false
end

#tab_ticketsObject



517
518
519
# File 'app/controllers/customers_controller.rb', line 517

def tab_tickets
  render layout: false
end

#tab_trainingObject



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

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

#updateObject

PUT /customers/1



73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'app/controllers/customers_controller.rb', line 73

def update
  if @customer.update(params[:customer] || {})
    @customer.qualify_lead if @customer.guest?
    PartyRecordTimezone.new.process(@customer)
    redirect_to_return_path_or_default(customer_path(@customer))
  else
    set_edit_variables
    render_edit_action(status: :unprocessable_content)
  end
rescue ActiveRecord::RecordNotSaved => e
  ErrorReporting.from_controller(self).informational(
    'Customer update failed: RecordNotSaved',
    customer_id: @customer.id, error: e.message
  )
  @customer.errors.add(:base, 'Could not save the customer record. Please try again or contact support.')
  set_edit_variables
  render_edit_action(status: :unprocessable_content)
end

#update_accounting_informationObject



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'app/controllers/customers_controller.rb', line 281

def update_accounting_information
  @can_update = .has_role?('admin') || .has_role?('accounting_rep')
  unless @can_update
    redirect_to customer_path(@customer), alert: 'You are not authorized to update accounting information.'
    return
  end
  respond_to do |format|
    if @customer.update(params[:customer] || {})
      format.html { redirect_to(customer_path(@customer)) }
      format.xml  { head :ok }
    else
      set_edit_variables
      format.html { render action: 'edit_accounting_information', status: :unprocessable_content }
      format.xml  { render xml: @document.errors, status: :unprocessable_content }
    end
  end
end

#update_catalogObject



373
374
375
376
377
378
379
380
381
382
# File 'app/controllers/customers_controller.rb', line 373

def update_catalog
  new_catalog = Catalog.find(params[:customer][:catalog_id])
  result = Catalog::AssignCatalogToCustomer.new.process(@customer, new_catalog)
  if result.catalog_assigned?
    flash[:info] = "Catalog changed to #{new_catalog.name} and all orders, quotes and rooms converted successfully. #{result.messages.join}".first(500)
  else
    flash[:error] = result.messages.to_sentence.first(500)
  end
  redirect_to_return_path_or_default customer_path(@customer)
end

#update_default_addressObject



447
448
449
450
451
452
453
454
# File 'app/controllers/customers_controller.rb', line 447

def update_default_address
  authorize!(:update, @customer)
  if @customer.update(params[:customer])
    redirect_to_return_path_or_default customer_path(@customer, tab: 'addresses')
  else
    render :edit_default_address, status: :unprocessable_content
  end
end

#update_iq_accessory_preferenceObject



335
336
337
338
339
340
341
342
# File 'app/controllers/customers_controller.rb', line 335

def update_iq_accessory_preference
  @iq_accessory_filter = IqAccessoryFilter.for_customer(@customer).is_auto_generated_only.find(params[:iq_accessory_filter_id])
  if @iq_accessory_filter.update(params[:iq_accessory_filter])
    redirect_to(iq_accessory_preferences_customer_path(@customer))
  else
    render action: 'edit_iq_accessory_preference', status: :unprocessable_content
  end
end

#update_shipping_preferencesObject



301
302
303
304
305
306
307
# File 'app/controllers/customers_controller.rb', line 301

def update_shipping_preferences
  if @customer.update(params[:customer] || {})
    redirect_to_return_path_or_default customer_path(@customer, tab: 'addresses')
  else
    render action: 'edit_shipping_preferences', status: :unprocessable_content
  end
end