Class: CustomerFilter

Inherits:
ApplicationRecord show all
Includes:
Memery, Models::Auditable
Defined in:
app/models/customer_filter.rb

Overview

== Schema Information

Table name: customer_filters
Database name: primary

id :integer not null, primary key
cities :string(255) default([]), is an Array
customer_states :string(255) default([]), is an Array
digest :string(32)
exclude_buying_group_ids :boolean default(FALSE)
exclude_catalog_ids :boolean default(FALSE)
exclude_cities :boolean default(FALSE)
exclude_customer_states :boolean default(FALSE)
exclude_parties :boolean default(FALSE)
exclude_postal_codes :boolean default(FALSE)
exclude_profile_ids :boolean default(FALSE)
exclude_report_groupings :boolean default(FALSE)
exclude_source_ids :boolean default(FALSE)
exclude_state_codes :boolean default(FALSE)
exclude_tier2_program_pricing_ids :boolean default(FALSE)
has_email :integer default(0), not null
has_fax :integer default(0), not null
has_phone :integer default(0), not null
has_sales_order :integer default(0), not null
is_auto_generated :boolean
name :string(120) not null
opportunity_count_max :integer
opportunity_count_min :integer
opportunity_value_max :integer
opportunity_value_min :integer
phone_number_starts_with :string(12)
position :integer
postal_codes :string(10) default([]), is an Array
report_groupings :string default([]), is an Array
state_codes :string(2) default([]), is an Array
created_at :datetime not null
updated_at :datetime not null
creator_id :integer
store_id :integer
updater_id :integer

Indexes

index_customer_filters_on_digest (digest) UNIQUE
index_customer_filters_on_is_auto_generated (is_auto_generated)
index_customer_filters_on_store_id_and_name (store_id,name) UNIQUE
index_phone_number_starts_with_trgrm (phone_number_starts_with) USING gin

Foreign Keys

customer_filters_store_id_fk (store_id => stores.id)

Defined Under Namespace

Classes: QueryBuilder

Constant Summary collapse

DEFAULT =

"All w/out DB/ETL/HD/COSTCO"

138
CONDITIONS =

Represent all the accessor methods to conditional values

[:party_ids, :customer_states, :report_groupings, :catalogs, :store, :profiles, :buying_groups,
:sources, :state_codes, :postal_codes, :cities, :tier2_program_pricings, :phone_number_starts_with,
:has_sales_order ]
UNIQUE_FIELDS =

These fields will be used to compare uniqueness of customer filters

[ :store_id, :exclude_catalog_ids, :exclude_profile_ids, :exclude_state_codes, :exclude_cities,
:exclude_postal_codes, :exclude_buying_group_ids, :exclude_source_ids, :exclude_tier2_program_pricing_ids,
:exclude_report_groupings, :exclude_customer_states, :exclude_parties,
:state_codes, :cities, :postal_codes, :report_groupings, :customer_states,
:catalog_ids, :profile_ids, :buying_group_ids, :source_ids,
:tier2_program_pricing_ids, :party_ids, :has_phone, :has_email, :has_fax, :has_sales_order ]
NA =
0
YES =
1
NO =
2
TRIPLE_STATE =
{ NA => 'Not Applicable', YES => 'Must Have One', NO => 'Must Have None' }

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Belongs to collapse

Methods included from Models::Auditable

#creator, #updater

Has many collapse

Has and belongs to many collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Models::Auditable

#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::EventPublishable

#publish_event

Class Method Details

.create_single_customer_filter(customer) ⇒ Object



115
116
117
118
119
120
121
122
123
# File 'app/models/customer_filter.rb', line 115

def self.create_single_customer_filter(customer)
  cf = nil
  if customer and customer.kind_of?(Customer) # ensure we are dealing with a real customer
    party_criteria = {parties: {id: customer.id}, is_auto_generated: true}
    create_criteria = {name: "#{customer.full_name} - #{customer.reference_number} - Customer Preference", is_auto_generated: true, party_ids: [customer.id]}
    cf = CustomerFilter.joins(:parties).where(party_criteria).first_or_create(create_criteria)
  end
  return cf
