Class: Api::Partner::AttachmentSerializer

Inherits:
BaseSerializer show all
Includes:
ExportConstants
Defined in:
app/serializers/api/partner/attachment_serializer.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

Class Method Summary collapse

Methods inherited from BaseSerializer

enable_caching, mtime, record_cache_options

Class Method Details

.build_enhanced_status(base_status, row_info, percentage) ⇒ Object

Build enhanced status with detailed information



234
235
236
237
238
239
240
# File 'app/serializers/api/partner/attachment_serializer.rb', line 234

def build_enhanced_status(base_status, row_info, percentage)
  if row_info
    "#{base_status} (Generated #{row_info[:text]}) - #{percentage}%"
  else
    "#{base_status} - #{percentage}%"
  end
end

.build_error_information(object) ⇒ Object

Build error information for failed reservation exports



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'app/serializers/api/partner/attachment_serializer.rb', line 268

def build_error_information(object)
  error_data = {}

  # Get error information from Sidekiq status if available
  if object.job_id.present?
    sidekiq_data = Sidekiq::Status.get_all(object.job_id)
    error_data[:message] = sidekiq_data['message'] if sidekiq_data['message'].present?
    error_data[:category] = sidekiq_data['error_category'] if sidekiq_data['error_category'].present?
    error_data[:retry_count] = sidekiq_data['retry_count'].to_i if sidekiq_data['retry_count'].present?
  end

  # Add user-friendly error messages for reservation exports
  error_data[:user_message] =
    generate_reservation_export_error_message(error_data[:category], error_data[:message])
  error_data[:suggested_action] = generate_reservation_export_suggested_action(error_data[:category])

  error_data.presence
end

.build_timing_information(object) ⇒ Object

Build timing information for reservation exports



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'app/serializers/api/partner/attachment_serializer.rb', line 243

def build_timing_information(object)
  created_at = object.created_at&.in_time_zone('Asia/Bangkok')
  finished_at = object.finished_at&.in_time_zone('Asia/Bangkok')

  timing_info = {
    created_at: created_at&.strftime('%B %-d, %Y, %-I:%M %p'),
    finished_at: finished_at&.strftime('%B %-d, %Y, %-I:%M %p'),
    created_at_iso: created_at&.iso8601,
    finished_at_iso: finished_at&.iso8601,
  }

  if created_at && finished_at
    duration_seconds = finished_at - created_at
    timing_info[:duration] = format_duration(duration_seconds)
    timing_info[:duration_seconds] = duration_seconds.round(2)
  elsif created_at && object.status.to_s == 'on_progress'
    duration_seconds = Time.current - created_at
    timing_info[:current_duration] = format_duration(duration_seconds)
    timing_info[:current_duration_seconds] = duration_seconds.round(2)
  end

  timing_info
end

.determine_completion_state(object, sidekiq_data) ⇒ Object

Intelligently determine if export is actually completed



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'app/serializers/api/partner/attachment_serializer.rb', line 96

def determine_completion_state(object, sidekiq_data)
  # Primary indicators of completion
  has_download_link = object.download_link.present?
  has_finished_at = object.finished_at.present?

  # Secondary indicators from Sidekiq
  sidekiq_complete = sidekiq_data['pct_complete'].to_i >= 100
  sidekiq_status_done = sidekiq_data['status']&.downcase&.include?('complete') ||
    sidekiq_data['status']&.downcase&.include?('done')

  # Export is completed if it has both primary indicators OR strong secondary indicators
  (has_download_link && has_finished_at) ||
    (has_download_link && sidekiq_complete) ||
    (object.status.to_s == 'done')
end

.determine_phase_from_status(status) ⇒ Object

Determine phase from status for legacy support



344
345
346
347
348
349
350
351
352
353
354
355
# File 'app/serializers/api/partner/attachment_serializer.rb', line 344

def determine_phase_from_status(status)
  case status.to_s
  when 'on_queue'
    'initializing'
  when 'on_progress'
    'generating_file'
  when 'done'
    'completed'
  else
    'initializing'
  end
end

.enhance_reservation_progress(progress_data, object, sidekiq_data, is_actually_completed = false, status_info = {}) ⇒ Object

Enhance progress data for reservation exports (adds to existing structure)



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'app/serializers/api/partner/attachment_serializer.rb', line 122

