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.
This commit is contained in:
twotalesanimation
2026-01-02 21:01:17 +02:00
parent 93148ef9af
commit ed57956694
3 changed files with 62 additions and 47 deletions
@@ -3,15 +3,18 @@
namespace App\Listeners;
use App\Events\OrderCreated;
use App\Services\QrStickerService;
use App\Services\SlackNotifierService;
use App\Services\TrelloService;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
class NotifySlackOnOrderCreated
{
public function __construct(
protected SlackNotifierService $slack,
protected TrelloService $trello,
protected QrStickerService $qrSticker,
) {
}
@@ -62,6 +65,35 @@ class NotifySlackOnOrderCreated
$this->trello->setTextField($cardId, 'print_size', $printSize);
Log::info('Trello card created for order', ['order_uuid' => $order->uuid, 'card_id' => $cardId]);
// Generate QR sticker and attach to Trello card
try {
$stickerPaths = $this->qrSticker->generateSticker($order);
$pdfPath = storage_path('app/public/' . $stickerPaths['pdf_path']);
if (Storage::disk('public')->exists($stickerPaths['pdf_path'])) {
// Generate a public URL for the PDF
$stickerUrl = Storage::disk('public')->url($stickerPaths['pdf_path']);
// Attach to Trello card
$this->trello->attachFile(
$cardId,
"QR-Sticker-{$order->order_number}.pdf",
$stickerUrl
);
Log::info('QR sticker attached to Trello card', [
'order_uuid' => $order->uuid,
'card_id' => $cardId,
'pdf_path' => $stickerPaths['pdf_path'],
]);
}
} catch (\Exception $e) {
Log::error('Failed to generate or attach QR sticker to Trello', [
'order_uuid' => $order->uuid,
'error' => $e->getMessage(),
]);
}
}
}
}
+19 -32
View File
@@ -133,7 +133,7 @@ class QrStickerService
}
.container {
display: flex;
gap: 3mm;
gap: 2mm;
height: 100%;
}
.info-section {
@@ -144,7 +144,7 @@ class QrStickerService
overflow: hidden;
}
.qr-section {
width: 50mm;
width: 100mm;
display: flex;
align-items: center;
justify-content: center;
@@ -164,29 +164,15 @@ class QrStickerService
letter-spacing: 0.5pt;
}
.order-number {
font-size: 9pt;
font-size: 16pt;
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;
font-size: 10pt;
color: #666;
line-height: 1.4;
}
@@ -197,20 +183,21 @@ class QrStickerService
margin-bottom: 1mm;
}
.detail-label {
font-weight: 500;
color: #999;
text-transform: uppercase;
font-size: 6.5pt;
flex: 1;
}
.detail-value {
text-align: right;
font-weight: bold;
text-transform: uppercase;
color: #222;
flex: 0 0 auto;
}
.qr-code {
width: 50mm;
height: 50mm;
width: 95mm;
height: 95mm;
}
.qr-code img {
width: 100%;
@@ -233,14 +220,14 @@ class QrStickerService
<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">Customer: </span>
<span class="detail-value">' . htmlspecialchars($customerSurname) . '</span>
</div>
<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>
<span class="detail-value">' . htmlspecialchars($this->truncate($designName, 12)) . '</span>
</div>
<div class="detail-row">
<span class="detail-label">Date:</span>
@@ -251,14 +238,14 @@ class QrStickerService
<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="' . $qrDataUri . '" alt="QR Code" />
</div>
<div class="footer">
Gen: ' . htmlspecialchars($order->created_at->format('Y-m-d H:i')) . '
</div>
</div>
</div>
+7 -11
View File
@@ -116,7 +116,7 @@ Route::match(['get', 'post'], '/test/sticker/{orderNumber}/regenerate', function
$order = \App\Models\Order::where('order_number', $orderNumber)->first();
if (!$order) {
return response()->json(['error' => 'Order not found'], 404);
return response('Order not found', 404);
}
try {
@@ -131,18 +131,14 @@ Route::match(['get', 'post'], '/test/sticker/{orderNumber}/regenerate', function
$service = new \App\Services\QrStickerService();
$paths = $service->generateSticker($order);
return response()->json([
'success' => true,
'message' => 'Sticker PDF generated successfully',
'svg_path' => $paths['svg_path'],
'pdf_path' => $paths['pdf_path'],
'view_url' => route('test.sticker', ['orderNumber' => $orderNumber])
// Return PDF inline for viewing
$pdfPath = storage_path('app/public/' . $paths['pdf_path']);
return response()->file($pdfPath, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'inline; filename="QR-' . $order->order_number . '.pdf"'
]);
} catch (\Exception $e) {
return response()->json([
'error' => 'Failed to generate sticker',
'message' => $e->getMessage()
], 500);
return response('Failed to generate sticker: ' . $e->getMessage(), 500);
}
})->name('test.sticker.regenerate');