Class: CustomerSearch

Inherits:
Search show all
Includes:
Models::CustomerSearchCriteria, Models::RepSearchable, Models::SearchAddressable
Defined in:
app/models/customer_search.rb

Overview

== Schema Information

Table name: searches
Database name: primary

id :integer not null, primary key
name :string(255)
persist :boolean default(FALSE)
pinned :boolean default(FALSE), not null
query_params :jsonb
result_set_size :integer default(0)
selected_columns :string is an Array
set_limit :integer default(25)
sort_column :string(255)
sort_columns :string default([]), not null, is an Array
sort_direction :string(255) default("ASC")
type :string(255) not null
created_at :datetime
updated_at :datetime
employee_id :integer

Indexes

employee_id_pinned (employee_id,pinned)
employee_id_type (employee_id,type)

Saved advanced-search query for CRM Customers — a Search STI subclass.

Its +query_params+ drive Ransack for the expressible predicates and the
non-Ransack filters in Models::CustomerSearchCriteria (has-ordered EXISTS,
ltree product containment, campaign membership, drop events, profile,
certified installer). It also exposes the CRM customer mass-actions, each of
which enqueues a +MassSearch::*+ worker over the search's selected records.

Constant Summary collapse

CUSTOM_CRITERIA_KEYS =

Keys consumed by apply_custom_criteria — excluded from the Ransack strict probe
so we don't get false-positive InvalidSearchError reports in AppSignal.

%w[
  has_ordered_item_id has_ordered_product_category_id has_ordered_product_line_id
  has_ordered_days has_ordered_days_time_range_gteq has_ordered_days_time_range_lteq
  minimum_number_of_orders has_ordered_type
  has_not_ordered_days has_not_quoted_days
  campaign_id_in campaign_id_not_in has_campaign_activity_status
  custom_drop_reason_codes custom_drop_rep_ids
  profile_in profile_not_in is_certified_installer
].freeze

Constants inherited from Search

Search::DISTANCE_SEARCH_KEYS

Constants included from Models::Schedulable

Models::Schedulable::SIMPLE_FORM_OPTIONS

Instance Attribute Summary

Attributes inherited from Search

#composite_columns, #pagy_count, #pagy_limit, #pagy_page, #sql_select_columns

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Models::CustomerSearchCriteria

#apply_custom_criteria

Methods included from Models::SearchAddressable

#mass_googlemap

Methods inherited from Search

#all_composite_columns, allowed_role_ids, #append_custom_column, #append_ouput_column, #append_to_sql_select_columns, #applicable_sort_terms, #apply_array_match, #apply_criteria, #apply_customer_cross_reference, #apply_distance_search, #apply_select, #apply_sort, #available_events, available_output_columns, #available_output_columns, base_search_class_name, #cleanup_search_results, composite_column, #composite_column, database_columns, default_columns, default_sort, #discard_excess, #effective_name, #effective_short_name, #employee, #enqueue_navbar_pinned_refresh, #fast_count, favorites, friendly_column_name, #get_pinned_results, #get_sort_term, global_favorite, has_role_for_search?, #human_query_params, instantiate_query_template, #instantiate_resource_updater, #limit_options, main_resource_class, main_resource_table, mass_actions_for_select, #mass_export, maximum_unpersisted_queries, #normalize_for_mass_update, options, options_classes, #output_columns_for_select, #pagy_from_search, #perform, #perform_selected, #pinned_query, #prepend_custom_column, #prepend_output_column, query_favorites_templates, #ransack_probe_params, recent_searches_for_employee_id, #record_list, #refresh_pinned_results, #remove_other_pins, search_name, #search_name, #search_results, search_type, search_type_humanized, select_sort_columns, #select_sort_columns, #select_statement, #set_sort_term, #sort_column1, #sort_column1=, #sort_column2, #sort_column2=, #sort_column3, #sort_column3=, #sort_columns_for_select, #sort_columns_to_hash, #sortable_column?, #target_geocoding, unpersisted_queries_quote_reached?, #unrecord_list, #validated_selected_columns, view_resource_class, view_resource_klass, #view_resource_klass, view_resource_table, visible?, with_search_results

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Class Method Details

.custom_criteria_keysArray<String>