end

.duplicate_filtersActiveRecord::Relation<CustomerFilter>

A relation of CustomerFilters that are duplicate filters. Active Record Scope

Returns:

See Also:



104
# File 'app/models/customer_filter.rb', line 104

scope :duplicate_filters, -> { where("EXISTS(select digest from customer_filters cf where cf.digest = customer_filters.digest group by digest having count(digest) > 1)") }

.options_for_selectObject



109
110
111
112
113
# File 'app/models/customer_filter.rb', line 109

def self.options_for_select
  cfs = CustomerFilter.all
  cfs = yield(cfs) if block_given?
  cfs.sorted.pluck(:name, :id)
end

.sortedActiveRecord::Relation<CustomerFilter>

A relation of CustomerFilters that are sorted. Active Record Scope

Returns:

See Also:



103
# File 'app/models/customer_filter.rb', line 103

scope :sorted, -> { order("customer_filters.name") }

Instance Method Details

#activity_typesActiveRecord::Relation<ActivityType>

Returns:

See Also:



86
# File 'app/models/customer_filter.rb', line 86

has_many :activity_types, inverse_of: :customer_filter

#all_catalog_idsObject



445
446
447
448
449
450
451
452
453
454
455
456
457
# File 'app/models/customer_filter.rb', line 445

def all_catalog_ids
  # Convenience method that will retrieve all catalog_ids associated with the filter
  cids = []
  cids += parties.map{|p| p.try(:catalog_id)}
  if catalog_ids.present?
    if exclude_catalog_ids
      cids -= catalog_ids
    else
      cids += catalog_ids
    end
  end
  cids.compact.uniq.sort
end

#applies_to_customer?(customer) ⇒ Boolean

Returns:

  • (Boolean)


231
232
233
# File 'app/models/customer_filter.rb', line 231

def applies_to_customer?(customer)
  build_matcher(customer).match?
end

#auto_apply_couponsActiveRecord::Relation<Coupon>

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



88
# File 'app/models/customer_filter.rb', line 88

has_many :auto_apply_coupons, inverse_of: :auto_apply_customer_filter, class_name: 'Coupon', foreign_key: :auto_apply_customer_filter_id

#build_matcher(customer) ⇒ Object



358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
# File 'app/models/customer_filter.rb', line 358

def build_matcher(customer)
	CustomerRuleMatcher.new(self, customer,
								:party_match?,
								:customer_state_match?,
								:report_grouping_match?,
								:catalog_match?,
								:store_match?,
								:profile_match?,
                :tier2_program_pricing_match?,
                :buying_group_match?,
								:source_match?,
								:state_code_match?,
								:postal_code_match?,
								:city_match?,
                :has_phone_match?,
                :has_fax_match?,
                :has_email_match?,
                :has_phone_number_match?,
                :has_sales_order_match?,
                :has_opportunity_match?
								)
end

#buying_group_match?(customer) ⇒ Boolean

Returns:

  • (Boolean)


151
152
153
# File 'app/models/customer_filter.rb', line 151

def buying_group_match?(customer)
  meta_assoc_match buying_groups, customer.buying_group_id, exclude_buying_group_ids
end

#buying_groupsActiveRecord::Relation<BuyingGroup>

Returns:

See Also:



93
# File 'app/models/customer_filter.rb', line 93

has_and_belongs_to_many :buying_groups

#calculate_digestObject



409
410
411
412
413
# File 'app/models/customer_filter.rb', line 409

def calculate_digest
  dg = Digest::MD5.hexdigest raw_digest_fields
  logger.debug "Calculated digest #{dg} for customer filter #{self.id}"
  dg
end

#catalog_match?(customer) ⇒ Boolean

Returns:

  • (Boolean)


155
156
157
# File 'app/models/customer_filter.rb', line 155

def catalog_match?(customer)
  meta_assoc_match catalogs, customer.catalog_id, exclude_catalog_ids
end

#catalogsActiveRecord::Relation<Catalog>

Returns:

  • (ActiveRecord::Relation<Catalog>)

See Also:



91
# File 'app/models/customer_filter.rb', line 91

