Class: ActivityType

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

Overview

== Schema Information

Table name: activity_types
Database name: primary

id :integer not null, primary key
allow_time_lock :boolean default(FALSE), not null
autopin :boolean default(TRUE), not null
closing_instructions :text
description :string(255)
email_defer_days :integer
email_defer_tod :time
grouping :string(255)
inactive :boolean default(FALSE), not null
instructions :text
max_age_in_days :integer
next_instructions :text
notify_assigned_resource :boolean default(FALSE), not null
party_requirement :integer default("party_unrestricted"), not null
priority :integer default(2)
resource_requirement :integer default("resource_optional")
resource_restriction :string default([]), is an Array
sales_rep_as_sender :boolean default(FALSE), not null
skip_email_template_with_result :boolean default(FALSE), not null
task_type :string(255)
uniqueness :integer default("unrestricted")
created_at :datetime
updated_at :datetime
campaign_id :integer
customer_filter_id :integer
default_assignee_id :integer
email_template_id :integer
sender_party_id :integer

Indexes

activity_types_customer_filter_id_idx (customer_filter_id)
activity_types_email_template_id_idx (email_template_id)
by_id_w (id) WHERE (priority IS NOT NULL)
by_idn_w (id) WHERE (priority IS NULL)
index_activity_types_on_campaign_id (campaign_id)
index_activity_types_on_inactive_and_task_type (inactive,task_type)
index_activity_types_on_priority (priority)
index_activity_types_on_sender_party_id (sender_party_id)
index_activity_types_on_task_type (task_type)

Foreign Keys

activity_types_customer_filter_id_fk (customer_filter_id => customer_filters.id)
activity_types_email_template_id_fk (email_template_id => email_templates.id) ON DELETE => nullify
fk_rails_... (campaign_id => campaigns.id)
fk_rails_... (sender_party_id => parties.id)

Constant Summary collapse

PRIORITY_MAX =

Priority Activities always get assigned to someone

2
TOTAL_TIERS =
5
TAGS_FOR_REPORTS =
[['CLOSE_THE_DEAL', 0], ['LEAD_THE_WAY', 1], ['NURTURE_ME', 2], ['MISC', 3], ['CALLBLOCK', 4], ['OTHERS', 5]].freeze

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Instance Attribute Summary collapse

Belongs to collapse

Methods included from Models::Auditable

#creator, #updater

Has many collapse

Methods included from Models::Taggable

#tag_records, #taggings

Has and belongs to many collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Models::Taggable

#add_tag, all_tags, #has_tag?, normalize_tag_names, not_tagged_with, #remove_tag, #tag_list, #tag_list=, #taggable_type_for_tagging, tagged_with, #tags, #tags=, tags_cloud, tags_exclude, tags_include, with_all_tags, with_any_tags, without_all_tags, without_any_tags

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, ransortable_attributes, #to_relation

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#descriptionObject (readonly)



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

validates :task_type, :priority, :description, :party_requirement, :uniqueness, presence: true

#party_requirementObject (readonly)



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

validates :task_type, :priority, :description, :party_requirement, :uniqueness, presence: true

#priorityObject (readonly)



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

validates :task_type, :priority, :description, :party_requirement, :uniqueness, presence: true

#task_typeObject (readonly)



90
# File 'app/models/activity_type.rb', line 90

validates :task_type, uniqueness: true

#uniquenessObject (readonly)



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

validates :task_type, :priority, :description, :party_requirement, :uniqueness, presence: true

Class Method Details

.activeActiveRecord::Relation<ActivityType>

A relation of ActivityTypes that are active. Active Record Scope

Returns:

See Also:



97
# File 'app/models/activity_type.rb', line 97

scope :active, -> { where(inactive: false) }

.condensed_options_for_selectObject



157
158
159
# File 'app/models/activity_type.rb', line 157

def self.condensed_options_for_select
  active.sorted.map { |at| [at.task_type, at.id] }
end