Returns +query_params+ keys handled outside Ransack (by
Models::CustomerSearchCriteria), excluded from the strict Ransack probe.

Returns:



109
110
111
# File 'app/models/customer_search.rb', line 109

def self.custom_criteria_keys
  CUSTOM_CRITERIA_KEYS
end

.scope_options_for_mass_schedule_activityArray<Array(String, String)>

Returns +[label, value]+ scope options for the
schedule-activity mass action.

Returns:

  • (Array<Array(String, String)>)

    +[label, value]+ scope options for the
    schedule-activity mass action



298
299
300
# File 'app/models/customer_search.rb', line 298

def self.scope_options_for_mass_schedule_activity
  [['Customer Only', 'customer_only'], ['Primary Contact', 'primary_contact'], ['All Active Contacts', 'all_active_contacts']]
end

.select_options_for_profileArray<Array(String, String)>

Returns profile select options, prefixed with
the "Any Organization" ('ORG') and "All Trade" ('TRADE') sentinels.

Returns:

  • (Array<Array(String, String)>)

    profile select options, prefixed with
    the "Any Organization" ('ORG') and "All Trade" ('TRADE') sentinels



400
401
402
# File 'app/models/customer_search.rb', line 400

def self.select_options_for_profile
  [['Any Organization', 'ORG'], ['All Trade', 'TRADE']] + Profile.options_for_select(false)
end

Instance Method Details

#address_id_fieldsArray<Array(String, String)>

Returns +[label, address-id column]+ pairs for
the address-based mass-action target selector.

Returns:

  • (Array<Array(String, String)>)

    +[label, address-id column]+ pairs for
    the address-based mass-action target selector



394
395
396
# File 'app/models/customer_search.rb', line 394

def address_id_fields
  [%w[Shipping shipping_address_id], %w[Billing billing_address_id], %w[Mailing mailing_address_id]]
end

#drop_action_reps_for_selectArray<Array>

Sales-rep select options scoped to the customers currently in the search's
result set.

Returns:

  • (Array<Array>)

    +[label, id]+ pairs for a rep select input



138
139
140
141
# File 'app/models/customer_search.rb', line 138

def drop_action_reps_for_select
  selected_customer_ids = search_results.pluck(:resource_id)
  Employee.assigned_sales_rep_options_for_select(customer_ids: selected_customer_ids)
end

#mass_assign_reps(params, cur_user = nil) ⇒ Hash

Enqueues rep assignment (with an optional logged activity) over the selected customers.

Parameters:

  • params (ActionController::Parameters, Hash)

    +:resource+ and +:activity+ params

  • cur_user (Employee, nil) (defaults to: nil)

    the acting user

Returns:

  • (Hash)

    +{ status: :ok, job_id: String }+



148
149
150
151
152
153
154
155
156
# File 'app/models/customer_search.rb', line 148

def mass_assign_reps(params, cur_user = nil)
  jid = MassSearch::CustomerAssignRepsWorker.perform_async(
    'search_id'     => id,
    'action_params' => { 'resource_params' => (params[:resource] || {}).to_h, 'activity' => (params[:activity] || {}).to_h },
    'user_id'       => cur_user&.id,
    'locale'        => I18n.locale.to_s
  )
  { status: :ok, job_id: jid }
end

#mass_assign_source(params, cur_user = nil) ⇒ Hash

Enqueues a source-id update over the selected customers.

Parameters:

  • params (ActionController::Parameters, Hash)

    +:source_id+

  • cur_user (Employee, nil) (defaults to: nil)

    the acting user

Returns:

  • (Hash)

    +{ status: :ok, job_id: String }+



182
183
184
185
186
187
188
189
190
# File 'app/models/customer_search.rb', line 182

def mass_assign_source(params, cur_user = nil)
  jid = ::SearchResourceUpdateWorker.perform_async(
    'search_id'     => id,
    'action_params' => { 'resource_params' => { 'source_id' => params[:source_id] } },
    'user_id'       => cur_user&.id,
    'locale'        => I18n.locale.to_s
  )
  { status: :ok, job_id: jid }
end

#mass_assign_to_audience(params, cur_user = nil) ⇒ Hash

Enqueues assignment of the selected customers to an existing audience.