has_and_belongs_to_many :catalogs

#chain_buying_groups_query(customers_query) ⇒ Object



339
340
341
342
343
344
345
346
# File 'app/models/customer_filter.rb', line 339

def chain_buying_groups_query(customers_query)
  return customers_query unless buying_group_ids.present?
  if exclude_buying_group_ids
    customers_query.where.not(buying_group_id: buying_group_ids)
  else
    customers_query.where(buying_group_id: buying_group_ids)
  end
end

#chain_catalogs_query(customers_query) ⇒ Object



262
263
264
265
266
267
268
269
# File 'app/models/customer_filter.rb', line 262

def chain_catalogs_query(customers_query)
  return customers_query unless catalog_ids.present?
  if exclude_catalog_ids
    customers_query.where.not(catalog_id: catalog_ids)
  else
    customers_query.where(catalog_id: catalog_ids)
  end
end

#chain_cities_query(customers_query) ⇒ Object



298
299
300
301
302
303
304
305
# File 'app/models/customer_filter.rb', line 298

def chain_cities_query(customers_query)
  return customers_query unless cities.present?
  if exclude_cities
    customers_query.joins(:addresses).where(Address[:city].not_in(cities).or(Address[:city].eq(nil)))
  else
    customers_query.joins(:addresses).where(Address[:city].in(cities))
  end
end

#chain_customer_states_query(customers_query) ⇒ Object



312
313
314
315
316
317
318
319
# File 'app/models/customer_filter.rb', line 312

def chain_customer_states_query(customers_query)
  return customers_query unless customer_states.present?
  if exclude_customer_states
    customers_query.where.not(state: customer_states)
  else
    customers_query.where(state: customer_states)
  end
end

#chain_parties_query(customers_query) ⇒ Object



330
331
332
333
334
335
336
337
# File 'app/models/customer_filter.rb', line 330

def chain_parties_query(customers_query)
  return customers_query unless party_ids.present?
  if exclude_parties
    customers_query.where.not(id: party_ids)
  else
    customers_query.where(id: party_ids)
  end
end

#chain_postal_codes_query(customers_query) ⇒ Object



289
290
291
292
293
294
295
296
# File 'app/models/customer_filter.rb', line 289

def chain_postal_codes_query(customers_query)
  return customers_query unless postal_codes.present?
  if exclude_postal_codes
    customers_query.joins(:addresses).where(Address[:zip].not_in(postal_codes).or(Address[:zip].eq(nil)))
  else
    customers_query.joins(:addresses).where(Address[:zip].in(postal_codes))
  end
end

#chain_profiles_query(customers_query) ⇒ Object



253
254
255
256
257
258
259
260
# File 'app/models/customer_filter.rb', line 253

def chain_profiles_query(customers_query)
  return customers_query unless profile_ids.present?
  if exclude_profile_ids
    customers_query.where(Customer[:profile_id].not_in(profile_ids).or(Customer[:profile_id].eq(nil)))
  else
    customers_query.where(profile_id: profile_ids)
  end
end

#chain_program_pricing_query(customers_query) ⇒ Object



321
322
323
324
325
326
327
328
# File 'app/models/customer_filter.rb', line 321

def chain_program_pricing_query(customers_query)
  return customers_query unless tier2_program_pricing_ids.present?
  if exclude_tier2_program_pricing_ids
    customers_query.where.not(tier2_program_pricing_id: tier2_program_pricing_ids)
  else
    customers_query.where(tier2_program_pricing_id: tier2_program_pricing_ids)
  end
end

#chain_query(customers_query) ⇒ Object



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'app/models/customer_filter.rb', line 236

def chain_query(customers_query)
  customers_query = chain_report_groupings_query(customers_query)
  customers_query = chain_buying_groups_query(customers_query)
  customers_query = chain_parties_query(customers_query)
  customers_query = chain_program_pricing_query(customers_query)
  customers_query = chain_customer_states_query(customers_query)
  customers_query = chain_store_query(customers_query)
  customers_query = chain_cities_query(customers_query)
  customers_query = chain_postal_codes_query(customers_query)
  customers_query = chain_state_codes_query(customers_query)
  customers_query = chain_sources_query(customers_query)
  customers_query = chain_catalogs_query(customers_query)
  customers_query = chain_profiles_query(customers_query)
  # Todo phone match, email match, fax match, order match
  customers_query