.email_activitiesActiveRecord::Relation<ActivityType>

A relation of ActivityTypes that are email activities. Active Record Scope

Returns:

See Also:



105
# File 'app/models/activity_type.rb', line 105

scope :email_activities, -> { where(ActivityType[:task_type].matches('EMAIL%')) }

.meetingActiveRecord::Relation<ActivityType>

A relation of ActivityTypes that are meeting. Active Record Scope

Returns:

See Also:



102
# File 'app/models/activity_type.rb', line 102

scope :meeting, -> { where(task_type: ActivityTypeConstants::MEETING_TYPES) }

.options_for_select(options = {}) ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'app/models/activity_type.rb', line 121

def self.options_for_select(options = {})
  cache_key = [:activity_options_for_select]
  cache_key << (options[:party] ? options[:party].cache_key : :all_parties)
  cache_key << (options[:include_activity_type_id] || :default)
  cache_key << (options[:exclude_activity_type_id] || :default)
  cache_key << ActivityType.maximum(:updated_at).to_i
  Rails.cache.fetch(cache_key, expires_in: 15.minutes) do
    activities = ActivityType.where(ActivityType[:inactive].eq(false))
    activities = activities.where.not(id: options[:exclude_activity_type_id]) if options[:exclude_activity_type_id]
    activities = activities.or(ActivityType.where(id: options[:include_activity_type_id])) if options[:include_activity_type_id].present?
    if options[:party].present?
      # Preload customer_filter with all associations to avoid N+1 queries in valid_for_party?
      activities = activities.includes(customer_filter: [:parties, :catalogs, :profiles, :buying_groups, :sources, :tier2_program_pricings])
      activities = activities.select { |at| at.id == options[:include_activity_type_id] || at.valid_for_party?(options[:party]) }
    end
    activities = activities.sort_by(&:task_type)
    activities.map { |at| [at.name, at.id] }
  end
end

.options_for_select_by_user(user, extra_at_id = nil) ⇒ Object



238
239
240
241
242
243
244
245
# File 'app/models/activity_type.rb', line 238

def self.options_for_select_by_user(user, extra_at_id = nil)
  rids = user..inherited_role_ids
  ActivityType.where(
    'NOT EXISTS(select 1 from activity_types_roles atr WHERE atr.activity_type_id = activity_types.id) OR EXISTS(select 1 from activity_types_roles atr WHERE atr.activity_type_id = activity_types.id AND atr.role_id IN (?)) OR activity_types.id = ?', rids, extra_at_id
  ).order('activity_types.task_type').map do |at|
    [at.name, at.id]
  end
end

.options_for_select_shortObject



141
142
143
# File 'app/models/activity_type.rb', line 141

def self.options_for_select_short
  ActivityType.where.not(inactive: true).order(:task_type).pluck(:task_type, :id)
end

.party_requirement_options_for_selectObject



153
154
155
# File 'app/models/activity_type.rb', line 153

def self.party_requirement_options_for_select
  party_requirements.map { |k, _v| [k.humanize, k] }
end

.priority_friendly_name(i) ⇒ Object



165
166
167
168
169
# File 'app/models/activity_type.rb', line 165

def self.priority_friendly_name(i)
  return 'Unprioritized' unless i

  "Tier #{i} #{'(High Priority)' if i <= PRIORITY_MAX}"
end

.priority_select_optionsObject



161
162
163
# File 'app/models/activity_type.rb', line 161

def self.priority_select_options
  (1...6).to_a.map { |i| [priority_friendly_name(i), i] }
end

.ransackable_scopes(_auth_object = nil) ⇒ Object



117
118
119
# File 'app/models/activity_type.rb', line 117

def self.ransackable_scopes(_auth_object = nil)
  %i[tagged_with not_tagged_with tags_include]
end

.resource_requirement_options_for_selectObject



149
150
151
# File 'app/models/activity_type.rb', line 149

def self.resource_requirement_options_for_select
  resource_requirements.map { |k, _v| [k.humanize, k] }