Parameters:

  • params (ActionController::Parameters, Hash)

    +:audience_id+

  • cur_user (Employee, nil) (defaults to: nil)

    the acting user

Returns:

  • (Hash)

    +{ status: :ok, job_id: String }+



215
216
217
218
219
220
221
222
223
224
225
# File 'app/models/customer_search.rb', line 215

def mass_assign_to_audience(params, cur_user = nil)
  return { status: :error, message: 'You must specify an audience' } if params[:audience_id].blank?

  jid = MassSearch::CustomerAssignToAudienceWorker.perform_async(
    'search_id'     => id,
    'action_params' => { 'audience_id' => params[:audience_id] },
    'user_id'       => cur_user&.id,
    'locale'        => I18n.locale.to_s
  )
  { status: :ok, job_id: jid }
end

#mass_assign_to_campaign(params, cur_user = nil) ⇒ Hash

Enqueues assignment of the selected customers to an existing or newly-named campaign.

Parameters:

  • params (ActionController::Parameters, Hash)

    +:campaign+ (+:campaign_id+ or +:new_campaign_name+)

  • cur_user (Employee, nil) (defaults to: nil)

    the acting user

Returns:

  • (Hash)

    +{ status: :ok, job_id: String }+, or +{ status: :error, message: String }+
    when neither an existing campaign nor a new name is supplied



233
234
235
236
237
238
239
240
241
242
243
244
# File 'app/models/customer_search.rb', line 233

def mass_assign_to_campaign(params, cur_user = nil)
  campaign_params = params[:campaign] || {}
  return { status: :error, message: 'You must specify an existing campaign or enter a name for a new campaign' } if campaign_params[:campaign_id].blank? && campaign_params[:new_campaign_name].blank?

  jid = MassSearch::CustomerAssignToCampaignWorker.perform_async(
    'search_id'     => id,
    'action_params' => { 'campaign' => campaign_params.to_h },
    'user_id'       => cur_user&.id,
    'locale'        => I18n.locale.to_s
  )
  { status: :ok, job_id: jid }
end

#mass_auto_assign_sales_rep(params, cur_user = nil) ⇒ Hash

Enqueues automatic sales-rep assignment over the selected customers.

Parameters:

  • params (ActionController::Parameters, Hash)

    +:remove_existing_reps+, +:skip_lead_assignment+, +:activity+

  • cur_user (Employee, nil) (defaults to: nil)

    the acting user

Returns:

  • (Hash)

    +{ status: :ok, job_id: String }+



163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'app/models/customer_search.rb', line 163

def mass_auto_assign_sales_rep(params, cur_user = nil)
  jid = MassSearch::CustomerAutoAssignSalesRepWorker.perform_async(
    'search_id'     => id,
    'action_params' => {
      'remove_existing_reps'    => params[:remove_existing_reps],
      'skip_lead_assignment'    => params[:skip_lead_assignment],
      'activity'                => (params[:activity] || {}).to_h
    },
    'user_id'       => cur_user&.id,
    'locale'        => I18n.locale.to_s
  )
  { status: :ok, job_id: jid }
end

#mass_create_audience(params, cur_user = nil) ⇒ Hash

Enqueues creation of an audience from the selected customers.

Parameters:

  • params (ActionController::Parameters, Hash)

    +:audience+ attributes (+:audience_name+, +:audience_type+, +:add_all_emails+)

  • cur_user (Employee, nil) (defaults to: nil)

    the acting user

Returns:

  • (Hash)

    +{ status: :ok, job_id: String }+



197
198
199
200
201
202
203
204
205
206
207
208
# File 'app/models/customer_search.rb', line 197

def mass_create_audience(params, cur_user = nil)
  audience_params = (params[:audience] || {}).to_h
  return { status: :error, message: 'You must specify a name and type for the audience' } if audience_params[:audience_name].blank? || audience_params[:audience_type].blank?

  jid = MassSearch::CustomerCreateAudienceWorker.perform_async(
    'search_id'     => id,
    'action_params' => { 'audience' => audience_params },
    'user_id'       => cur_user&.id,
    'locale'        => I18n.locale.to_s
  )
  { status: :ok, job_id: jid }
end

