Class: Activity::ChainRunner
- Inherits:
-
Object
- Object
- Activity::ChainRunner
- Defined in:
- app/services/activity/chain_runner.rb
Overview
Service object: chain runner.
Defined Under Namespace
Classes: NoActivityResultError, NoActivityTypeError
Instance Attribute Summary collapse
-
#activity ⇒ Object
readonly
Returns the value of attribute activity.
-
#activity_result_type ⇒ Object
readonly
Returns the value of attribute activity_result_type.
-
#activity_type ⇒ Object
readonly
Returns the value of attribute activity_type.
-
#chained_activities ⇒ Object
readonly
Returns the value of attribute chained_activities.
-
#logger ⇒ Object
readonly
Returns the value of attribute logger.
Class Method Summary collapse
-
.initialize_and_execute_chains(activity, options = {}) ⇒ Symbol?
:no_activity_typeor:no_activity_resulton failure.
Instance Method Summary collapse
-
#assign_resource_and_time(new_activity, offset_days = 0) ⇒ Activity
Assigns the best resource employee and computes the target datetime, falling back to a week out (with a validation error) when the employee's schedule can't produce one.
-
#chains_for_result ⇒ Array<ActivityChainType>
Chain types configured for the activity's current result type.
-
#chains_for_result_has_campaigns? ⇒ Boolean
Whether any chain for the current result is campaign-scoped.
-
#chains_to_execute ⇒ Array<ActivityChainType>
Chains applicable to this activity, filtered by campaign when the result's chains are campaign-scoped.
-
#execute_activity_chain(chain) ⇒ void
Creates the chained follow-up activity for a chain, computing the target date from opportunity promise dates or the chain's day offset.
-
#execute_chain(chain) ⇒ void
Executes one chain: creates the chained activity and sends its email template.
-
#execute_chains ⇒ void
Runs every applicable chain and records the created activities on
activity.chained_activity_result. -
#execute_email_template(chain) ⇒ void
Sends the chain's email template as a communication to the activity's party.
- #initialize(activity, options = {}) ⇒ void constructor
Constructor Details
#initialize(activity, options = {}) ⇒ void
33 34 35 36 37 38 39 40 41 42 43 |
# File 'app/services/activity/chain_runner.rb', line 33 def initialize(activity, = {}) @activity = activity @activity_type = activity.activity_type @activity_result_type = [:new_activity_result_type] || activity.activity_result_type @logger = [: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
#activity ⇒ Object (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_type ⇒ Object (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_type ⇒ Object (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_activities ⇒ Object (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 |
#logger ⇒ Object (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 = {}) ⇒ Symbol?
Returns :no_activity_type or :no_activity_result on failure.
17 18 19 20 21 22 23 24 |
# File 'app/services/activity/chain_runner.rb', line 17 def self.initialize_and_execute_chains(activity, = {}) chain_runner = new(activity, ) 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) ⇒ Activity
Assigns the best resource employee and computes the target datetime,
falling back to a week out (with a validation error) when the employee's
schedule can't produce one.
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 |
# File 'app/services/activity/chain_runner.rb', line 158 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.) logger.error "Cannot assign activity to employee #{new_activity.assigned_resource.id}: #{e.}" # 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_result ⇒ Array<ActivityChainType>
Chain types configured for the activity's current result type.
47 48 49 |
# File 'app/services/activity/chain_runner.rb', line 47 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
Whether any chain for the current result is campaign-scoped.
53 54 55 |
# File 'app/services/activity/chain_runner.rb', line 53 def chains_for_result_has_campaigns? chains_for_result.any?(&:campaign_id) end |
#chains_to_execute ⇒ Array<ActivityChainType>
Chains applicable to this activity, filtered by campaign when the
result's chains are campaign-scoped.
60 61 62 63 64 65 66 67 68 |
# File 'app/services/activity/chain_runner.rb', line 60 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) ⇒ void
This method returns an undefined value.
Creates the chained follow-up activity for a chain, computing the
target date from opportunity promise dates or the chain's day offset.
93 94 95 96 97 98 99 100 101 102 103 104 105 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 |
# File 'app/services/activity/chain_runner.rb', line 93 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) ⇒ void
This method returns an undefined value.
Executes one chain: creates the chained activity and sends its email template.
84 85 86 87 |
# File 'app/services/activity/chain_runner.rb', line 84 def execute_chain(chain) execute_activity_chain chain execute_email_template chain end |
#execute_chains ⇒ void
This method returns an undefined value.
Runs every applicable chain and records the created activities on
activity.chained_activity_result.
73 74 75 76 77 78 79 |
# File 'app/services/activity/chain_runner.rb', line 73 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) ⇒ void
This method returns an undefined value.
Sends the chain's email template as a communication to the activity's party.
139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'app/services/activity/chain_runner.rb', line 139 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 |