def enhance_reservation_progress(progress_data, object, sidekiq_data, is_actually_completed = false, status_info = {})
  # Add enhanced features while keeping original keys
  progress_data[:enhanced_available] = true

  # Use synchronized status information
  progress_data[:phase] =
    status_info[:phase] || (is_actually_completed ? 'completed' : (sidekiq_data['phase'] || determine_phase_from_status(object.status)))

  # Add phase-specific information for reservation exports
  if progress_data[:phase].present?
    phase_info = get_reservation_phase_information(progress_data[:phase])
    progress_data[:phase_info] = phase_info if phase_info
  end

  # Add progress bar information with synchronized state
  progress_data[:progress_bar] = {
    percentage: status_info[:percentage],
    color: get_progress_color(status_info[:percentage], status_info[:raw_status]),
    animated: !is_actually_completed && status_info[:raw_status].to_s == 'on_progress',
  }

  # Add timing information
  if !is_actually_completed && status_info[:raw_status].to_s == 'on_progress' && object.created_at && status_info[:percentage].to_i > 0
    estimated_remaining = estimate_remaining_time(object, status_info[:percentage])
    if estimated_remaining
      progress_data[:estimated_remaining_seconds] = estimated_remaining.round(2)
      progress_data[:estimated_completion] = (Time.current + estimated_remaining).
        in_time_zone('Asia/Bangkok').strftime('%B %-d, %Y, %-I:%M %p')
    end
  elsif is_actually_completed
    # For completed exports, show completion time
    progress_data[:estimated_remaining_seconds] = 0.0
    if object.finished_at
      progress_data[:estimated_completion] = object.finished_at.
        in_time_zone('Asia/Bangkok').strftime('%B %-d, %Y, %-I:%M %p')
    end
  end

  progress_data
end

.estimate_remaining_time(object, percentage) ⇒ Object

Estimate remaining time based on current progress



394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'app/serializers/api/partner/attachment_serializer.rb', line 394

def estimate_remaining_time(object, percentage)
  return nil unless object.created_at

  # Ensure percentage is a number and greater than 0
  pct = percentage.to_i
  return nil unless pct > 0

  current_duration = Time.current - object.created_at
  total_estimated_time = (current_duration / pct) * 100
  remaining_time = total_estimated_time - current_duration

  [remaining_time, 0].max
end

.extract_row_information(message) ⇒ Object

Extract row information from progress messages



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'app/serializers/api/partner/attachment_serializer.rb', line 208

def extract_row_information(message)
  return nil if message.blank?

  begin
    # Look for patterns like "Generated 3800/15709 rows" or "Processing 1500/5000 reservations"
    if match = message.match(/(?:Generated|Processing|Processed)\s+(\d+)\/(\d+)\s+(?:rows?|reservations?)/i)
      {
        current: match[1].to_i,
        total: match[2].to_i,
        text: "#{match[1]}/#{match[2]} rows",
      }
    elsif match = message.match(/(\d+)\/(\d+)/)
      {
        current: match[1].to_i,
        total: match[2].to_i,
        text: "#{match[1]}/#{match[2]} items",
      }
    end
  rescue StandardError => e
    # Log error but don't break serialization
    HH_LOGGER.warn("Error extracting row information from message '#{message}': #{e.message}")
    nil
  end
end

.format_duration(seconds) ⇒ Object

Format duration in human-readable format



378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'app/serializers/api/partner/attachment_serializer.rb', line 378

def format_duration(seconds)
  return '0 seconds' if seconds <= 0

  hours = seconds / 3600
  minutes = (seconds % 3600) / 60
  secs = seconds % 60

  parts = []
  parts << "#{hours.to_i}h" if hours >= 1
  parts << "#{minutes.to_i}m" if minutes >= 1
  parts << "#{secs.round}s" if secs >= 1 || parts.empty?

  parts.join(' ')
end

.generate_reservation_export_error_message(category, technical_message) ⇒ Object

Generate user-friendly error message for reservation exports



409
410
411
412
413
414
415
416
417
418
419
420
421
422
# File 'app/serializers/api/partner/attachment_serializer.rb', line 409

def generate_reservation_export_error_message(category, technical_message)
  case category&.to_s
  when 'data_collection'
    'There was an issue collecting the reservation data. This might be due to a temporary database issue.'
  when 'file_generation'
    'There was an issue generating the reservation export file. The data might be too large or contain invalid characters.'
  when 'file_upload'
    'There was an issue uploading the reservation export file. This might be due to a temporary storage issue.'
  when 'email_sending'
    'There was an issue sending the reservation export notification email. Please check your email address and try again.'
  else
    technical_message || 'An unexpected error occurred during the reservation export process.'
  end
end

.generate_reservation_export_suggested_action(category) ⇒ Object

Generate suggested action for reservation export error recovery



425
426
427
428
429
430
431
432
433
434
435
436
437
438
# File 'app/serializers/api/partner/attachment_serializer.rb', line 425

def generate_reservation_export_suggested_action(category)
  case category&.to_s
  when 'data_collection'
    'Please try again in a few minutes. If the issue persists, try reducing the date range for your reservation export.'
  when 'file_generation'
    'Please try reducing the date range for your reservation export or contact support if the issue persists.'
  when 'file_upload'
    'Please try again in a few minutes. The reservation export file should be available for download once processing completes.'
  when 'email_sending'
    'Please check your email address and try the reservation export again.'
  else
    'Please try again in a few minutes. If the issue persists, contact support for assistance with your reservation export.'
  end