end

#chain_report_groupings_query(customers_query) ⇒ Object



348
349
350
351
352
353
354
355
356
# File 'app/models/customer_filter.rb', line 348

def chain_report_groupings_query(customers_query)
  return customers_query unless report_groupings.present?
  if exclude_report_groupings
    # a customer with nil is considered excluded too we have to test for this condition specifically in sql
    customers_query.where(Customer[:report_grouping].eq(nil).or(Customer[:report_grouping].not_in(report_groupings)))
  else
    customers_query.where(Customer[:report_grouping].in(report_groupings))
  end
end

#chain_sources_query(customers_query) ⇒ Object



271
272
273
274
275
276
277
278
# File 'app/models/customer_filter.rb', line 271

def chain_sources_query(customers_query)
  return customers_query unless source_ids.present?
  if exclude_source_ids
    customers_query.where(Customer[:source_id].not_in(source_ids).or(Customer[:source_id].eq(nil)))
  else
    customers_query.where(Customer[:source_id].in(source_ids))
  end
end

#chain_state_codes_query(customers_query) ⇒ Object



280
281
282
283
284
285
286
287
# File 'app/models/customer_filter.rb', line 280

def chain_state_codes_query(customers_query)
  return customers_query unless state_codes.present?
  if exclude_state_codes
    customers_query.where(Customer[:state_code].not_in(state_codes).or(Customer[:state_code].eq(nil)))
  else
    customers_query.where(Customer[:state_code].in(state_codes))
  end
end

#chain_store_query(customers_query) ⇒ Object



307
308
309
310
# File 'app/models/customer_filter.rb', line 307

def chain_store_query(customers_query)
  return customers_query unless store_id.present?
  customers_query.joins(:catalog).where(Catalog[:store_id].eq(store_id))
end

#city_match?(customer) ⇒ Boolean

Returns:

  • (Boolean)


179
180
181
# File 'app/models/customer_filter.rb', line 179

def city_match?(customer)
  meta_string_match customer.city, cities, exclude_cities
end

#couponsActiveRecord::Relation<Coupon>

Returns:

  • (ActiveRecord::Relation<Coupon>)

See Also:



87
# File 'app/models/customer_filter.rb', line 87

has_many :coupons, inverse_of: :customer_filter

#customer_state_match?(customer) ⇒ Boolean

Returns:

  • (Boolean)


167
168
169
# File 'app/models/customer_filter.rb', line 167

def customer_state_match?(customer)
  meta_string_match customer.state, customer_states, exclude_customer_states
end

#duplicate_filtersObject



381
382
383
384
385
# File 'app/models/customer_filter.rb', line 381

def duplicate_filters
  filters = CustomerFilter.where(digest: calculate_digest)
  filters = filters.where(CustomerFilter[:id].not_eq(id)) unless new_record?
  filters
end

#fields_arrayObject



391
392
393
394
395
396
397
398
399
# File 'app/models/customer_filter.rb', line 391

def fields_array
  raw_fields = UNIQUE_FIELDS.inject([]) do |array, field|
    val = send(field)
    val = val.compact.sort if !val.nil? and val.is_a?Array
    array << "#{field}:#{val}" if val.present?
    array
  end
  (raw_fields || []).sort
end

#friendly_digestObject



401
402
403
# File 'app/models/customer_filter.rb', line 401

def friendly_digest
  fields_array.to_sentence
end

#has_a_condition?Boolean

Returns:

  • (Boolean)


441
442
443
# File 'app/models/customer_filter.rb', line 441

def has_a_condition?
  CONDITIONS.any?{|cnd| send(cnd).present? }
end

#has_duplicate_filter?Boolean

Returns:

  • (Boolean)


387
388
389
# File 'app/models/customer_filter.rb', line 387

def has_duplicate_filter?
  duplicate_filters.present?
end

#has_email_match?(customer) ⇒ Boolean

Returns:

  • (Boolean)