end

.resource_type_for_selectObject



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

def self.resource_type_for_select
  %w[ServiceJob CreditApplication Invoice SpiffEnrollment Party PurchaseOrder Delivery Opportunity SupportCaseParticipant Order CreditMemo Quote RoomConfiguration SupportCase LocatorRecord Rma].map { |v| [v.titleize.humanize, v] }
end

.sales_activitiesActiveRecord::Relation<ActivityType>

A relation of ActivityTypes that are sales activities. Active Record Scope

Returns:

See Also:



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

scope :sales_activities, -> { tagged_with('sale', 'sales_call') }

.sortedActiveRecord::Relation<ActivityType>

A relation of ActivityTypes that are sorted. Active Record Scope

Returns:

See Also:



98
# File 'app/models/activity_type.rb', line 98

scope :sorted, -> { order(:priority, :task_type) }

.trainingActiveRecord::Relation<ActivityType>

A relation of ActivityTypes that are training. Active Record Scope

Returns:

See Also:



101
# File 'app/models/activity_type.rb', line 101

scope :training, -> { where(task_type: ActivityTypeConstants::TRAINING_TYPES) }

.uniqueness_options_for_selectObject



145
146
147
# File 'app/models/activity_type.rb', line 145

def self.uniqueness_options_for_select
  uniquenesses.map { |k, _v| [k.humanize, k] }
end

.with_customer_filterActiveRecord::Relation<ActivityType>

A relation of ActivityTypes that are with customer filter. Active Record Scope

Returns:

See Also:



100
# File 'app/models/activity_type.rb', line 100

scope :with_customer_filter, -> { where.not(activity_types: { customer_filter_id: nil }) }

.with_open_counterActiveRecord::Relation<ActivityType>

A relation of ActivityTypes that are with open counter. Active Record Scope

Returns:

See Also:



99
# File 'app/models/activity_type.rb', line 99

scope :with_open_counter, -> { select('activity_types.*, (select count(a.id) from activities a where a.activity_type_id = activity_types.id and a.activity_result_type_id IS NULL) as open_counter') }

Instance Method Details

#activitiesActiveRecord::Relation<Activity>

Returns:

See Also:



81
# File 'app/models/activity_type.rb', line 81

has_many :activities, dependent: :nullify, inverse_of: :activity_type

#activity_chain_typesActiveRecord::Relation<ActivityChainType>

Returns:

See Also:



82
# File 'app/models/activity_type.rb', line 82

has_many :activity_chain_types, dependent: :destroy

#activity_chain_types_for_selectObject



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

def activity_chain_types_for_select
  activity_chain_types.map { |act| [act.activity_result_type.result_code, act.id] }
end

#activity_result_typesActiveRecord::Relation<ActivityResultType>

Returns:

See Also:



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

has_many :activity_result_types, through: :activity_chain_types

#activity_type_assignment_queuesActiveRecord::Relation<ActivityTypeAssignmentQueue>

Returns:

See Also:



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

has_many :activity_type_assignment_queues, dependent: :destroy

#activity_type_rulesActiveRecord::Relation<ActivityTypeRule>

Returns:

See Also:



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

has_many :activity_type_rules, dependent: :destroy

#assignment_queuesActiveRecord::Relation<AssignmentQueue>

Returns:

See Also:



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

has_many :assignment_queues, through: :activity_type_assignment_queues

#auto_close_result_chainObject



320
321
322
# File 'app/models/activity_type.rb', line 320

def auto_close_result_chain
  activity_chain_types.detect { |act| !act.not_set? }
end

#campaignCampaign

Returns:

See Also:



80
# File 'app/models/activity_type.rb', line 80

belongs_to :campaign, optional: true

#cancel_invalid_activities(options = {}) ⇒ Object

Over time activity type rules can change.
in particular the customer state restriction
Whenever this is called all activities open with a customer state restriction
now met will be cancelled



259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'app/models/activity_type.rb', line 259

