Class: PartnerService::Reservations::Exports::TrackingService

Inherits:
ApplicationService show all
Includes:
ElasticAPM::SpanHelpers, ExportConstants
Defined in:
app/services/partner_service/reservations/exports/tracking_service.rb

Constant Summary

Constants included from ExportConstants

ExportConstants::CHANNEL_IDS_TO_SKIP, ExportConstants::DEFAULT_QUEUE, ExportConstants::EMAIL_BATCH_SIZE, ExportConstants::ERROR_CATEGORIES, ExportConstants::EXCEL_FILE_EXTENSION, ExportConstants::LARGE_DATASET_WARNING_THRESHOLD, ExportConstants::LONG_PROCESS_DAYS_THRESHOLD, ExportConstants::LONG_PROCESS_QUEUE, ExportConstants::MAX_RETRIES, ExportConstants::PDF_FILE_EXTENSION, ExportConstants::PROGRESS_MESSAGES, ExportConstants::PROGRESS_PHASES, ExportConstants::PROGRESS_UPDATE_BATCH_SIZE, ExportConstants::RESERVATION_BATCH_SIZE, ExportConstants::RETRY_BASE_INTERVAL, ExportConstants::RETRY_MAX_ELAPSED_TIME, ExportConstants::ZERO_COMMISSION_CHANNEL_ID

Instance Attribute Summary collapse

Attributes inherited from ApplicationService

#object

Instance Method Summary collapse

Methods inherited from ApplicationService

#execute, #execute!

Constructor Details

#initialize(attachment, job_id = nil) ⇒ TrackingService

Initialize the export tracking service

Parameters:

  • attachment (Attachment)

    The attachment record to track

  • job_id (String) (defaults to: nil)

    Optional Sidekiq job ID for status tracking



32
33
34
35
36
37
38
39
40
# File 'app/services/partner_service/reservations/exports/tracking_service.rb', line 32

def initialize(attachment, job_id = nil)
  @attachment = attachment
  @job_id = job_id || attachment&.job_id
  @start_time = Time.current
  @current_phase = :initializing
  @error_info = {}

  validate_attachment!
end

Instance Attribute Details

#attachmentObject (readonly)

Constants are now included from ExportConstants module



26
27
28
# File 'app/services/partner_service/reservations/exports/tracking_service.rb', line 26

def attachment
  @attachment
end

#current_phaseObject (readonly)

Constants are now included from ExportConstants module



26
27
28
# File 'app/services/partner_service/reservations/exports/tracking_service.rb', line 26

def current_phase
  @current_phase
end

#error_infoObject (readonly)

Constants are now included from ExportConstants module



26
27
28
# File 'app/services/partner_service/reservations/exports/tracking_service.rb', line 26

def error_info
  @error_info
end

#job_idObject (readonly)

Constants are now included from ExportConstants module



26
27
28
# File 'app/services/partner_service/reservations/exports/tracking_service.rb', line 26

def job_id
  @job_id
end

#start_timeObject (readonly)

Constants are now included from ExportConstants module



26
27
28
# File 'app/services/partner_service/reservations/exports/tracking_service.rb', line 26

def start_time
  @start_time
end

Instance Method Details

#complete_export(download_url, additional_data = {}) ⇒ 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
# File 'app/services/partner_service/reservations/exports/tracking_service.rb', line 106

def complete_export(download_url, additional_data = {})
  update_progress(:completed, 100, 'Export completed successfully', additional_data)

  # Ensure finished_at is properly set
  current_time = Time.current
  update_data = {
    status: :done,
    finished_at: current_time,
    download_link: format_download_link(download_url),
  }

  # Update attachment with explicit error handling
  begin
    attachment.update!(update_data)
  rescue StandardError => e
    HH_LOGGER.error("Failed to update attachment #{attachment.id}: #{e.message}")
    # Try to update without download_link in case that's causing issues
    attachment.update!(status: :done, finished_at: current_time)
  end

  if job_id.present?
    store_sidekiq_status(100, 'Export completed successfully', {
                           status: Attachment::DONE_STATUS,
                           email_status: Attachment::SENT_EMAIL_STATUS,
                         })
  end

  log_export_event('export_completed', {
    duration_seconds: (Time.current - start_time).round(2),
    download_url: download_url,
  }.merge(additional_data))

  true
rescue StandardError => e
  handle_error(:system_error, 'Failed to complete export', e)
  false
end

#completed?Boolean

Check if export has completed successfully

Returns:

  • (Boolean)

    true if export completed successfully



