require "prawn" require "prawn/qrcode" require "rqrcode" # PDF ticket generator service using Prawn # # Generates simple, compact PDF tickets with QR codes for event entry validation # Clean, minimalist design that fits on a single page class TicketPdfGenerator # Suppress Prawn's internationalization warning for built-in fonts Prawn::Fonts::AFM.hide_m17n_warning = true attr_reader :ticket def initialize(ticket) @ticket = ticket end def generate Prawn::Document.new(page_size: "A4", margin: 40) do |pdf| # Simple header create_simple_header(pdf) # Event and ticket info in compact layout create_ticket_info(pdf) # QR code section create_qr_section(pdf) # Simple footer create_simple_footer(pdf) end.render end private def create_simple_header(pdf) # Brand name pdf.fill_color "6366F1" pdf.font "Helvetica", style: :bold, size: 24 pdf.text "AperoNight", align: :center pdf.move_down 5 pdf.font "Helvetica", size: 10 pdf.fill_color "64748B" pdf.text "Billet d'entree", align: :center pdf.move_down 20 # Simple divider line pdf.stroke_color "E5E7EB" pdf.horizontal_line 0, pdf.bounds.width pdf.move_down 20 end def create_ticket_info(pdf) # Event name - prominent pdf.fill_color "1F2937" pdf.font "Helvetica", style: :bold, size: 18 pdf.text ticket.event.name, align: :center pdf.move_down 15 # Two-column layout for ticket details pdf.bounding_box([0, pdf.cursor], width: pdf.bounds.width, height: 120) do # Left column pdf.bounding_box([0, pdf.cursor], width: pdf.bounds.width / 2 - 20, height: 120) do create_info_item(pdf, "Date", ticket.event.start_time.strftime("%d %B %Y")) create_info_item(pdf, "Heure", ticket.event.start_time.strftime("%H:%M")) create_info_item(pdf, "Lieu", ticket.event.venue_name) end # Right column pdf.bounding_box([pdf.bounds.width / 2 + 20, pdf.cursor], width: pdf.bounds.width / 2 - 20, height: 120) do create_info_item(pdf, "Type", ticket.ticket_type.name) create_info_item(pdf, "Prix", "#{sprintf('%.2f', ticket.price_euros)} EUR") create_info_item(pdf, "Titulaire", "#{ticket.first_name} #{ticket.last_name}") end end pdf.move_down 30 end def create_info_item(pdf, label, value) pdf.font "Helvetica", style: :bold, size: 9 pdf.fill_color "64748B" pdf.text label.upcase pdf.move_down 2 pdf.font "Helvetica", size: 11 pdf.fill_color "1F2937" pdf.text value pdf.move_down 12 end def create_qr_section(pdf) # Center the QR code horizontally qr_size = 120 x_position = (pdf.bounds.width - qr_size) / 2 pdf.bounding_box([x_position, pdf.cursor], width: qr_size, height: qr_size + 40) do # QR Code title pdf.font "Helvetica", style: :bold, size: 12 pdf.fill_color "1F2937" pdf.text "Code d'entree", align: :center pdf.move_down 10 # Generate QR code generate_simple_qr_code(pdf, qr_size) pdf.move_down 10 # QR code ID pdf.font "Helvetica", size: 8 pdf.fill_color "64748B" pdf.text "ID: #{ticket.qr_code[0..15]}...", align: :center end pdf.move_down 40 end def generate_simple_qr_code(pdf, size) # Ensure all required data is present before generating QR code if ticket.qr_code.blank? raise "Ticket QR code is missing" end # Build QR code data with safe association loading qr_code_data = build_qr_code_data(ticket) # Validate QR code data before creating QR code if qr_code_data.blank? || qr_code_data == "{}" Rails.logger.error "QR code data is empty: ticket_id=#{ticket.id}, qr_code=#{ticket.qr_code}, event_id=#{ticket.ticket_type&.event_id}, user_id=#{ticket.order&.user_id}" raise "QR code data is empty or invalid" end # Ensure qr_code_data is a proper string for QR code generation unless qr_code_data.is_a?(String) && qr_code_data.length > 2 Rails.logger.error "QR code data is not a valid string: #{qr_code_data.inspect} (class: #{qr_code_data.class})" raise "QR code data must be a valid string" end # Generate QR code pdf.print_qr_code(qr_code_data, extent: size, align: :center) end def create_simple_footer(pdf) # Security notice pdf.font "Helvetica", size: 8 pdf.fill_color "64748B" pdf.text "Ce billet est valable pour une seule entree.", align: :center pdf.text "Presentez ce code QR a l'entree de l'evenement.", align: :center pdf.move_down 10 # Divider line pdf.stroke_color "E5E7EB" pdf.horizontal_line 0, pdf.bounds.width pdf.move_down 5 # Generation timestamp pdf.font "Helvetica", size: 7 pdf.fill_color "9CA3AF" timestamp = "Genere le #{Time.current.strftime('%d/%m/%Y a %H:%M')}" pdf.text timestamp, align: :center end def build_qr_code_data(ticket) # Try multiple approaches to get valid QR code data begin # Primary approach: full JSON with all data data = { ticket_id: ticket.id, qr_code: ticket.qr_code, event_id: ticket.ticket_type&.event_id, user_id: ticket.order&.user_id }.compact # Ensure we have the minimum required data if data[:ticket_id] && data[:qr_code] return data.to_json end rescue StandardError => e Rails.logger.warn "Failed to build complex QR data: #{e.message}" end # Fallback approach: just use the ticket's QR code string begin return ticket.qr_code.to_s if ticket.qr_code.present? rescue StandardError => e Rails.logger.warn "Failed to use ticket QR code: #{e.message}" end # Final fallback: simple ticket identifier "TICKET-#{ticket.id}" end end