Class: Activity::ChainRunner

Inherits:
Object
  • Object
show all
Defined in:
app/services/activity/chain_runner.rb

Overview

Service object: chain runner.

Defined Under Namespace

Classes: NoActivityResultError, NoActivityTypeError

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(activity, options = {}) ⇒ ChainRunner

Returns a new instance of ChainRunner.



21
22
23
24
25
26
27
28
29
30
31
# File 'app/services/activity/chain_runner.rb', line 21

def initialize(activity, options = {})
  @activity = activity
  @activity_type = activity.activity_type
  @activity_result_type = options[:new_activity_result_type] || activity.activity_result_type
  @logger = options[:logger] || Rails.logger

  raise NoActivityTypeError unless @activity_type
  raise NoActivityResultError unless @activity_result_type

  @chains_for_result = @activity_type.activity_chain_types.select { |act| act.activity_result_type_id == @activity_result_type.id }
end

Instance Attribute Details

#activityObject (readonly)

Returns the value of attribute activity.



10
11
12
# File 'app/services/activity/chain_runner.rb', line 10

def activity
  @activity
end

#activity_result_typeObject (readonly)

Returns the value of attribute activity_result_type.



10
11
12
# File 'app/services/activity/chain_runner.rb', line 10

def activity_result_type
  @activity_result_type
end

#activity_typeObject (readonly)

Returns the value of attribute activity_type.



10
11
12
# File 'app/services/activity/chain_runner.rb', line 10

def activity_type
  @activity_type
end

#chained_activitiesObject (readonly)

Returns the value of attribute chained_activities.



10
11
12
# File 'app/services/activity/chain_runner.rb', line 10

def chained_activities
  @chained_activities
end

#loggerObject (readonly)

Returns the value of attribute logger.



10
11
12
# File 'app/services/activity/chain_runner.rb', line 10

def logger
  @logger
end

Class Method Details

.initialize_and_execute_chains(activity, options = {}) ⇒ Object



12
13
14
15
16
17
18
19
# File 'app/services/activity/chain_runner.rb', line 12

def self.initialize_and_execute_chains(activity, options = {})
  chain_runner = new(activity, options)
  chain_runner.execute_chains
rescue NoActivityTypeError
  :no_activity_type
rescue NoActivityResultError
  :no_activity_result
end

Instance Method Details

#assign_resource_and_time(new_activity, offset_days = 0) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'app/services/activity/chain_runner.rb', line 120

def assign_resource_and_time(new_activity, offset_days = 0)
  new_activity.assigned_resource = new_activity.best_resource_employee

  if new_activity.assigned_resource.instance_of? Employee
    begin
      # next_working_day returns a Date; coerce to an app-zone datetime so the
      # :datetime attribute holds a TimeWithZone (a bare Date is passed through
      # uncast by AR and later blows up on #hour, and would round-trip as
      # midnight-UTC — i.e. the previous calendar day in Central).
      new_activity.target_datetime = new_activity.assigned_resource.next_working_day(Time.current, offset_days).in_time_zone
    rescue StandardError => e
      # Add validation error instead of using fallback date
      new_activity.errors.add(:assigned_resource, e.message)
      logger.error "Cannot assign activity to employee #{new_activity.assigned_resource.id}: #{e.message}"
      # Set a reasonable fallback for now, but the validation error will prevent saving
      new_activity.target_datetime = (Time.current + offset_days.days + 1.week).beginning_of_day
    end
  else
    new_activity.target_datetime = offset_days.days.from_now
  end
  new_activity
end

#chains_for_resultObject



33
34
35
# File 'app/services/activity/chain_runner.rb', line 33

def chains_for_result
  activity_type.activity_chain_types.select { |chain| chain.activity_result_type_id == @activity_result_type.id }
end

#chains_for_result_has_campaigns?Boolean

Returns:

  • (Boolean)


37
38
39
# File 'app/services/activity/chain_runner.rb', line 37

def chains_for_result_has_campaigns?
  chains_for_result.any?(&:campaign_id)
end

#chains_to_executeObject



41
42
43
44
45
46
47
48
49
# File 'app/services/activity/chain_runner.rb', line 41

def chains_to_execute
  # If our list of chains has campaigns specified, we focus results only on those with the same campaign specified
  # or nil campaign matching
  if chains_for_result_has_campaigns?
    @chains_for_result.select { |chain| chain.campaign_id == @activity.campaign_id }
  else # All results are valid which have no campaign
    @chains_for_result.select { |chain| chain.campaign_id.nil? }
  end
end

#execute_activity_chain(chain) ⇒ Object



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
105
# File 'app/services/activity/chain_runner.rb', line 64

def execute_activity_chain(chain)
  return unless chain.chain_activity_type

  if @activity.party.respond_to?(:assign_chained_activities?) && !@activity.party.assign_chained_activities?
    logger.warn "Could not run chained activity #{chain.chain_activity_type} triggered on activity id:#{@activity.id} because party assign_chained_activities returned false"
    return
  end
  # Chain activity
  new_activity = Activity.new activity_type: chain.chain_activity_type,
                              party: @activity.party,
                              resource: @activity.resource,
                              parent_activity_id: @activity.id,
                              campaign_id: @activity.campaign_id,
                              creator: @activity.closed_by || @activity.creator

  # If our activity chain is opportunity_promise we make some calculations
  if chain.opportunity_promise? && @activity.resource.respond_to?(:close_date)
    opp = @activity.resource
    # When is the opportunity promise date?
    target_date = opp.close_date || opp.planned_installation_date
    if target_date
      # What's the offset in days
      offset_days = (target_date.to_date - Date.current).to_i
      # Go 7 days earlier
      offset_days -= 7
      # Is it negative or zero, its probably garbage data or too close so lets' ignore it and let the
      # default offset take it over
      offset_days = nil if offset_days <= 0
    end
  end
  # The static offset is the default
  offset_days ||= chain.offset_days
  offset_days ||= 0

  assign_resource_and_time(new_activity, offset_days)

  if new_activity.save
    @chained_activities << new_activity
  else
    logger.error "Could not save chained activity #{new_activity.inspect} triggered on activity id:#{@activity.id}, errors: #{new_activity.errors_to_s}"
  end
end

#execute_chain(chain) ⇒ Object



59
60
61
62
# File 'app/services/activity/chain_runner.rb', line 59

def execute_chain(chain)
  execute_activity_chain chain
  execute_email_template chain
end

#execute_chainsObject



51
52
53
54
55
56
57
# File 'app/services/activity/chain_runner.rb', line 51

def execute_chains
  @chained_activities = []
  chains_to_execute.each do |chain|
    execute_chain(chain)
  end
  @activity.chained_activity_result = @chained_activities
end

#execute_email_template(chain) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
# File 'app/services/activity/chain_runner.rb', line 107

def execute_email_template(chain)
  return unless chain.email_template

  comm = CommunicationBuilder.new(sender_party: @activity.sender_party,
                                  resource: @activity.resource,
                                  recipient_party: @activity.party,
                                  template: chain.email_template,
                                  current_user: @activity.updater,
                                  transmit_at: chain.email_transmit_at_time,
                                  use_best_email: true).create
  Rails.logger.info "Chain Runner for chain #{chain.id} created communication #{comm.id}"
end