221
222
223
# File 'app/models/customer_filter.rb', line 221

def has_email_match?(customer)
  relation_triple_state_presence_check(customer.all_contact_points.emails, has_email)
end

#has_fax_match?(customer) ⇒ Boolean

Returns:

  • (Boolean)


191
192
193
# File 'app/models/customer_filter.rb', line 191

def has_fax_match?(customer)
  relation_triple_state_presence_check(customer.all_contact_points.faxes, has_fax)
end

#has_opportunity_match?(customer) ⇒ Boolean

Returns:

  • (Boolean)


199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'app/models/customer_filter.rb', line 199

def has_opportunity_match?(customer)
  # The lack of filter is a true check
  return true unless opportunity_value_min || opportunity_value_max || opportunity_count_min || opportunity_count_max

  opp_query = customer.opportunities.open_opportunities.where(opportunity_type: 'S')

  if opportunity_value_min.present?
    opp_query = opp_query.where(value: opportunity_value_min..)
  end
  if opportunity_value_max.present?
    opp_query = opp_query.where(value: opportunity_value_max..)
  end
  if opportunity_count_max.present?
    return false if opp_query.size > opportunity_count_max
  end
  if opportunity_count_min.present?
    return false if opp_query.size < opportunity_count_min
  end
  return true # Logically you'll exit before this
end

#has_phone_match?(customer) ⇒ Boolean

Returns:

  • (Boolean)


187
188
189
# File 'app/models/customer_filter.rb', line 187

def has_phone_match?(customer)
  relation_triple_state_presence_check(customer.all_contact_points.voice_callable, has_phone)
end

#has_phone_number_match?(customer) ⇒ Boolean

Returns:

  • (Boolean)


225
226
227
228
229
# File 'app/models/customer_filter.rb', line 225

def has_phone_number_match?(customer)
  phones = customer.all_contact_points.dialable
  return true if phones.empty?
  phones.detect{|cp| meta_string_match cp.detail, phone_number_starts_with, false, :allow_partial }
end

#has_sales_order_match?(customer) ⇒ Boolean

Returns:

  • (Boolean)


195
196
197
# File 'app/models/customer_filter.rb', line 195

def has_sales_order_match?(customer)
  relation_triple_state_presence_check(customer.orders.so_only.active, has_sales_order)
end

#iq_accessory_filtersActiveRecord::Relation<IqAccessoryFilter>

Returns:

See Also:



89
# File 'app/models/customer_filter.rb', line 89

has_many :iq_accessory_filters, inverse_of: :customer_filter

#meta_assoc_match(assoc, value, exclude) ⇒ Object

Convenience method to rescue us from repetition. Association to check against, value to check for, exclude turned on or off
Optimized to use in-memory lookup when association is preloaded to avoid N+1 queries



418
419
420
421
422
423
424
425
426
427
428
429
# File 'app/models/customer_filter.rb', line 418

def meta_assoc_match(assoc, value, exclude)
  # Use loaded? check to avoid N+1 - if preloaded, check in memory
  if assoc.loaded?
    return true if assoc.empty?
    value_in_list = assoc.any? { |record| record.id == value }
  else
    # When not preloaded, fall back to database queries
    return true if assoc.empty?
    value_in_list = assoc.exists?(id: value)
  end
  exclude ? !value_in_list : value_in_list
end

#meta_string_match(value, values, exclude, allow_partial = false) ⇒ Object



431
432
433
434
435
436
437
438
439
# File 'app/models/customer_filter.rb', line 431

def meta_string_match(value, values, exclude, allow_partial = false)
  return true unless values.present?
  if allow_partial
    value_in_list = value && value.starts_with?(*values)
  else
    value_in_list = value.in?(values)
  end
  exclude ? !value_in_list : value_in_list
end

#partiesActiveRecord::Relation<Party>

Returns:

  • (ActiveRecord::Relation<Party>)

See Also:



95
# File 'app/models/customer_filter.rb', line 95

has_and_belongs_to_many :parties, validate: false

#party_match?(customer) ⇒ Boolean

Returns:

  • (Boolean)