#mass_delete_on_lead_qualify_and_guest_state(_params, cur_user = nil) ⇒ Hash

Enqueues deletion of the selected customers that are in lead-qualify or guest state.

Parameters:

  • _params (ActionController::Parameters, Hash)

    unused (accepted for mass-action arity)

  • cur_user (Employee, nil) (defaults to: nil)

    the acting user

Returns:

  • (Hash)

    +{ status: :ok, job_id: String }+



266
267
268
269
270
271
272
273
274
# File 'app/models/customer_search.rb', line 266

def mass_delete_on_lead_qualify_and_guest_state(_params, cur_user = nil)
  jid = MassSearch::CustomerDeleteLeadsWorker.perform_async(
    'search_id'     => id,
    'action_params' => {},
    'user_id'       => cur_user&.id,
    'locale'        => I18n.locale.to_s
  )
  { status: :ok, job_id: jid }
end

#mass_drop_sales_rep(params, cur_user) ⇒ Hash

Enqueues dropping a sales rep from the selected customers, recording a reason.

Parameters:

  • params (ActionController::Parameters, Hash)

    +:sales_rep_id+, +:reason_code+, +:comment+

  • cur_user (Employee)

    the acting user

Returns:

  • (Hash)

    +{ status: :ok, job_id: String }+



307
308
309
310
311
312
313
314
315
# File 'app/models/customer_search.rb', line 307

def mass_drop_sales_rep(params, cur_user)
  jid = MassSearch::CustomerDropSalesRepWorker.perform_async(
    'search_id'     => id,
    'action_params' => { 'sales_rep_id' => params[:sales_rep_id], 'reason_code' => params[:reason_code], 'comment' => params[:comment] },
    'user_id'       => cur_user&.id,
    'locale'        => I18n.locale.to_s
  )
  { status: :ok, job_id: jid }
end

#mass_locator_blacklist(params, cur_user) ⇒ Hash

Enqueues locator blacklisting of the selected customers.

Parameters:

  • params (ActionController::Parameters, Hash)

    +:reason+

  • cur_user (Employee)

    the acting user

Returns:

  • (Hash)

    +{ status: :ok, job_id: String }+



337
338
339
340
341
342
343
344
345
# File 'app/models/customer_search.rb', line 337

def mass_locator_blacklist(params, cur_user)
  jid = MassSearch::CustomerLocatorBlacklistWorker.perform_async(
    'search_id'     => id,
    'action_params' => { 'reason' => params[:reason] },
    'user_id'       => cur_user&.id,
    'locale'        => I18n.locale.to_s
  )
  { status: :ok, job_id: jid }
end

#mass_locator_unblacklist(params, cur_user) ⇒ Hash

Enqueues removal of locator blacklisting from the selected customers.

Parameters:

  • params (ActionController::Parameters, Hash)

    +:inform_customer+

  • cur_user (Employee)

    the acting user

Returns:

  • (Hash)

    +{ status: :ok, job_id: String }+



367
368
369
370
371
372
373
374
375
# File 'app/models/customer_search.rb', line 367

def mass_locator_unblacklist(params, cur_user)
  jid = MassSearch::CustomerLocatorUnblacklistWorker.perform_async(
    'search_id'     => id,
    'action_params' => { 'inform_customer' => params[:inform_customer] },
    'user_id'       => cur_user&.id,
    'locale'        => I18n.locale.to_s
  )
  { status: :ok, job_id: jid }
end

#mass_locator_unwhitelist(_params, cur_user) ⇒ Hash

Enqueues removal of locator whitelisting from the selected customers.

Parameters:

  • _params (ActionController::Parameters, Hash)

    unused (accepted for mass-action arity)

  • cur_user (Employee)

    the acting user

Returns:

  • (Hash)

    +{ status: :ok, job_id: String }+



352
353
354
355
356
357
358
359
360
# File 'app/models/customer_search.rb', line 352

def mass_locator_unwhitelist(_params, cur_user)
  jid = MassSearch::CustomerLocatorUnwhitelistWorker.perform_async(
    'search_id'     => id,
    'action_params' => {},
    'user_id'       => cur_user&.id,
    'locale'        => I18n.locale.to_s
  )
  { status: :ok, job_id: jid }
end

