Class: Maintenance::OpportunityMaintenance
- Inherits:
-
BaseService
- Object
- BaseService
- Maintenance::OpportunityMaintenance
- Defined in:
- app/services/maintenance/opportunity_maintenance.rb
Overview
Service object: opportunity maintenance.
Instance Attribute Summary
Attributes inherited from BaseService
Instance Method Summary collapse
- #cancelling_orphaned_follow_ups ⇒ Object
-
#correct_misattributed_sources(dry_run: true, since: Date.new(2024, 1, 1)) ⇒ Hash
Corrects opportunities where source was manually overridden despite having a visit with Google Ads attribution.
- #delete_empty_opps ⇒ Object
-
#narrow_google_sources(dry_run: true, since: 1.year.ago.to_date) ⇒ Hash
Upgrades sources from the ambiguous "Google" (4003) parent to the specific "Google Ads" (1155) child on customers and opportunities where a gclid-bearing visit provides definitive evidence of paid traffic.
- #process ⇒ Object
- #sync_opportunity_states ⇒ Object
- #sync_reps_on_open_opps ⇒ Object
Methods inherited from BaseService
#initialize, #log_debug, #log_error, #log_info, #log_warning, #logger, #tagged_logger
Constructor Details
This class inherits a constructor from BaseService
Instance Method Details
#cancelling_orphaned_follow_ups ⇒ Object
175 176 177 178 179 180 181 182 183 184 |
# File 'app/services/maintenance/opportunity_maintenance.rb', line 175 def cancelling_orphaned_follow_ups # Catch any loose ends. won_opp_with_quofus = Opportunity.won.joins(:activities).merge(Activity.quofu.open_activities) @logger.info "Closing Quofus on #{won_opp_with_quofus.size} won opportunities" won_opp_with_quofus.find_each do |o| @logger.info "Closing Quofus on opportunity id #{o.id}" o.cancel_followups(include_large_op_activities: true) end end |
#correct_misattributed_sources(dry_run: true, since: Date.new(2024, 1, 1)) ⇒ Hash
Corrects opportunities where source was manually overridden despite having
a visit with Google Ads attribution. This restores proper attribution
for reporting and offline conversion tracking.
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'app/services/maintenance/opportunity_maintenance.rb', line 21 def correct_misattributed_sources(dry_run: true, since: Date.new(2024, 1, 1)) @logger.info "Correcting misattributed sources (dry_run: #{dry_run}, since: #{since})" misattributed = Opportunity .joins(:visit) .merge(Visit.with_any_marketing_key(Visit::GOOGLE_ADS_MARKETING_KEYS)) .where.not(visits: { source_id: nil }) .where(Opportunity.arel_table[:source_id].is_distinct_from(Visit.arel_table[:source_id])) .where(opportunities: { created_at: since.. }) .includes(:visit, :source) total = misattributed.count corrected = 0 errors = [] @logger.info "Found #{total} opportunities with misattributed sources" misattributed.find_each do |opp| current_source = opp.source&.name || 'nil' correct_source = opp.visit.source&.name || 'nil' @logger.info " #{opp.reference_number}: '#{current_source}' → '#{correct_source}'" next if dry_run opp.update_column(:source_id, opp.visit.source_id) corrected += 1 rescue StandardError => e @logger.error " Error correcting #{opp.reference_number}: #{e.}" errors << { reference_number: opp.reference_number, error: e. } end summary = { total_found: total, corrected: corrected, dry_run: dry_run, errors: errors } @logger.info "Completed: #{summary.to_json}" summary end |
#delete_empty_opps ⇒ Object
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 |
# File 'app/services/maintenance/opportunity_maintenance.rb', line 186 def delete_empty_opps @logger.info 'Deleting empty opportunities' counter = 0 empty_opps = Opportunity .where.missing(:quotes, :orders, :activities, :room_configurations) .where(created_at: ...7.days.ago) empty_opps.find_each do |opportunity| @logger.info "Deleting empty opportunity #{opportunity.id} #{opportunity.name}" opportunity.destroy! counter += 1 rescue StandardError => e @logger.error "Problem deleting opportunity id #{opportunity.id}, #{e}" end @logger.info "Completed, deleted: #{counter}" end |
#narrow_google_sources(dry_run: true, since: 1.year.ago.to_date) ⇒ Hash
Upgrades sources from the ambiguous "Google" (4003) parent to the specific
"Google Ads" (1155) child on customers and opportunities where a gclid-bearing
visit provides definitive evidence of paid traffic.
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'app/services/maintenance/opportunity_maintenance.rb', line 71 def narrow_google_sources(dry_run: true, since: 1.year.ago.to_date) google_parent = Source.find_by(id: 4003) # "Google" google_ads = Source.google_ads # "Google Ads" (1155) unless google_parent && google_ads @logger.error 'Could not find Google (4003) or Google Ads source' return { error: 'missing sources' } end customer_corrections = narrow_customer_sources(google_parent, google_ads, dry_run:, since:) opp_corrections = narrow_opportunity_sources(google_parent, dry_run:, since:) summary = { dry_run: dry_run, customers: customer_corrections, opportunities: opp_corrections } @logger.info "narrow_google_sources completed: #{summary.to_json}" summary end |
#process ⇒ Object
5 6 7 8 9 10 11 12 |
# File 'app/services/maintenance/opportunity_maintenance.rb', line 5 def process PaperTrail.request(whodunnit: 'Maintenance::OpportunityMaintenance') do sync_opportunity_states cancelling_orphaned_follow_ups delete_empty_opps sync_reps_on_open_opps end end |
#sync_opportunity_states ⇒ Object
162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'app/services/maintenance/opportunity_maintenance.rb', line 162 def sync_opportunity_states @logger.info 'Syncing opportunity states' opps = Opportunity.where(state: %w[interest qualify follow_up]) counter = 0 opps.find_each do |opp| @logger.info " Synching state for opportunity #{opp.id} currently #{opp.state}" opp.sync_state counter += 1 @logger.info " New state: #{opp.state}" end @logger.info "Completed, updated #{counter} opportunities" end |
#sync_reps_on_open_opps ⇒ Object
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'app/services/maintenance/opportunity_maintenance.rb', line 202 def sync_reps_on_open_opps opp_t = Opportunity.arel_table party_t = Party.arel_table rep_mismatch_conditions = [ Opportunity.where(opp_t[:primary_sales_rep_id].is_distinct_from(party_t[:primary_sales_rep_id])), Opportunity.where(opp_t[:secondary_sales_rep_id].is_distinct_from(party_t[:secondary_sales_rep_id])), Opportunity.where(opp_t[:local_sales_rep_id].is_distinct_from(party_t[:local_sales_rep_id])).where.not(parties: { local_sales_rep_id: nil }) ] misaligned_opps = Opportunity.where.not(state: %w[won lost]).joins(:customer).where.any_of(*rep_mismatch_conditions) total_opps_misaligned = misaligned_opps.size @logger.info "Found #{total_opps_misaligned} opportunities with misaligned reps" misaligned_opps.find_each do |opp| changes = opp.synchronize_reps @logger.debug("Synced reps on opportunity", opportunity_id: opp.id, changed: changes.present?) opp.save! rescue StandardError => e ErrorReporting.error e logger.error e end @logger.info "Completed syncing reps on #{total_opps_misaligned} opportunities" total_opps_misaligned end |