223
224
225
226
227
# File 'app/services/partner_service/reservations/exports/tracking_service.rb', line 223

def completed?
  # Reload attachment to get latest status from database
  attachment.reload
  attachment.status.to_s == 'done'
end

#current_statusHash

Get current export status information

Returns:

  • (Hash)

    Current status with progress and phase information



202
203
204
205
206
207
208
209
210
211
# File 'app/services/partner_service/reservations/exports/tracking_service.rb', line 202

def current_status
  {
    phase: current_phase,
    percentage: get_current_percentage,
    message: get_current_message,
    status: attachment.status,
    error_info: error_info,
    duration_seconds: (Time.current - start_time).round(2),
  }
end

#estimated_time_remainingInteger

Get estimated time remaining based on current progress

Returns:

  • (Integer)

    Estimated seconds remaining (nil if cannot estimate)



239
240
241
242
243
244
245
246
247
248
249
250
# File 'app/services/partner_service/reservations/exports/tracking_service.rb', line 239

def estimated_time_remaining
  return nil unless in_progress?

  current_percentage = get_current_percentage
  return nil if current_percentage <= 0

  elapsed_time = Time.current - start_time
  total_estimated_time = (elapsed_time / current_percentage) * 100
  remaining_time = total_estimated_time - elapsed_time

  [remaining_time.round, 0].max
end

#failed?Boolean

Check if export has failed

Returns:

  • (Boolean)

    true if export has failed



232
233
234
# File 'app/services/partner_service/reservations/exports/tracking_service.rb', line 232

def failed?
  attachment.status.to_s == 'failed'
end

#handle_error(category, message, exception = nil, retry_count = 0) ⇒ Object



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
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'app/services/partner_service/reservations/exports/tracking_service.rb', line 151

def handle_error(category, message, exception = nil, retry_count = 0)
  @error_info = {
    category: category,
    message: message,
    exception: exception,
    retry_count: retry_count,
    occurred_at: Time.current,
    phase: current_phase,
  }

  # Update attachment status
  status = retry_count > 0 ? :retrying : :failed
  update_attachment_status(status)

  # Store error in Sidekiq status
  if job_id.present?
    error_status = retry_count > 0 ? Attachment::RETRYING_STATUS : Attachment::FAILED_STATUS
    store_sidekiq_status(nil, format_error_message(category, message), {
                           status: error_status,
                           error_category: category.to_s,
                           retry_count: retry_count,
                         })
  end

  # Log error with context
  log_export_event('export_error', {
                     error_category: category,
                     error_message: message,
                     exception_class: exception&.class&.name,
                     exception_message: exception&.message,
                     retry_count: retry_count,
                     phase: current_phase,
                   })

  # Report to APM with full context
  APMErrorHandler.report("Export #{category} error: #{message}", {
                           attachment_id: attachment.id,
                           restaurant_id: attachment.restaurant_id,
                           report_type: attachment.report_type,
                           error_category: category,
                           phase: current_phase,
                           retry_count: retry_count,
                           exception: exception,
                         })

  false
end

#in_progress?Boolean

Check if export is in progress

Returns:

  • (Boolean)

    true if export is currently running



216
217
218
# File 'app/services/partner_service/reservations/exports/tracking_service.rb', line 216

def in_progress?
  attachment.status.to_s == 'on_progress' && current_phase != :completed
end

#start_exportBoolean

Start the export process and set initial status

Returns:

  • (Boolean)

    true if started successfully



45
# File 'app/services/partner_service/reservations/exports/tracking_service.rb', line 45

span_method :start_export

#update_progress(phase, percentage, message = nil, additional_data = {}) ⇒ Object



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
# File 'app/services/partner_service/reservations/exports/tracking_service.rb', line 69

def update_progress(phase, percentage, message = nil, additional_data = {})
  validate_phase!(phase)
  validate_percentage!(percentage, phase)

  @current_phase = phase
  progress_message = message || ExportConstants::PROGRESS_MESSAGES[phase]

  # Update Sidekiq status if job_id is available
  if job_id.present?
    store_sidekiq_status(percentage, progress_message, additional_data)
  end

  # Update attachment with progress info
  update_attachment_progress(phase, percentage, progress_message, additional_data)

  log_export_event('progress_updated', {
    phase: phase,
    percentage: percentage,
    message: progress_message,
  }.merge(additional_data))

  true
rescue StandardError => error
  APMErrorHandler.report('Export progress update failed', {
                           attachment_id: attachment.id,
                           phase: phase,
                           percentage: percentage,
                           error: error,
                         })
  false
end