#mass_locator_whitelist(params, cur_user) ⇒ Hash

Enqueues locator whitelisting of the selected customers.

Parameters:

  • params (ActionController::Parameters, Hash)

    +:reason+

  • cur_user (Employee)

    the acting user

Returns:

  • (Hash)

    +{ status: :ok, job_id: String }+



322
323
324
325
326
327
328
329
330
# File 'app/models/customer_search.rb', line 322

def mass_locator_whitelist(params, cur_user)
  jid = MassSearch::CustomerLocatorWhitelistWorker.perform_async(
    'search_id'     => id,
    'action_params' => { 'reason' => params[:reason] },
    'user_id'       => cur_user&.id,
    'locale'        => I18n.locale.to_s
  )
  { status: :ok, job_id: jid }
end

#mass_online_account_invite(_params, cur_user = nil) ⇒ Hash

Enqueues online-account invitations to the selected customers.

Parameters:

  • _params (ActionController::Parameters, Hash)

    unused (accepted for mass-action arity)

  • cur_user (Employee, nil) (defaults to: nil)

    the acting user

Returns:

  • (Hash)

    +{ status: :ok, job_id: String }+



382
383
384
385
386
387
388
389
390
# File 'app/models/customer_search.rb', line 382

def (_params, cur_user = nil)
  jid = MassSearch::CustomerOnlineAccountInviteWorker.perform_async(
    'search_id'     => id,
    'action_params' => {},
    'user_id'       => cur_user&.id,
    'locale'        => I18n.locale.to_s
  )
  { status: :ok, job_id: jid }
end

#mass_schedule_activity(params, cur_user = nil) ⇒ Hash

Enqueues scheduling of an activity over the selected customers, optionally
scoped to contacts and/or tied to a campaign.

Parameters:

  • params (ActionController::Parameters, Hash)

    +:activity+ (incl. +apply_scope+ and +campaign_id+)

  • cur_user (Employee, nil) (defaults to: nil)

    the acting user

Returns:

  • (Hash)

    +{ status: :ok, job_id: String }+



282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'app/models/customer_search.rb', line 282

def mass_schedule_activity(params, cur_user = nil)
  activity_params = (params[:activity] || {}).to_h
  apply_scope = activity_params.delete('apply_scope')
  campaign_id = activity_params.delete('campaign_id')

  jid = MassSearch::CustomerScheduleActivityWorker.perform_async(
    'search_id'     => id,
    'action_params' => { 'activity' => activity_params, 'apply_scope' => apply_scope, 'campaign_id' => campaign_id },
    'user_id'       => cur_user&.id,
    'locale'        => I18n.locale.to_s
  )
  { status: :ok, job_id: jid }
end

#mass_switch_rep_roles(_params, cur_user = nil) ⇒ Hash

Enqueues swapping of primary/secondary rep roles over the selected customers.

Parameters:

  • _params (ActionController::Parameters, Hash)

    unused (accepted for mass-action arity)

  • cur_user (Employee, nil) (defaults to: nil)

    the acting user

Returns:

  • (Hash)

    +{ status: :ok, job_id: String }+



251
252
253
254
255
256
257
258
259
# File 'app/models/customer_search.rb', line 251

def mass_switch_rep_roles(_params, cur_user = nil)
  jid = MassSearch::CustomerSwitchRepRolesWorker.perform_async(
    'search_id'     => id,
    'action_params' => {},
    'user_id'       => cur_user&.id,
    'locale'        => I18n.locale.to_s
  )
  { status: :ok, job_id: jid }
end

#set_defaultsvoid

This method returns an undefined value.

Default new customer searches to every state except guest. Expressed as
state_in (positive list) rather than state_not_in: ['guest'] so the
Status filter renders as "is any of" with the included states visible —
clearer to users than an exclusion they have to mentally invert.



119
120
121
122
123
124
125
126
127
128
# File 'app/models/customer_search.rb', line 119

def set_defaults
  super
  return unless new_record?

  qp = (query_params || {}).with_indifferent_access
  return if qp[:state_in].present? || qp[:state_not_in].present?

  non_guest_states = Customer.states_for_select.map(&:last) - ['guest']
  self.query_params = (query_params || {}).merge('state_in' => non_guest_states)
end