Files
Additional/app/Services/QrStickerService.php
twotalesanimation ed57956694 feat: Attach QR sticker PDF to Trello card on order creation
When a new order is created and a Trello card is made, now automatically
generate the A6 QR sticker PDF and attach it to the card using Trello's
attachment API. Include error handling and logging.
2026-01-02 21:01:17 +02:00

293 lines
8.8 KiB
PHP
Raw Permalink 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 SVG, then convert to data URI
$options = new QROptions([
'outputType' => QRCode::OUTPUT_MARKUP_SVG,
'eccLevel' => QRCode::ECC_H,
'scale' => 4,
'imageBase64' => false,
]);
$qrCode = new QRCode($options);
$qrSvg = $qrCode->render($qrUrl);
// Convert SVG to data URI for embedding in HTML
$qrDataUri = 'data:image/svg+xml;base64,' . base64_encode($qrSvg);
// 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: 2mm;
height: 100%;
}
.info-section {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
overflow: hidden;
}
.qr-section {
width: 100mm;
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: 16pt;
font-weight: bold;
margin-top: 1mm;
color: #222;
word-break: break-all;
}
.details {
font-size: 10pt;
color: #666;
line-height: 1.4;
}
.detail-row {
display: flex;
justify-content: space-between;
gap: 2mm;
margin-bottom: 1mm;
}
.detail-label {
color: #999;
text-transform: uppercase;
flex: 1;
}
.detail-value {
text-align: right;
font-weight: bold;
text-transform: uppercase;
color: #222;
flex: 0 0 auto;
}
.qr-code {
width: 95mm;
height: 95mm;
}
.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="details">
<div class="detail-row">
<span class="detail-label">Customer: </span>
<span class="detail-value">' . htmlspecialchars($customerSurname) . '</span>
</div>
<div class="detail-row">
<span class="detail-label">Design:</span>
<span class="detail-value">' . 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>
<div class="qr-code">
<img src="' . $qrDataUri . '" alt="QR Code" />
</div>
<div class="footer">
Gen: ' . htmlspecialchars($order->created_at->format('Y-m-d H:i')) . '
</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);
}
}