Class: ProgressTrackableReportPdf

Inherits:
Prawn::Document
  • Object
show all
Includes:
MoneyRails::ActionViewExtension, ReservationExportHelpers
Defined in:
app/my_lib/progress_trackable_report_pdf.rb

Overview

Progress-trackable version of ReportPdf that provides granular progress updates during PDF generation for better user experience in booking export workers.

This class maintains full compatibility with the existing ReportPdf output format while adding progress callback functionality for real-time status updates.

Constant Summary collapse

TABLE_COLUMN_WIDTHS =
[
  50, # ID
  73, # Restaurant-Name
  50, # Customer-Name
  50, # Dining-Date
  45, # Dining-Time
  30, # Adult
  30, # Kids
  30, # Party-Size
  45, # Status
  50, # Special-Request
  60, # Package-Type
  60, # Package-Price
  50, # Restaurant-Revenue
  40, # Commision
  50, # Voucher-Code
  50, # Voucher-Amount
].freeze
TABLE_COLUMN_WIDTHS_F2 =
[
  50, # ID
  70, # Created-At
  60, # Service-Type
  50, # Distance
  40, # Pre-Payment
  60, # Payment-Type
  60, # Paid-At-Date
  60, # Paid-At-Time
  50, # Payment-Gateway
  50, # Payment-Gateway-Account
  70, # Old-Reservation-ID
].freeze

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

Instance Method Summary collapse

Methods included from ReservationExportHelpers

#calc_prepayment, #calculate_commission, #calculate_restaurant_revenue, #calculate_total_package_price, #date_format, #decimal_format, #format_add_on_names_with_quantity, #format_addon_names_for_report, #format_money, #format_package_names, #format_package_names_for_report, #format_package_names_with_quantity, #format_voucher_amount, #format_voucher_code, #get_reservation_distance, #paid_amount, #restaurant_delivery_fee, #sanitize_package_name, #sanitizer, #valid_email?, #voucher_amount, #voucher_code

Constructor Details

#initialize(reservations = nil, args = {}) ⇒ ProgressTrackableReportPdf

Returns a new instance of ProgressTrackableReportPdf.



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'app/my_lib/progress_trackable_report_pdf.rb', line 49

def initialize(reservations = nil, args = {})
  @reservations = reservations
  @progress_callback = args.delete(:progress_callback) # Extract progress callback

  if args.present?
    args.to_h.with_indifferent_access.each do |k, v|
      send("#{k}=".to_sym, v)
    end
  end

  super(top_margin: 10, page_size: 'A4', page_layout: :landscape)
  font_families.update('Mitr' => {
                         normal: Rails.root.join('vendor/assets/fonts/mitr/Mitr-Regular.ttf'),
                         bold: Rails.root.join('vendor/assets/fonts/mitr/Mitr-Bold.ttf'),
                       })

  font 'Mitr'
  @pdf_url = {}
end

Instance Attribute Details

#date_filter_typeObject

Returns the value of attribute date_filter_type.



46
47
48
# File 'app/my_lib/progress_trackable_report_pdf.rb', line 46

def date_filter_type
  @date_filter_type
end

#end_dateObject

Returns the value of attribute end_date.



46
47
48
# File 'app/my_lib/progress_trackable_report_pdf.rb', line 46

def end_date
  @end_date
end

#pdf_urlObject

Returns the value of attribute pdf_url.



46
47
48
# File 'app/my_lib/progress_trackable_report_pdf.rb', line 46

def pdf_url
  @pdf_url
end

#progress_callbackObject

Returns the value of attribute progress_callback.



46
47
48
# File 'app/my_lib/progress_trackable_report_pdf.rb', line 46

def progress_callback
  @progress_callback
end

#reservationsObject

Returns the value of attribute reservations.



46
47
48
# File 'app/my_lib/progress_trackable_report_pdf.rb', line 46

def reservations
  @reservations
end

#restaurant_group_idObject

Returns the value of attribute restaurant_group_id.



46
47
48
# File 'app/my_lib/progress_trackable_report_pdf.rb', line 46

def restaurant_group_id
  @restaurant_group_id
end

#restaurant_idObject

Returns the value of attribute restaurant_id.



46
47
48
# File 'app/my_lib/progress_trackable_report_pdf.rb', line 46

def restaurant_id
  @restaurant_id
end

#staff_idObject

Returns the value of attribute staff_id.



46
47
48
# File 'app/my_lib/progress_trackable_report_pdf.rb', line 46

def staff_id
  @staff_id
end

#start_dateObject

Returns the value of attribute start_date.



46
47
48
# File 'app/my_lib/progress_trackable_report_pdf.rb', line 46

def start_date
  @start_date
end

Instance Method Details



336
337
338
339
340
341
342
343
344
# File 'app/my_lib/progress_trackable_report_pdf.rb', line 336

