Files
Additional/app/Services/QrStickerService.php
T
twotalesanimation 5dfb63dacb fix: Use base64-encoded PNG for QR code in PDF stickers
DomPDF renders embedded PNG images more reliably than inline SVG.
Changed QR code generation to OUTPUT_IMAGE_PNG with base64 encoding,
then embed as img tag in the PDF HTML.
2026-01-02 20:42:19 +02:00

303 lines
9.0 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Services;
use App\Models\Order;
use Barryvdh\DomPDF\Facade\Pdf;
use chillerlan\QRCode\QRCode;
use chillerlan\QRCode\QROptions;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class QrStickerService
{
/**
* Generate both SVG and A6 PDF sticker for an order
*/
public function generateSticker(Order $order): array
{
Log::info('Starting QR sticker generation', [
'order_uuid' => $order->uuid,
'order_number' => $order->order_number,
]);
try {
// Generate QR code URL
$qrUrl = route('ops.order.show', ['token' => $order->qr_token]);
Log::debug('Generating QR sticker', [
'order_uuid' => $order->uuid,
'order_number' => $order->order_number,
'qr_url' => $qrUrl,
]);
// Generate SVG (for web display)
$svgPath = $this->generateSvg($order, $qrUrl);
Log::info('SVG QR code generated', [
'order_uuid' => $order->uuid,
'svg_path' => $svgPath,
]);
// Generate A6 PDF (for printing)
$pdfPath = $this->generatePdf($order, $qrUrl);
Log::info('QR sticker generated successfully', [
'order_uuid' => $order->uuid,
'svg_path' => $svgPath,
'pdf_path' => $pdfPath,
]);
return [
'svg_path' => $svgPath,
'pdf_path' => $pdfPath,
];
} catch (\Exception $e) {
Log::error('Failed to generate QR sticker', [
'order_uuid' => $order->uuid,
'error' => $e->getMessage(),
]);
throw $e;
}
}
/**
* Generate SVG QR code for web display
*/
private function generateSvg(Order $order, string $qrUrl): string
{
$options = new QROptions([
'outputType' => QRCode::OUTPUT_MARKUP_SVG,
'eccLevel' => QRCode::ECC_H,
'scale' => 3,
'imageBase64' => false,
]);
$qrCode = new QRCode($options);
$qrSvg = $qrCode->render($qrUrl);
$path = "qr-codes/{$order->uuid}.svg";
Storage::disk('public')->put($path, $qrSvg);
return $path;
}
/**
* Generate A6 PDF sticker (105mm × 148mm) with QR code and readable info
*/
private function generatePdf(Order $order, string $qrUrl): string
{
// Generate QR code as base64-encoded PNG (DomPDF handles this better than SVG)
$options = new QROptions([
'outputType' => QRCode::OUTPUT_IMAGE_PNG,
'eccLevel' => QRCode::ECC_H,
'scale' => 8,
'imageBase64' => true,
]);
$qrCode = new QRCode($options);
$qrBase64 = $qrCode->render($qrUrl);
// Determine order type
$orderType = $order->type === 'standard' ? 'STOCK ORDER' : 'CUSTOM ORDER';
// Extract design name
$designName = '';
if ($order->items && $order->items->count() > 0) {
$firstItem = $order->items->first();
$designName = $firstItem->product?->name ?? $firstItem->name ?? 'Custom Design';
}
// Extract customer surname
$customerName = $order->user?->name ?? '';
$nameParts = explode(' ', trim($customerName));
$customerSurname = end($nameParts);
// Build HTML with proper SVG embedding (avoid heredoc issues)
$html = '<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: Arial, sans-serif; margin: 0; padding: 0; }
.sticker {
width: 148mm;
height: 105mm;
display: flex;
flex-direction: column;
padding: 5mm;
background: white;
page-break-after: avoid;
}
.container {
display: flex;
gap: 3mm;
height: 100%;
}
.info-section {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
overflow: hidden;
}
.qr-section {
width: 50mm;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.header {
border-bottom: 1pt solid #ddd;
padding-bottom: 2mm;
}
.order-type {
display: inline-block;
background: #333;
color: white;
padding: 1mm 2mm;
font-size: 7pt;
font-weight: bold;
letter-spacing: 0.5pt;
}
.order-number {
font-size: 9pt;
font-weight: bold;
margin-top: 1mm;
color: #222;
word-break: break-all;
}
.customer-info {
font-size: 8pt;
color: #555;
line-height: 1.3;
}
.customer-info .label {
font-size: 7pt;
color: #999;
text-transform: uppercase;
letter-spacing: 0.3pt;
}
.customer-info .name {
font-weight: 500;
color: #222;
}
.details {
font-size: 7.5pt;
color: #666;
line-height: 1.4;
}
.detail-row {
display: flex;
justify-content: space-between;
gap: 2mm;
margin-bottom: 1mm;
}
.detail-label {
font-weight: 500;
color: #999;
text-transform: uppercase;
font-size: 6.5pt;
flex: 1;
}
.detail-value {
text-align: right;
color: #222;
flex: 0 0 auto;
}
.qr-code {
width: 50mm;
height: 50mm;
}
.qr-code img {
width: 100%;
height: 100%;
}
.footer {
font-size: 6pt;
color: #999;
text-align: center;
border-top: 0.5pt solid #eee;
padding-top: 1mm;
}
</style>
</head>
<body>
<div class="sticker">
<div class="container">
<div class="info-section">
<div class="header">
<span class="order-type">' . htmlspecialchars($orderType) . '</span>
<div class="order-number">' . htmlspecialchars($order->order_number) . '</div>
</div>
<div class="customer-info">
<div class="label">Customer</div>
<div class="name">' . htmlspecialchars($customerSurname) . '</div>
</div>
<div class="details">
<div class="detail-row">
<span class="detail-label">Design:</span>
<span class="detail-value" style="font-size: 6.5pt;">' . htmlspecialchars($this->truncate($designName, 12)) . '</span>
</div>
<div class="detail-row">
<span class="detail-label">Date:</span>
<span class="detail-value">' . htmlspecialchars($order->created_at->format('M d')) . '</span>
</div>
<div class="detail-row">
<span class="detail-label">Status:</span>
<span class="detail-value">' . htmlspecialchars($this->formatStatus($order->status)) . '</span>
</div>
</div>
<div class="footer">
Gen: ' . htmlspecialchars($order->created_at->format('Y-m-d H:i')) . '
</div>
</div>
<div class="qr-section">
<div class="qr-code">
<img src="' . $qrBase64 . '" alt="QR Code" />
</div>
</div>
</div>
</div>
</body>
</html>';
try {
$pdf = Pdf::loadHTML($html);
// Set to A6 size (105mm × 148mm) in portrait orientation
// 105mm = 297.64 points, 148mm = 420.94 points
$pdf->setPaper([0, 0, 297.64, 420.94], 'portrait');
$pdf->setOption('isRemoteEnabled', true);
$filename = "{$order->uuid}.pdf";
$path = "qr-stickers/{$filename}";
$pdfContent = $pdf->output();
Storage::disk('public')->put($path, $pdfContent);
return $path;
} catch (\Exception $e) {
Log::error('DomPDF generation failed', [
'order_uuid' => $order->uuid,
'error' => $e->getMessage(),
]);
throw $e;
}
}
/**
* Helper: Truncate string
*/
private function truncate($string, $length): string
{
return substr($string, 0, $length);
}
/**
* Helper: Format status text
*/
private function formatStatus($status): string
{
return str_replace('_', ' ', $status);
}
}