163
164
165
# File 'app/models/customer_filter.rb', line 163

def party_match?(customer)
  meta_assoc_match parties, customer.id, exclude_parties
end

#postal_code_match?(customer) ⇒ Boolean

Returns:

  • (Boolean)


175
176
177
# File 'app/models/customer_filter.rb', line 175

def postal_code_match?(customer)
  meta_string_match customer.zip, postal_codes, exclude_postal_codes, :allow_partial
end

#profile_match?(customer) ⇒ Boolean

Returns:

  • (Boolean)


143
144
145
# File 'app/models/customer_filter.rb', line 143

def profile_match?(customer)
  meta_assoc_match profiles, customer.profile_id, exclude_profile_ids
end

#profilesActiveRecord::Relation<Profile>

Returns:

  • (ActiveRecord::Relation<Profile>)

See Also:



92
# File 'app/models/customer_filter.rb', line 92

has_and_belongs_to_many :profiles

#raw_digest_fieldsObject



405
406
407
# File 'app/models/customer_filter.rb', line 405

def raw_digest_fields
  fields_array.join('|').to_s
end

#referenced?Boolean

Returns:

  • (Boolean)


129
130
131
132
133
134
135
136
137
# File 'app/models/customer_filter.rb', line 129

def referenced?
  (
    sales_rep_queue_entries.present? or
    topics.present? or
    activity_types.present? or
    coupons.present? or
    auto_apply_coupons.present?
  )
end

#report_grouping_match?(customer) ⇒ Boolean

Returns:

  • (Boolean)


183
184
185
# File 'app/models/customer_filter.rb', line 183

def report_grouping_match?(customer)
  meta_string_match customer.effective_report_grouping, report_groupings, exclude_report_groupings
end

#sales_rep_queue_entriesActiveRecord::Relation<SalesRepQueueEntry>

Returns:

See Also:



84
# File 'app/models/customer_filter.rb', line 84

has_many :sales_rep_queue_entries, inverse_of: :customer_filter

#source_match?(customer) ⇒ Boolean

Returns:

  • (Boolean)


159
160
161
# File 'app/models/customer_filter.rb', line 159

def source_match?(customer)
  meta_assoc_match sources, customer.source_id, exclude_source_ids
end

#sourcesActiveRecord::Relation<Source>

Returns:

  • (ActiveRecord::Relation<Source>)

See Also:



94
# File 'app/models/customer_filter.rb', line 94

has_and_belongs_to_many :sources

#state_code_match?(customer) ⇒ Boolean

Returns:

  • (Boolean)


171
172
173
# File 'app/models/customer_filter.rb', line 171

def state_code_match?(customer)
  meta_string_match customer.state_code, state_codes, exclude_state_codes
end

#storeStore

Returns:

See Also:



83
# File 'app/models/customer_filter.rb', line 83

belongs_to :store, optional: true

#store_match?(customer) ⇒ Boolean

Returns:

  • (Boolean)


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

def store_match?(customer)
  store.nil? or customer.store == store
end

#tier2_program_pricing_match?(customer) ⇒ Boolean

Returns:

  • (Boolean)


147
148
149
# File 'app/models/customer_filter.rb', line 147

def tier2_program_pricing_match?(customer)
  meta_assoc_match tier2_program_pricings, customer.tier2_program_pricing_id, exclude_tier2_program_pricing_ids
end

#tier2_program_pricingsActiveRecord::Relation<Coupon::Tier2ProgramPricing>

Returns:

See Also:



96
97
98
# File 'app/models/customer_filter.rb', line 96

has_and_belongs_to_many :tier2_program_pricings, class_name: 'Coupon::Tier2ProgramPricing',
join_table: 'customer_filters_program_pricings',
association_foreign_key: 'tier2_program_pricing_id'

#to_sObject



125
126
127
# File 'app/models/customer_filter.rb', line 125

def to_s
  "#{name} (#{id})"
end

#topicsActiveRecord::Relation<Topic>

Returns:

  • (ActiveRecord::Relation<Topic>)

See Also:



85
# File 'app/models/customer_filter.rb', line 85

has_many :topics, inverse_of: :customer_filter