def cancel_invalid_activities(options = {})
  return unless customer_filter

  logr = options[:logger] || logger
  logr.info "ActivityType#cancel_invalid_activities for Activity Type id (#{id}/#{task_type}), looking for activities scheduled on customer not matching filter #{customer_filter}"
  counter = 0
  error_msg = []
  invalid_activities(options).find_each do |activity|
    logr.info "Invalidating activity id #{activity.id}"
    if invalidate_activity(activity, options)
      counter += 1
    else
      err_msg = "Activity id #{activity.id} #{activity.errors_to_s}"
      logr.error err_msg
      error_msg << err_msg
    end
  end
  msg = "Cancelled invalid activities completed, cancelled: #{counter} activities"
  logr.info msg
  msg << ", errors: \n#{error_msg.join(",\n")}" if error_msg.present?
  msg
end

#customer_filterCustomerFilter



79
# File 'app/models/activity_type.rb', line 79

belongs_to :customer_filter, optional: true

#deep_dupObject



64
65
66
67
68
# File 'app/models/activity_type.rb', line 64

def deep_dup
  deep_clone(include: [:activity_chain_types, :activity_type_assignment_queues]) do |original, copy|
    copy.task_type = "#{original.task_type}_COPY" if copy.is_a?(ActivityType)
  end
end

#default_assigneeEmployee

Returns:

See Also:



76
# File 'app/models/activity_type.rb', line 76

belongs_to :default_assignee, class_name: 'Employee', optional: true

#determine_assigned_resource(party, cur_user_id = nil, target_date = nil, resource = nil, options = {}) ⇒ Object



199
200
201
202
203
204
# File 'app/models/activity_type.rb', line 199

def determine_assigned_resource(party, cur_user_id = nil, target_date = nil, resource = nil, options = {})
  rep = nil
  rep_id = determine_assigned_resource_id(party, cur_user_id, target_date, resource, options)
  rep = Employee.find(rep_id) if rep_id
  rep
end

#determine_assigned_resource_id(party, cur_user_id = nil, target_date = nil, resource = nil, options = {}) ⇒ Object

For a given customer (which is handled by a specific wy company) this method will determine
the resource to assign to the activity
party : the customer object, contact, or supplier
cur_user_id : the current user
target_date : the intended target date for the activity, this is used to determine the individual resource when a
group type assignment is desired and we need to determine the least busy agent.



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'app/models/activity_type.rb', line 222

def determine_assigned_resource_id(party, cur_user_id = nil, target_date = nil, resource = nil, options = {})
  assigned_resource_id = nil
  company_id = party&.determine_company_id || Company::USA
  customer = party&.customer

  begin
    if ataq = activity_type_assignment_queues.detect { |ataq| ataq.company_id == company_id }
      assigned_resource_id = ataq.get_first_resource(customer, cur_user_id, target_date, priority, nil, resource, options)
    end
  rescue AssignmentQueue::UnassignableActivity
    logger.error "Could not determine an assignable rep for activity type #{id} on party_id #{party.id}"
  end
  assigned_resource_id ||= cur_user_id
  assigned_resource_id
end

#email_templateEmailTemplate



78
# File 'app/models/activity_type.rb', line 78

belongs_to :email_template, optional: true

#email_template_descriptionObject



324
325
326
327
328
329
330
331
# File 'app/models/activity_type.rb', line 324

def email_template_description
  s = []
  s << email_template.description
  s << "[#{email_template.category}]" if email_template.category.present?
  s << "+#{email_defer_days}d" if email_defer_days.present?
  s << "@#{email_defer_tod.strftime('%I:%M %p')}" if email_defer_tod.present?
  s.join(' ')
end

#email_transmit_at_timeObject

Calculates the date/time when the creation email for this activity
should be transmitted. It will transmit immediately by default. If the
activity type has configured email deferral rules, it will apply those
by adding a number of days or setting the time of day to transmit.



337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
# File 'app/models/activity_type.rb', line 337

