require "prawn" require "prawn/qrcode" require "rqrcode" # Service de génération de billets PDF utilisant Prawn # # Génère des billets PDF simples et compacts avec codes QR pour la validation d'entrée # Design propre et minimaliste qui tient sur une seule 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: [ 350, 600 ], margin: 20) do |pdf| # Header pdf.fill_color "2D1B69" pdf.font "Helvetica", style: :bold, size: 24 pdf.text ENV.fetch("APP_NAME", "Aperonight"), align: :center pdf.move_down 10 # Event name pdf.fill_color "000000" pdf.font "Helvetica", style: :bold, size: 18 pdf.text ticket.event.name, align: :center pdf.move_down 10 # Ticket info box pdf.stroke_color "E5E7EB" pdf.fill_color "F9FAFB" pdf.rounded_rectangle [ 0, pdf.cursor ], 310, 150, 10 pdf.fill_and_stroke pdf.move_down 10 pdf.fill_color "000000" pdf.font "Helvetica", size: 12 # Customer name pdf.indent 10 do pdf.text "Titulaire du billet :", style: :bold pdf.text "#{ticket.first_name} #{ticket.last_name}" end pdf.move_down 8 # Ticket details pdf.indent 10 do pdf.text "Type de billet :", style: :bold pdf.text ticket.ticket_type.name end pdf.move_down 8 pdf.indent 10 do pdf.text "Prix :", style: :bold pdf.text "#{ticket.price_euros} €" end pdf.move_down 8 pdf.indent 10 do pdf.text "Date et heure :", style: :bold pdf.text ticket.event.start_time.strftime("%d %B %Y à %H:%M") end pdf.move_down 20 # Informations sur le lieu pdf.fill_color "374151" pdf.font "Helvetica", style: :bold, size: 14 pdf.text "Informations sur le lieu" pdf.move_down 8 pdf.font "Helvetica", size: 11 pdf.text ticket.event.venue_name, style: :bold pdf.text ticket.event.venue_address pdf.move_down 20 # Code QR pdf.fill_color "000000" pdf.font "Helvetica", style: :bold, size: 14 pdf.text "Code QR", align: :center pdf.move_down 10 # 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 - prawn-qrcode expects the data string directly pdf.print_qr_code(qr_code_data, extent: 120, align: :center) pdf.move_down 15 # QR code text pdf.font "Helvetica", size: 8 pdf.fill_color "6B7280" pdf.text "#{ticket.qr_code}", align: :center # Ticket ID pdf.font "Helvetica", size: 8 pdf.fill_color "6B7280" pdf.text "ID du billet : #{ticket.id}", align: :center # Footer pdf.move_down 30 pdf.stroke_color "E5E7EB" pdf.horizontal_line 0, 310 pdf.move_down 6 pdf.font "Helvetica", size: 8 pdf.fill_color "6B7280" pdf.text "Ce billet est valable pour une seule entrée.", align: :center pdf.text "Présentez ce billet à l'entrée du lieu.", align: :center pdf.move_down 5 pdf.text "Généré le #{Time.current.strftime('%d %B %Y à %H:%M')}", align: :center end.render end private def create_simple_header(pdf) # Nom de la marque 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) # Nom de l'événement - proéminent 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)} €") 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