297 lines
9.1 KiB
Ruby
Executable File
297 lines
9.1 KiB
Ruby
Executable File
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 |