end

.get_completed_status_info(_object, sidekiq_data) ⇒ Object

Get status info for completed exports



164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'app/serializers/api/partner/attachment_serializer.rb', line 164

def get_completed_status_info(_object, sidekiq_data)
  # Extract row information from message if available
  message = sidekiq_data['message'] || 'Export completed successfully'
  row_info = extract_row_information(message)

  {
    percentage: 100,
    raw_status: 'done',
    enhanced_status: build_enhanced_status('Completed', row_info, 100),
    email_status: sidekiq_data['email_status'] || 'sent',
    message: message,
    phase: 'completed',
  }
end

.get_default_message_for_status(status) ⇒ Object

Get default message based on status for reservation exports



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'app/serializers/api/partner/attachment_serializer.rb', line 310

def get_default_message_for_status(status)
  case status.to_s
  when 'on_queue'
    'Reservation export queued for processing...'
  when 'on_progress'
    'Processing reservation export...'
  when 'done'
    'Reservation export completed successfully'
  when 'failed'
    'Reservation export failed'
  when 'retrying'
    'Retrying reservation export...'
  else
    'Processing reservation export...'
  end
end

.get_default_percentage_for_status(status) ⇒ Object

Get default percentage based on status



328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'app/serializers/api/partner/attachment_serializer.rb', line 328

def get_default_percentage_for_status(status)
  case status.to_s
  when 'on_queue'
    0
  when 'on_progress'
    50
  when 'done'
    100
  when 'failed', 'retrying'
    0
  else
    0
  end
end

.get_in_progress_status_info(object, sidekiq_data) ⇒ Object

Get status info for in-progress exports



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'app/serializers/api/partner/attachment_serializer.rb', line 180

def get_in_progress_status_info(object, sidekiq_data)
  percentage = sidekiq_data['pct_complete']&.to_i || get_default_percentage_for_status(object.status)
  message = sidekiq_data['message'] || get_default_message_for_status(object.status)
  row_info = extract_row_information(message)

  # Determine humanized status based on object status
  humanized_status = case object.status.to_s
                     when 'on_progress', 'in_progress'
                       'On progress'
                     when 'on_queue'
                       'Queued'
                     when 'failed'
                       'Failed'
                     else
                       object.status.to_s.humanize
                     end

  {
    percentage: percentage,
    raw_status: object.status.to_s,
    enhanced_status: build_enhanced_status(humanized_status, row_info, percentage),
    email_status: sidekiq_data['email_status'] || 'pending',
    message: message,
    phase: sidekiq_data['phase'] || determine_phase_from_status(object.status),
  }
end

.get_progress_color(percentage, status) ⇒ Object

Get progress bar color based on percentage and status



358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# File 'app/serializers/api/partner/attachment_serializer.rb', line 358

def get_progress_color(percentage, status)
  return 'danger' if status.to_s.in?(['failed'])
  return 'warning' if status.to_s.in?(['retrying'])
  return 'success' if status.to_s == 'done'

  case percentage
  when 0..25
    'info'
  when 26..75
    'primary'
  when 76..99
    'warning'
  when 100
    'success'
  else
    'secondary'
  end
end

.get_reservation_phase_information(phase) ⇒ Object

Get phase information for reservation exports



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'app/serializers/api/partner/attachment_serializer.rb', line 288

def get_reservation_phase_information(phase)
  return nil if phase.blank?

  phase_sym = phase.to_sym

  # Use the new constants structure from ExportConstants
  phase_range = ExportConstants::PROGRESS_PHASES[phase_sym]
  phase_message = ExportConstants::PROGRESS_MESSAGES[phase_sym]

  return nil unless phase_range && phase_message

  {
    name: phase.humanize,
    description: phase_message,
    range: {
      min: phase_range.min,
      max: phase_range.max,
    },
  }
end

.get_synchronized_status(object, sidekiq_data, is_actually_completed) ⇒ Object

Get synchronized status information for consistent display



113
114
115
116
117
118
119
# File 'app/serializers/api/partner/attachment_serializer.rb', line 113

def get_synchronized_status(object, sidekiq_data, is_actually_completed)
  if is_actually_completed
    get_completed_status_info(object, sidekiq_data)
  else
    get_in_progress_status_info(object, sidekiq_data)
  end
end

.is_reservation_export?(object) ⇒ Boolean

Check if this is a reservation export (booking report)

Returns:

  • (Boolean)


91
92
93
# File 'app/serializers/api/partner/attachment_serializer.rb', line 91

def is_reservation_export?(object)
  object.report_type.to_s == 'booking'
end