def email_transmit_at_time
  # Determine here if we send immediately or schedule the email
  return unless email_defer_days&.positive? || email_defer_tod.present?

  transmit_at = Time.current
  if email_defer_days&.positive?
    # Add this number of days to the current time
    transmit_at += email_defer_days.days
  end
  if email_defer_tod
    # If email has a deferal rule for a specific time of the day
    transmit_at = email_defer_tod.on(transmit_at)
    # But if the time of day has passed already we advance to the next day
    transmit_at += 1.day if transmit_at < Time.current
  end
  transmit_at
end

#invalid_activities(options = {}) ⇒ Object



295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'app/models/activity_type.rb', line 295

def invalid_activities(options = {})
  res = Activity.none
  return res unless customer_filter

  logger.info "ActivityType#cancel_invalid_activities : Hunting for customers with a filter that does not match #{customer_filter}"
  invalid_customer_ids = []
  customers = Customer.joins(:linked_activities).merge(activities.open_activities)
  customers = customers.limit(options[:batch_size]) if options[:batch_size].present?
  customers.find_each do |customer|
    invalid_customer_ids << customer.id unless valid_for_party?(customer)
  end
  invalid_customer_ids = invalid_customer_ids.compact.uniq
  res = activities.open_activities.where(customer_id: invalid_customer_ids).select('activities.*') if invalid_customer_ids.present?
  res
end

#invalidate_activity(a, _options = {}) ⇒ Object



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

def invalidate_activity(a, _options = {})
  a.new_note = 'Cancelled after customer state change no longer meets criteria established for this activity type.'
  a.activity_result_type_id = ActivityResultTypeConstants::CANCEL
  a.completion_datetime = Time.current
  res = a.save
  if res
    logger.info(" * cancelled activity id #{a.id}")
  else
    logger.error(" * could not cancel activity id #{a.id}")
  end
  res
end

#is_a_quote_follow_up?Boolean

Returns:

  • (Boolean)


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

def is_a_quote_follow_up?
  ActivityTypeConstants::QUOFUS_IDS.include?(id)
end

#is_email?Boolean

Returns:

  • (Boolean)


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

def is_email?
  task_type.match?(/^EMAIL/)
end

#nameObject



247
248
249
# File 'app/models/activity_type.rb', line 247

def name
  "#{task_type} - #{description}"
end

#priority_friendly_nameObject



251
252
253
# File 'app/models/activity_type.rb', line 251

def priority_friendly_name
  self.class.priority_friendly_name(priority)
end

#priority_tier?Boolean

Returns:

  • (Boolean)


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

def priority_tier?
  priority && priority <= PRIORITY_MAX
end

#result_options_for_selectObject



206
207
208
209
210
211
212
213
214
# File 'app/models/activity_type.rb', line 206

def result_options_for_select
  # Use includes instead of joins to preload activity_result_type and avoid N+1
  chains = activity_chain_types.includes(:activity_result_type, :email_template)
  chains.map do |c|
    display = c.activity_result_type.result_code.to_s
    display << " [Email: #{c.email_template.description}]" if c.email_template
    [display, c.activity_result_type_id]
  end.uniq
end

#rolesActiveRecord::Relation<Role>

Returns:

  • (ActiveRecord::Relation<Role>)

See Also:



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

has_and_belongs_to_many :roles

#sales_activityObject



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

def sales_activity
  has_tag?('sale')
end

#sender_partyEmployee

Returns:

See Also:



77
# File 'app/models/activity_type.rb', line 77

belongs_to :sender_party, class_name: 'Employee', optional: true

#to_sObject



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

def to_s
  "#{task_type} [#{id}]"
end

#valid_for_party?(party) ⇒ Boolean

Returns:

  • (Boolean)


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

def valid_for_party?(party)
  return true unless party
  return true unless customer_filter
  return true unless customer = party.try(:customer)
  return true unless customer.is_a?(Customer) # Now we have suppliers

  customer_filter.applies_to_customer?(customer)
end