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 :enum default("party_unrestricted"), not null
priority :integer default(2)
resource_requirement :enum 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 :enum 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 =

Total tiers.

5
TAGS_FOR_REPORTS =

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

Constants included from Schedulable

Schedulable::SIMPLE_FORM_OPTIONS

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 Schedulable

config

Methods included from Models::AfterCommittable

#after_commit

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#descriptionObject (readonly)



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

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

#party_requirementObject (readonly)



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

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

#priorityObject (readonly)



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

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

#task_typeObject (readonly)



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

validates :task_type, uniqueness: true

#uniquenessObject (readonly)



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

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:



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

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

.condensed_options_for_selectObject



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

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:



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

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:



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

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

.options_for_select(options = {}) ⇒ Object



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'app/models/activity_type.rb', line 125

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: %i[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



242
243
244
245
246
247
248
249
# File 'app/models/activity_type.rb', line 242

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



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

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

.party_requirement_options_for_selectObject



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

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

.priority_friendly_name(i) ⇒ Object



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

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

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

.priority_select_optionsObject



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

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

.ransackable_scopes(_auth_object = nil) ⇒ Object



121
122
123
# File 'app/models/activity_type.rb', line 121

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

.resource_requirement_options_for_selectObject



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

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

.resource_type_for_selectObject



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

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:



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

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

.sortedActiveRecord::Relation<ActivityType>

A relation of ActivityTypes that are sorted. Active Record Scope

Returns:

See Also:



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

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

.trainingActiveRecord::Relation<ActivityType>

A relation of ActivityTypes that are training. Active Record Scope

Returns:

See Also:



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

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

.uniqueness_options_for_selectObject



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

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:



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

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:



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

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:



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

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

#activity_chain_typesActiveRecord::Relation<ActivityChainType>

Returns:

See Also:



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

has_many :activity_chain_types, dependent: :destroy

#activity_chain_types_for_selectObject



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

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:



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

has_many :activity_result_types, through: :activity_chain_types

#activity_type_assignment_queuesActiveRecord::Relation<ActivityTypeAssignmentQueue>

Returns:

See Also:



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

has_many :activity_type_assignment_queues, dependent: :destroy

#activity_type_rulesActiveRecord::Relation<ActivityTypeRule>

Returns:

See Also:



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

has_many :activity_type_rules, dependent: :destroy

#assignment_queuesActiveRecord::Relation<AssignmentQueue>

Returns:

See Also:



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

has_many :assignment_queues, through: :activity_type_assignment_queues

#auto_close_result_chainObject



324
325
326
# File 'app/models/activity_type.rb', line 324

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

#campaignCampaign

Returns:

See Also:



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

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



263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'app/models/activity_type.rb', line 263

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



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

belongs_to :customer_filter, optional: true

#deep_dupObject



67
68
69
70
71
# File 'app/models/activity_type.rb', line 67

def deep_dup
  deep_clone(include: %i[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:



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

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

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



203
204
205
206
207
208
# File 'app/models/activity_type.rb', line 203

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.



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'app/models/activity_type.rb', line 226

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.find { |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



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

belongs_to :email_template, optional: true

#email_template_descriptionObject



328
329
330
331
332
333
334
335
# File 'app/models/activity_type.rb', line 328

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.



341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
# File 'app/models/activity_type.rb', line 341

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



299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'app/models/activity_type.rb', line 299

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



286
287
288
289
290
291
292
293
294
295
296
297
# File 'app/models/activity_type.rb', line 286

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)


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

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

#is_email?Boolean

Returns:

  • (Boolean)


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

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

#nameObject



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

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

#priority_friendly_nameObject



255
256
257
# File 'app/models/activity_type.rb', line 255

def priority_friendly_name
  self.class.priority_friendly_name(priority)
end

#priority_tier?Boolean

Returns:

  • (Boolean)


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

def priority_tier?
  priority && priority <= PRIORITY_MAX
end

#result_options_for_selectObject



210
211
212
213
214
215
216
217
218
# File 'app/models/activity_type.rb', line 210

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:



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

has_and_belongs_to_many :roles

#sales_activityObject



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

def sales_activity
  has_tag?('sale')
end

#sender_partyEmployee

Returns:

See Also:



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

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

#to_sObject



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

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

#valid_for_party?(party) ⇒ Boolean

Returns:

  • (Boolean)


315
316
317
318
319
320
321
322
# File 'app/models/activity_type.rb', line 315

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