def download_link
  link = attachment.pdf_url
  link = "#{Figaro.env.HH_HOST_URL!}#{link}" unless link.include? 'http'

  return link unless Figaro.bool_env! :ENABLE_CDN

  link.gsub(Figaro.env.CDN_URL, Figaro.env.HH_HOST_URL)
  link
end

#filepathObject



332
333
334
# File 'app/my_lib/progress_trackable_report_pdf.rb', line 332

def filepath
  Rails.root.join('tmp', 'reports', file_name).to_s
end

Generate download link by creating a temporary attachment for file storage



305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'app/my_lib/progress_trackable_report_pdf.rb', line 305

def generate_download_link
  # Upload PDF file to storage via temporary attachment
  temp_attachment = Attachment.new(
    pdf: open(filepath),
    name: "PDF_TEMP_#{file_name}_#{Time.current.to_i}",
    report_type: :booking,
    exported_by: :owner_staff,
    status: :done, # Mark as done so it doesn't appear in active exports
  )
  temp_attachment.save!

  # Use CarrierWave's built-in URL generation which handles CDN/host logic automatically
  temp_attachment.pdf.url
end

#generate_pdf(reservations, args = {}) ⇒ Object

Enhanced generate_pdf method with progress tracking



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
106
# File 'app/my_lib/progress_trackable_report_pdf.rb', line 70

def generate_pdf(reservations, args = {})
  total_files = 2

  update_progress(0, "Starting PDF generation for #{total_files} files...")

  [1, 2].each_with_index do |number, index|
    file_progress_start = (index * 50) # Each file takes 50% of total progress

    update_progress(file_progress_start, "Generating PDF file #{number} of #{total_files}...")

    args[:file_number] = number
    args[:file_name] = "Hungry-Hub-report-#{Time.zone.today}-#{restaurant_id}#{Time.current_time.to_i}-#{number}.pdf"
    args[:progress_callback] = create_file_progress_callback(file_progress_start, 50)

    file_pdf = self.class.new(reservations, args)
    file_pdf.generate_pdf_table

    update_progress(file_progress_start + 35, "Rendering PDF file #{number}...")
    file_pdf.render_file(file_pdf.filepath)

    update_progress(file_progress_start + 40, "Waiting for PDF file #{number} to be written...")
    path = Rails.root.join(file_pdf.filepath)
    loop do
      break if path.exist?

      sleep 1
    end

    update_progress(file_progress_start + 45, "Generating download link for PDF file #{number}...")
    # Generate download link without creating separate attachment records
    @pdf_url["file_#{number}".to_sym] = file_pdf.generate_download_link

    update_progress(file_progress_start + 50, "PDF file #{number} completed")
  end

  update_progress(100, 'All PDF files generated successfully')
end

#generate_pdf_tableObject

Generate PDF table (copied from original ReportPdf with progress tracking)



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
143
144
145
146
147
148
149
150
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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'app/my_lib/progress_trackable_report_pdf.rb', line 109

