Class: Activity::Prioritizer
Defined Under Namespace
Classes: Result
Constant Summary
collapse
- PRIORITIZATION_SELECT =
<<-EOS
activities.*,
COALESCE(cat.company_id, pc.company_id, 1) as company_id,
activity_types.priority as activity_type_priority,
pc.sales_priority_index,
COALESCE(activities.original_target_datetime,activities.target_datetime,activities.created_at) as original_target_datetime,
pc.full_name as customer_full_name,
pc.id as customer_id,
pc.primary_sales_rep_id,
pc.secondary_sales_rep_id,
pc.local_sales_rep_id,
pc.service_rep_id,
er.backup_rep_id
EOS
Instance Attribute Summary collapse
-
#activity_deck ⇒ Object
readonly
Pass these options to the initializer :allocation_start, if not specified, will look at start_range and if not use today's date :start_range, how far back to look for activities to reschedule :end_range, how far ahead to look for activities to reschedule.
Class Method Summary
collapse
Instance Method Summary
collapse
-
#activities_to_process? ⇒ Boolean
-
#allocate_priority_activities(activities, allocation_start) ⇒ Object
-
#allocate_standard_activities(activities, allocation_start) ⇒ Object
-
#append_to_decks(activities) ⇒ Object
Takes an list of activities, divide them in time sensitive and non time sensitive queues and load up our decks accordingly with a sort.
-
#assign_activity(activities, assign_rep_id, target_date) ⇒ Object
-
#available_activities_on_day_for_rep(rep_id, target_date) ⇒ Object
Returns the number of activity slots available for a rep_id on a given day This is the maximum allocatable per day.
-
#available_priority_activities_on_day_for_rep(rep_id, target_date) ⇒ Object
-
#can_take_activities_on_day?(rep_id, target_date) ⇒ Boolean
-
#can_take_priority_activities_on_day?(rep_id, target_date) ⇒ Boolean
-
#commit_assignment_matrix ⇒ Object
-
#employees_hash ⇒ Object
-
#extract_time_locked_activities ⇒ Object
this method extract all time locked activities in the future and pre-fills them in our assignment matrix.
-
#initialize_deck ⇒ Object
Initialize the deck of activities to sort.
-
#load_activities_for_processing(start_range = nil, end_range = nil) ⇒ Object
Loads up activities suitable for reorganization based on a date range.
-
#log_error(msg) ⇒ Object
-
#log_info(msg) ⇒ Object
-
#log_warning(msg) ⇒ Object
-
#mark_processed(success, a) ⇒ Object
This marks an activity as having been processed by keeping track in an internal array.
-
#prepare_activity(activity) ⇒ Object
-
#process(options = {}, &block) ⇒ Object
Start prioritization routine, will return a result object options are allocation_start: from what point you want to start allocating deck_limit: how many activities you want to process priorities: priorirty of activity to process rep_ids: focus only on those rep_ids (array) Yields back position, total to process, and info message to a block (optional).
-
#reps_available_for_activities_on_day(day) ⇒ Object
Returns an array of employee ids that have room for activities on a given day.
-
#reset_time_locks ⇒ Object
-
#sort_deck ⇒ Object
-
#to_s ⇒ Object
Methods inherited from BaseService
#initialize, #log_debug, #logger, #options, #tagged_logger
Constructor Details
This class inherits a constructor from BaseService
Instance Attribute Details
#activity_deck ⇒ Object
Pass these options to the initializer
:allocation_start, if not specified, will look at start_range and if not use today's date
:start_range, how far back to look for activities to reschedule
:end_range, how far ahead to look for activities to reschedule
7
8
9
|
# File 'app/services/activity/prioritizer.rb', line 7
def activity_deck
@activity_deck
end
|
Class Method Details
.assignable_activity?(a) ⇒ Boolean
369
370
371
372
|
# File 'app/services/activity/prioritizer.rb', line 369
def self.assignable_activity?(a)
(a.activity_type_priority <= ActivityType::PRIORITY_MAX and (a.assignment_resource_ids.present? or a.fallback_employee_id.present?)) or
(a.activity_type_priority > ActivityType::PRIORITY_MAX and a.assignment_resource_ids.present?)
end
|
.high_priority_activity_levels ⇒ Object
374
375
376
|
# File 'app/services/activity/prioritizer.rb', line 374
def self.high_priority_activity_levels
(1..ActivityType::PRIORITY_MAX).to_a
end
|
.standard_priority_activity_levels ⇒ Object
.workload_based_on_matrix(assignment_matrix, rep_id, target_date, priorities = []) ⇒ Object
203
204
205
206
207
208
209
210
211
|
# File 'app/services/activity/prioritizer.rb', line 203
def self.workload_based_on_matrix(assignment_matrix, rep_id, target_date, priorities = [])
assignment_matrix[rep_id] ||= {}
assignment_matrix[rep_id][target_date] ||= {}
wl = 0
assignment_matrix[rep_id][target_date].each do |priority, activities|
wl += activities.size if priorities.empty? or priorities.include?(priority)
end
wl
end
|
Instance Method Details
#activities_to_process? ⇒ Boolean
284
285
286
|
# File 'app/services/activity/prioritizer.rb', line 284
def activities_to_process?
@activity_deck.present?
end
|
#allocate_priority_activities(activities, allocation_start) ⇒ Object
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
# File 'app/services/activity/prioritizer.rb', line 106
def allocate_priority_activities(activities, allocation_start)
while activities.present?
play_deck = activities.select { |a| a.original_target_datetime.to_date <= allocation_start }
full_rep_list_ids = employees_hash.values.map(&:id)
working_rep_ids = reps_available_for_activities_on_day(allocation_start)
rep_ids_with_cap_reached = [] list_size = play_deck.size
log_info "Allocating priority activities on #{allocation_start}, list is #{list_size}, working reps: #{working_rep_ids}, full list of reps: #{full_rep_list_ids}"
rep_ids_in_activity_pool = play_deck.map(&:assignment_resource_ids).flatten.uniq
play_deck.each_with_index do |a, i|
log_info "[#{i}/#{list_size}] AID: #{a.id}, Running through priority activity, assignable to #{a.assignment_resource_ids}, working_rep_ids: #{working_rep_ids}, rep_ids_with_cap_reached: #{rep_ids_with_cap_reached} on #{allocation_start}"
assignable_rep_ids = working_rep_ids - rep_ids_with_cap_reached
rep_ids = (a.assignment_resource_ids & assignable_rep_ids).compact.uniq
log_info "[#{i}/#{list_size}] AID: #{a.id}, final assignment possible: #{rep_ids}"
rep_ids.each do |rep_id|
if can_take_priority_activities_on_day?(rep_id, allocation_start)
assign_activity(a, rep_id, allocation_start)
activities.delete(a)
break else
log_warning "Cap reached for rep id #{rep_id}"
rep_ids_with_cap_reached << rep_id
rep_ids_in_activity_pool.delete(rep_id)
end
end if rep_ids_in_activity_pool.empty?
log_warning 'No one left to allocate activities to, moving on to next day'
break
end
end
allocation_start = allocation_start.next_business_day
rep_ids_with_cap_reached = []
end
end
|
#allocate_standard_activities(activities, allocation_start) ⇒ Object
145
146
147
148
149
150
151
152
153
154
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
|
# File 'app/services/activity/prioritizer.rb', line 145
def allocate_standard_activities(activities, allocation_start)
list_size = activities.size
raise 'Some activities are unassignable, abnormal at this state' if activities.detect { |a| a.assignment_resource_ids.empty? }
grouped_activities = activities.group_by { |a| a.assignment_resource_ids.first }
grouped_activities.each do |rep_id, rep_activities|
log_info "[Rep: #{rep_id}] looking for next working day from #{allocation_start}"
rep_allocation_date = employees_hash[rep_id].next_working_day(allocation_start, 0) if rep_allocation_date.nil?
log_info "[Rep: #{rep_id}] rep_allocation_date is not found, total activities for this rep: #{rep_activities.size}, skipping this rep"
@unprocessed += rep_activities
next
end
log_info "[Rep: #{rep_id}] [#{rep_allocation_date}] Total activities for this rep: #{rep_activities.size}"
while rep_activities.present?
allocatable_activities = rep_activities.select { |a| a.original_target_datetime <= rep_allocation_date.end_of_day }
max_activities_today = available_activities_on_day_for_rep(rep_id, rep_allocation_date)
log_info "[Rep: #{rep_id}] [#{rep_allocation_date}] Allocatable: #{allocatable_activities.size}, Available Slots: #{max_activities_today}"
allocate_activities = allocatable_activities.shift(max_activities_today)
assign_activity(allocate_activities, rep_id, rep_allocation_date)
rep_activities -= allocate_activities
log_info "[Rep: #{rep_id}] [#{rep_allocation_date}] Rep has #{rep_activities.size} remaining to allocate, moving on to next day"
next_allocatable_activities_date = rep_activities.map { |a| a.original_target_datetime.try(:to_date) }.compact.min
next_business_day = employees_hash[rep_id].next_working_day(rep_allocation_date)
rep_allocation_date = [next_allocatable_activities_date, next_business_day].compact.max
log_info "[Rep: #{rep_id}] About to process next working day: #{rep_allocation_date}"
end
end
end
|
#append_to_decks(activities) ⇒ Object
Takes an list of activities, divide them in time sensitive and non time sensitive queues and load up our decks accordingly with a sort
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
|
# File 'app/services/activity/prioritizer.rb', line 383
def append_to_decks(activities)
log_info "Reviewing #{activities.size} activities for addition to deck"
new_rep_ids = [] new_activities = activities.select { |a| a.activity_type_priority.to_i.positive? }.map do |a|
a.activity_type_priority = a.activity_type_priority.to_i
a.sales_priority_index = a.sales_priority_index.to_i
a
end
good_counter = 0
bad_counter = 0
new_activities.each do |na|
a = prepare_activity(na)
if a and self.class.assignable_activity?(a)
good_counter += 1
@activity_deck[a.id] = a
new_rep_ids |= [a.fallback_employee_id, a.assignment_resource_ids].flatten.compact
else
bad_counter += 1
log_warning " * Activity #{na.id} marked as unprocessable, no one could be assigned to it, review queue and customer data."
@unprocessed << a
end
end
log_info " * Added #{good_counter} activities to activity deck, now we have #{@activity_deck.size} good activities."
log_info " * Unprocessable: #{bad_counter}, total in unprocessable: #{@unprocessed.size}."
log_info " * Rep Matrix: #{new_rep_ids.size}, we now have #{employees_hash.size} loaded"
sort_deck end
|
#assign_activity(activities, assign_rep_id, target_date) ⇒ Object
191
192
193
194
195
196
197
198
199
200
201
|
# File 'app/services/activity/prioritizer.rb', line 191
def assign_activity(activities, assign_rep_id, target_date)
activities = [activities].flatten
log_info "[Rep: #{assign_rep_id}] [#{target_date}] Assigning activities #{activities.map(&:id)}"
@assignment_matrix[assign_rep_id] ||= {}
@assignment_matrix[assign_rep_id][target_date] ||= {}
activities.group_by { |a| a.activity_type_priority }.each do |activity_type_priority, sub_activities|
@assignment_matrix[assign_rep_id][target_date][activity_type_priority] ||= []
@assignment_matrix[assign_rep_id][target_date][activity_type_priority] += sub_activities.map(&:id)
end
activities.each { |a| mark_processed(true, a) }
end
|
#available_activities_on_day_for_rep(rep_id, target_date) ⇒ Object
Returns the number of activity slots available for a rep_id on a given day
This is the maximum allocatable per day
252
253
254
255
256
257
258
259
260
261
262
263
264
|
# File 'app/services/activity/prioritizer.rb', line 252
def available_activities_on_day_for_rep(rep_id, target_date)
target_date = target_date.to_date unless target_date.is_a?(Date)
if emp_obj = employees_hash[rep_id]
current = self.class.workload_based_on_matrix(@assignment_matrix, rep_id, target_date)
max = employees_hash[rep_id].maximum_activities_per_day(target_date)
available = [max - current, 0].max
log_info "[Rep: #{rep_id}] [#{target_date}] Activity Load is #{current}/#{max}, #{available} available activity slots"
available
else
log_error "[Rep: #{rep_id}] [#{target_date}] #{rep_id} is missing from employees_hash" unless emp_obj
0
end
end
|
#available_priority_activities_on_day_for_rep(rep_id, target_date) ⇒ Object
266
267
268
269
270
271
272
|
# File 'app/services/activity/prioritizer.rb', line 266
def available_priority_activities_on_day_for_rep(rep_id, target_date)
current = self.class.workload_based_on_matrix(@assignment_matrix, rep_id, target_date, self.class.high_priority_activity_levels)
max = employees_hash[rep_id].maximum_priority_tier_activities_per_day(target_date)
available = [max - current, 0].max
log_info "[Rep: #{rep_id}] [#{target_date}] Priority Activity Load is #{current}/#{max}, #{available} available priority activity slots"
available
end
|
#can_take_activities_on_day?(rep_id, target_date) ⇒ Boolean
274
275
276
277
|
# File 'app/services/activity/prioritizer.rb', line 274
def can_take_activities_on_day?(rep_id, target_date)
target_date = target_date.to_date unless target_date.is_a?(Date)
available_activities_on_day_for_rep(rep_id, target_date) > 0
end
|
#can_take_priority_activities_on_day?(rep_id, target_date) ⇒ Boolean
279
280
281
282
|
# File 'app/services/activity/prioritizer.rb', line 279
def can_take_priority_activities_on_day?(rep_id, target_date)
target_date = target_date.to_date unless target_date.is_a?(Date)
available_priority_activities_on_day_for_rep(rep_id, target_date) > 0
end
|
#commit_assignment_matrix ⇒ Object
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
|
# File 'app/services/activity/prioritizer.rb', line 220
def commit_assignment_matrix
update_time = Time.current.utc.to_fs(:db)
bad_activities = []
@assignment_matrix.each do |rep_id, dates|
Activity.transaction do dates.each do |date, priorities|
target_datetime = WorkingHours.advance_to_closing_time(date)
act_ids = []
priorities.each do |priority, activities|
act_ids += (activities || []).select { |i| i > 0 } end
next unless act_ids.present?
logger.info "Employee id: #{rep_id} -> Assigning #{act_ids.size} activities on #{target_datetime}, ids: #{act_ids.join(',')}"
Activity.where(id: act_ids, activity_result_type_id: nil).find_each do |activity|
activity.original_target_datetime ||= activity.target_datetime
activity.original_assigned_resource_id ||= activity.assigned_resource_id
activity.target_datetime = target_datetime
activity.assigned_resource_id = rep_id
activity.skip_callbacks = true activity.skip_check_for_open_sales_activity = true
activity.save end
end
end
end
end
|
#employees_hash ⇒ Object
347
348
349
350
351
352
353
354
|
# File 'app/services/activity/prioritizer.rb', line 347
def employees_hash
@employees_hash ||= Employee
.includes(:employee_events, :company, :employee_record)
.active
.select(:id, :full_name, :company_id)
.to_a
.index_by { |e| e.id }
end
|
this method extract all time locked activities in the future and pre-fills them in our assignment matrix
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
|
# File 'app/services/activity/prioritizer.rb', line 300
def
log_info 'Extracting Time Locked Activities that are not overdue and where rep is working'
time_locked_counter = 0
@activity_deck.select { |aid, a| a.lock_target_datetime }.each do |aid, a|
log_info "[AID:#{aid}] is time locked"
if a.assigned_resource_id && employee = employees_hash[a.assigned_resource_id]
if a.overdue? || overloaded = !can_take_activities_on_day?(a.assigned_resource_id, a.target_datetime.to_date)
log_info "[AID:#{aid}] time locked and overdue on #{a.target_datetime.to_date}, looking for a new date" if a.overdue?
log_info "[AID:#{aid}] time locked and rep #{employee.full_name} [#{employee.id}] is overloaded on #{a.target_datetime.to_date}, looking for a new date" if overloaded
new_date = [Date.current, a.target_datetime.to_date].max
until can_take_activities_on_day?(a.assigned_resource_id, new_date)
new_date += 1.day
new_date = employee.next_working_day(new_date, 0)
end
log_info "[AID:#{aid}] found new date for rep #{employee.full_name} on #{new_date}"
new_target_datetime = WorkingHours.advance_to_closing_time(new_date)
a.target_datetime = new_target_datetime
log_info "[AID:#{aid}] is overdue and will be moved to next working day for employee from today #{new_target_datetime}"
a.skip_callbacks = true
a.save
end
time_locked_counter += 1
@assignment_matrix[a.assigned_resource_id] ||= {}
@assignment_matrix[a.assigned_resource_id][a.target_datetime.to_date] ||= {}
@assignment_matrix[a.assigned_resource_id][a.target_datetime.to_date][a.activity_type_priority] ||= []
@assignment_matrix[a.assigned_resource_id][a.target_datetime.to_date][a.activity_type_priority] << -a.id mark_processed(true, a)
else
log_warning "[AID:#{aid}] was originally time locked but can no longer remain in that state"
@reset_time_lock_aids << aid
end
end
@assignment_matrix.each do |resource_id, target_datetime_hsh|
target_datetime_hsh.each do |target_date, priority_hsh|
priority_hsh.each do |priority, activity_ids|
log_info "[Rep #{resource_id}] [#{target_date}] [Priority: #{priority}] #{activity_ids.size} locked activities: #{activity_ids.map { |aid| aid.abs }.join(', ')}"
end
end
end
log_info "Extracted #{time_locked_counter} Time Locked Activities that are assignable"
end
|
#initialize_deck ⇒ Object
Initialize the deck of activities to sort.
357
358
359
360
361
362
363
364
365
366
367
|
# File 'app/services/activity/prioritizer.rb', line 357
def initialize_deck
ardeck = load_activities_for_processing
append_to_decks ardeck
reset_time_locks
activities_to_process?
end
|
#load_activities_for_processing(start_range = nil, end_range = nil) ⇒ Object
Loads up activities suitable for reorganization based on a date range
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
|
# File 'app/services/activity/prioritizer.rb', line 465
def load_activities_for_processing(start_range = nil, end_range = nil)
ardeck = Activity.non_notes.open_activities.joins(:activity_type)
.where('target_datetime < ?', 1.year.from_now) .joins('INNER JOIN parties p ON p.id = activities.party_id')
.joins('INNER JOIN parties pc ON pc.id = COALESCE(p.customer_id, p.id)')
.joins('INNER JOIN catalogs cat ON cat.id = pc.catalog_id')
.joins('LEFT JOIN parties e ON pc.primary_sales_rep_id = e.id')
.joins('LEFT JOIN employee_records er ON e.id = er.party_id')
.except(:select)
.select(PRIORITIZATION_SELECT)
ardeck = ardeck.where(activity_types: { priority: @priorities }) if @priorities.present?
ardeck = ardeck.where(Activity[:target_datetime].gteq(start_range.beginning_of_day)) if start_range.present?
ardeck = ardeck.where(Activity[:target_datetime].lteq(end_range.end_of_day)) if end_range.present?
ardeck = ardeck.limit(@deck_limit) if @deck_limit.present?
ardeck = ardeck.where('activities.assigned_resource_id IN (:rep_ids) OR activities.original_assigned_resource_id IN (:rep_ids)', rep_ids: [@rep_ids].flatten) if @rep_ids.present?
@activity_deck_size = ardeck.size
ardeck
end
|
#log_error(msg) ⇒ Object
489
490
491
492
|
# File 'app/services/activity/prioritizer.rb', line 489
def log_error(msg)
super
@block.call(@total_processed, @activity_deck_size, msg) if @block
end
|
#log_info(msg) ⇒ Object
484
485
486
487
|
# File 'app/services/activity/prioritizer.rb', line 484
def log_info(msg)
super
@block.call(@total_processed, @activity_deck_size, msg) if @block
end
|
#log_warning(msg) ⇒ Object
494
495
496
497
|
# File 'app/services/activity/prioritizer.rb', line 494
def log_warning(msg)
super
@block.call(@total_processed, @activity_deck_size, msg) if @block
end
|
#mark_processed(success, a) ⇒ Object
This marks an activity as having been processed by keeping track in an internal array
289
290
291
292
293
294
295
296
297
|
# File 'app/services/activity/prioritizer.rb', line 289
def mark_processed(success, a)
if success
@processed << a
else
@unprocessed << a
end
@total_processed += 1
@activity_deck.delete(a.id)
end
|
#prepare_activity(activity) ⇒ Object
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
|
# File 'app/services/activity/prioritizer.rb', line 423
def prepare_activity(activity)
at = @activity_types[activity.activity_type_id]
company_id = activity.company_id.to_i
resource_ids = []
if ataq = at.activity_type_assignment_queues.detect { |ataq| ataq.company_id == company_id }
customer = OpenStruct.new
customer.id = activity.customer_id.to_i
customer.primary_sales_rep_id = activity.primary_sales_rep_id.to_i if activity.primary_sales_rep_id.present?
customer.secondary_sales_rep_id = activity.secondary_sales_rep_id.to_i if activity.secondary_sales_rep_id.present?
customer.local_sales_rep_id = activity.local_sales_rep_id.to_i if activity.local_sales_rep_id.present?
customer.service_rep_id = activity.service_rep_id.to_i if activity.service_rep_id.present?
customer.backup_rep_id = activity.backup_rep_id.to_i if activity.backup_rep_id.present?
current_user_id = activity.assigned_resource_id || activity.creator_id
current_user_id = nil unless current_user_id.in?(employees_hash.keys)
resource_ids += ataq.get_full_queue_resources(customer, current_user_id)
activity.fallback_employee_id = ataq.fallback_employee_id
resource_ids << ataq.fallback_employee_id if ataq.fallback_employee_id && activity.activity_type_priority <= ActivityType::PRIORITY_MAX
end
if resource_ids.empty?
resource_ids << activity.assigned_resource_id
resource_ids << activity.creator_id
resource_ids << activity.primary_sales_rep_id
end
resource_ids = resource_ids.compact.uniq & employees_hash.keys
activity.assignment_resource_ids = resource_ids
return activity if activity.assignment_resource_ids.present?
log_warning "Activity: #{activity.id}, Activity Type #{activity.activity_type_id} does not have a valid assignment possible"
nil
end
|
#process(options = {}, &block) ⇒ Object
Start prioritization routine, will return a result object
options are
allocation_start: from what point you want to start allocating
deck_limit: how many activities you want to process
priorities: priorirty of activity to process
rep_ids: focus only on those rep_ids (array)
Yields back position, total to process, and info message to a block (optional)
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
# File 'app/services/activity/prioritizer.rb', line 39
def process(options = {}, &block)
@block = block
@allocation_start = options[:allocation_start] || Date.current
@allocation_end = @allocation_start
raise 'Allocation cannot start in the past' if @allocation_start < Date.current
@deck_limit = options[:deck_limit]
@priorities = options[:priorities]
@rep_ids = options[:rep_ids]
@current_user_id = options[:current_user_id]
@activity_deck = {}
@unprocessed = []
@processed = []
@loaded_days = []
@total_processed = 0
@reset_time_lock_aids = []
@activity_types = ActivityType.eager_load(:assignment_queues).index_by { |at| at.id }
@assignment_matrix = {}
whodunnit = ['Activity', 'Prioritizer', @current_user_id].compact.join('::')
res = nil
PaperTrail.request(whodunnit: whodunnit) do
initialize_deck
log_info '* Activity Prioritizer ready to roll, call #prioritize'
log_info " #{@activity_deck.size} activities loaded"
log_info " #{@unprocessed.size} won't be processed"
log_info " #{employees_hash.size} employees loaded"
start_time = Time.current
@allocation_end = @allocation_start.dup
@total_activities = @activity_deck.size
@total_processed = 0
log_info "Prioritization started at #{start_time.to_fs(:crm_default)} for #{@total_activities} activities"
activity_deck_original_size = @activity_deck.size
log_info 'Splitting into priority and standard list'
priority_list, standard_list = @activity_deck.values.partition { |a| a.activity_type_priority <= ActivityType::PRIORITY_MAX }
allocate_priority_activities priority_list, @allocation_end
allocate_standard_activities standard_list, @allocation_end
commit_assignment_matrix
end_time = Time.current
duration = (end_time - start_time).round(2)
throughput = (@total_activities / duration).round(2)
messages = []
require 'chronic_duration'
messages << "Prioritization ended at #{end_time.to_fs(:crm_default)}, duration: #{ChronicDuration.output(duration)}, #{throughput} activities / sec"
messages << "!!! some activities remains and could find no allocation, total count #{@activity_deck.size}" if @activity_deck.present?
res = Result.new(total_activities: @total_activities,
duration: duration,
throughput: throughput,
unprocessable: @activity_deck.size,
messages: messages)
res.messages.each { |m| log_info m }
end
res
end
|
#reps_available_for_activities_on_day(day) ⇒ Object
Returns an array of employee ids that have room for activities on a given day
187
188
189
|
# File 'app/services/activity/prioritizer.rb', line 187
def reps_available_for_activities_on_day(day)
employees_hash.values.map(&:id).select { |eid| can_take_activities_on_day?(eid, day) }
end
|
#reset_time_locks ⇒ Object
213
214
215
216
217
218
|
# File 'app/services/activity/prioritizer.rb', line 213
def reset_time_locks
return unless @reset_time_lock_aids.present?
log_info "Resetting time locks for #{@reset_time_lock_aids.size}"
Activity.where(id: @reset_time_lock_aids).each(&:unlock)
end
|
#sort_deck ⇒ Object
414
415
416
417
418
419
420
421
|
# File 'app/services/activity/prioritizer.rb', line 414
def sort_deck
log_info 'Sorting Deck'
@activity_deck = @activity_deck.sort_by do |aid, a|
[a.activity_type_priority, a.original_target_datetime, -a.sales_priority_index]
end.to_h
log_info 'Sorting Deck Complete'
end
|
#to_s ⇒ Object
28
29
30
|
# File 'app/services/activity/prioritizer.rb', line 28
def to_s
"Activity:Prioritizer: #{@allocation_end} << #{@activity_deck.size}"
end
|