def generate_pdf_table
  update_progress(5, 'Initializing PDF table generation...')

  I18n.locale = :en
  data = []

  update_progress(10, 'Setting up PDF header and banner...')
  image_url = begin
    AdminSetting.report_pdf_image_url.present? && open(AdminSetting.report_pdf_image_url)
  rescue StandardError
    nil
  end
  banner = [[{ image: image_url, image_height: 110, image_width: 530, position: :center }]] if image_url.present?

  label = []
  if report_restaurant?
    label += [['Restaurant Name', restaurant.name]]
  elsif report_restaurant_group?
    label += [['Group Name', restaurant_group.name]]
  elsif report_staff_restaurants?
    group_name = if staff.default_restaurant.restaurant_group.present?
                   staff.default_restaurant.restaurant_group.name
                 else
                   staff.default_restaurant.name
                 end
    label += [['Group Name', group_name]]
  end

  case date_filter_type
  when 'single'
    label += [['Date', start_date.strftime('%d/%m/%Y')]]
  when 'range'
    label += [['Start Date', start_date.strftime('%d/%m/%Y')]]
    label += [['End Date', end_date.strftime('%d/%m/%Y')]]
  end

  bounding_box([0, 510], width: 230) do
    table(label, cell_style: { border_width: 0 })
  end
  if banner.present?
    bounding_box([370, 530], width: 250) do
      table(banner, cell_style: { border_width: 0 })
    end
  end

  move_down 20

  update_progress(20, 'Generating table headers...')
  case file_number
  when 1
    if report_restaurant?
      data.push(%w[ID Customer-Name Dining-Date Dining-Time
                   Adult Kids Party-Size Status Special-Request Package-Type Package-Price
                   Restaurant-Revenue Commision Voucher-Code Voucher-Amount])
    elsif report_restaurant_group? || report_staff_restaurants?
      data.push(%w[ID Restaurant-Name Customer-Name Dining-Date Dining-Time
                   Adult Kids Party-Size Status Special-Request Package-Type Package-Price
                   Restaurant-Revenue Commision Voucher-Code Voucher-Amount])
    end
  when 2
    data.push(%w[ID Created-At Service-Type Distance Pre-Payment
                 Payment-Type Paid-At-Date Paid-At-Time Payment-Gateway
                 Payment-Gateway-Account Old-Reservation-ID])
  end

  update_progress(30, 'Processing reservation data...')
  total_reservations = reservations.count
  processed_count = 0

  reservations.find_each do |r|
    processed_count += 1

    # More frequent progress updates for large datasets
    update_frequency = if total_reservations > 10000
                         50 # Every 50 reservations for very large datasets
                       elsif total_reservations > 1000
                         25  # Every 25 reservations for large datasets
                       elsif total_reservations > 100
                         10  # Every 10 reservations for medium datasets
                       else
                         1   # Every reservation for small datasets
                       end

    if processed_count % update_frequency == 0 || processed_count == total_reservations
      # Map data processing to 30-85% (55% range)
      progress_percent = 30 + ((processed_count.to_f / total_reservations) * 55).round
      update_progress(progress_percent, "Processing reservation #{processed_count} of #{total_reservations}")
    end

    total_package_price = if r.package?
                            total = r.package_obj.formatted_packages.map do |item|
                              Money.new(item['price_cents'], item['price_currency']).amount
                            end.sum
                            decimal_format(total)
                          end
    row_data = case file_number
               when 1
                 row = if report_restaurant_group? || report_staff_restaurants?
                         [r.id, r.restaurant.name]
                       else
                         [r.id]
                       end
                 row + [
                   r.name,
                   r.date,
                   r.start_time_format,
                   r.adult,
                   r.kids,
                   r.party_size,
                   r.status,
                   r.special_request,
                   r.package? ? r.package_obj.type : '',
                   total_package_price,
                   r.package? ? r.package_obj.amount : '',
                   r.package? ? r.package_obj.revenue_amount : '',
                   r.vouchers.present? ? r.voucher_codes_humanize : '',
                   r.vouchers.present? ? humanized_money(r.vouchers_amount) : '',
                 ]
               when 2
                 [
                   r.id,
                   date_format(r.created_at),
                   r.service_type_humanize,
                   get_reservation_distance(r.distance_to_restaurant),
                   calc_prepayment(r),
                   r.payment_type,
                   date_format(r.paid_at&.to_date),
                   r.paid_at&.strftime('%H:%M'),
                   r.payment_gateway,
                   r.,
                   r.old_reservation_id,
                 ]
               end
    data.push(row_data)
  end

  update_progress(85, "Preparing table rendering for #{data.length} rows...")

  # Add progress updates during table rendering
  if data.length > 5000
    update_progress(86, "Rendering large table with #{data.length} rows (this may take a few minutes)...")

    # Start a progress indicator thread for very large tables
    progress_thread = Thread.new do
      sleep(5) # Wait 5 seconds before starting progress updates
      [87, 88].each do |progress|
        sleep(10) # Update every 10 seconds
        update_progress(progress, "Table rendering in progress... (#{data.length} rows)")
      end
    end

    begin
      table(data, table_options(file_number))
    ensure
      progress_thread.kill if progress_thread&.alive?
    end

    update_progress(89, "Large table rendering completed (#{data.length} rows)")
  elsif data.length > 1000
    update_progress(87, "Rendering table with #{data.length} rows...")
    table(data, table_options(file_number))
    update_progress(89, 'Table rendering completed')
  else
    # For small datasets, render normally
    update_progress(87, "Rendering table with #{data.length} rows...")
    table(data, table_options(file_number))
    update_progress(89, 'Table rendering completed')
  end

  update_progress(90, 'PDF table generation completed')
end

#save_attachmentObject

Legacy methods kept for compatibility (but not used in new workflow)



321
322
323
324
# File 'app/my_lib/progress_trackable_report_pdf.rb', line 321

def save_attachment
  self.attachment = store_to_attachment
  delete_report(attachment)
end

#store_to_attachmentObject



326
327
328
329
330
# File 'app/my_lib/progress_trackable_report_pdf.rb', line 326

def store_to_attachment
  new_attachment = Attachment.new pdf: open(filepath)
  new_attachment.save!
  new_attachment
end

#table_options(file_number) ⇒ Object

Table options method (identical to original ReportPdf)



282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'app/my_lib/progress_trackable_report_pdf.rb', line 282

def table_options(file_number)
  table_column_widths = TABLE_COLUMN_WIDTHS.dup
  restaurant_name_column = table_column_widths.index(73)
  if report_restaurant? && restaurant_name_column.present?
    table_column_widths.delete_at(restaurant_name_column)
  end

  config = if file_number == 1
             table_column_widths
           else
             TABLE_COLUMN_WIDTHS_F2
           end

  column_widths = {}
  config.each_with_index { |v, k| column_widths[k] = v }
  {
    cell_style: { size: 8 },
    column_widths: column_widths,
    header: true,
  }
end