feat: Add A6 PDF QR stickers with human-readable order info
Implement QrStickerService to generate both SVG (web) and PDF (print) stickers:
- Generate 32x32mm QR codes using SimpleSoftwareIO
- Create A6 (148mm 105mm) PDF stickers using DomPDF
- Include human-readable info: order number, customer, status, date, tracking
- Store order type (STOCK/CUSTOM) with visual badge
- Add generation timestamp to footer
Create stickers/qr-sticker.blade.php template:
- Two-column layout: info left, QR right
- Styled for A6 landscape printing
- Shows order number, customer, status, date, tracking info
- QR code positioned for easy scanning
- Print-optimized CSS
Update GenerateQrCodeOnOrderCreated listener:
- Now calls QrStickerService::generateSticker()
- Generates both SVG and PDF on OrderCreated event
- Logs paths to both formats
Add OpsController::downloadSticker() endpoint:
- GET /ops/orders/{id}/sticker/download
- Returns PDF with filename QR-{order_number}.pdf
- Requires ops access authorization
- Logs all downloads with user context
Update ops order-detail view:
- Add download button for A6 sticker PDF
- Position next to QR code display
- Link to new sticker download route
This commit is contained in:
@@ -200,4 +200,39 @@ class OpsController extends Controller
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download QR sticker PDF for printing
|
||||
*
|
||||
* GET /ops/orders/{id}/sticker/download
|
||||
*/
|
||||
public function downloadSticker(Order $order)
|
||||
{
|
||||
// Authorize
|
||||
if (! auth()->user()->can('access-ops')) {
|
||||
abort(403, 'Unauthorized to access ops interface');
|
||||
}
|
||||
|
||||
$stickerPath = "qr-stickers/{$order->uuid}.pdf";
|
||||
|
||||
if (! \Illuminate\Support\Facades\Storage::disk('public')->exists($stickerPath)) {
|
||||
Log::warning('QR sticker PDF not found', [
|
||||
'order_uuid' => $order->uuid,
|
||||
'path' => $stickerPath,
|
||||
]);
|
||||
|
||||
abort(404, 'QR sticker not found');
|
||||
}
|
||||
|
||||
Log::info('QR sticker downloaded', [
|
||||
'order_uuid' => $order->uuid,
|
||||
'user_id' => auth()->id(),
|
||||
'user_name' => auth()->user()->name,
|
||||
]);
|
||||
|
||||
return \Illuminate\Support\Facades\Storage::disk('public')->download(
|
||||
$stickerPath,
|
||||
"QR-{$order->order_number}.pdf"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\OrderCreated;
|
||||
use App\Services\QrStickerService;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use SimpleSoftwareIO\QrCode\Facades\QrCode;
|
||||
|
||||
class GenerateQrCodeOnOrderCreated
|
||||
{
|
||||
@@ -25,26 +24,19 @@ class GenerateQrCodeOnOrderCreated
|
||||
$order->save();
|
||||
}
|
||||
|
||||
// Generate QR code pointing to ops interface
|
||||
$qrUrl = route('ops.order.show', ['token' => $order->qr_token]);
|
||||
|
||||
Log::debug('Generating QR code', [
|
||||
Log::debug('Generating QR sticker', [
|
||||
'order_uuid' => $order->uuid,
|
||||
'qr_url' => $qrUrl,
|
||||
'order_number' => $order->order_number,
|
||||
]);
|
||||
|
||||
// Generate QR code SVG
|
||||
$qrCode = QrCode::size(300)
|
||||
->errorCorrection('H')
|
||||
->generate($qrUrl);
|
||||
// Generate both SVG (web) and PDF (print) stickers
|
||||
$service = new QrStickerService();
|
||||
$paths = $service->generateSticker($order);
|
||||
|
||||
// Save QR code image
|
||||
$path = "qr-codes/{$order->uuid}.svg";
|
||||
Storage::disk('public')->put($path, $qrCode);
|
||||
|
||||
Log::info('QR code generated successfully', [
|
||||
Log::info('QR sticker generated successfully', [
|
||||
'order_uuid' => $order->uuid,
|
||||
'path' => $path,
|
||||
'svg_path' => $paths['svg_path'],
|
||||
'pdf_path' => $paths['pdf_path'],
|
||||
'token' => substr($order->qr_token, 0, 8) . '...',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Order;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use SimpleSoftwareIO\QrCode\Facades\QrCode;
|
||||
|
||||
class QrStickerService
|
||||
{
|
||||
/**
|
||||
* Generate both SVG and A6 PDF sticker for an order
|
||||
*/
|
||||
public function generateSticker(Order $order): array
|
||||
{
|
||||
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);
|
||||
|
||||
// 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
|
||||
{
|
||||
$qrCode = QrCode::size(300)
|
||||
->errorCorrection('H')
|
||||
->generate($qrUrl);
|
||||
|
||||
$path = "qr-codes/{$order->uuid}.svg";
|
||||
Storage::disk('public')->put($path, $qrCode);
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate A6 PDF sticker (105mm × 148mm) with QR code and readable info
|
||||
*
|
||||
* A6 dimensions: 105mm × 148mm landscape
|
||||
* Margins: 5mm all around
|
||||
* QR size: ~50mm × 50mm
|
||||
* Text area: remaining space for order info
|
||||
*/
|
||||
private function generatePdf(Order $order, string $qrUrl): string
|
||||
{
|
||||
// Generate inline QR code as base64 data URL
|
||||
$qrSvg = QrCode::size(400)
|
||||
->errorCorrection('H')
|
||||
->generate($qrUrl);
|
||||
|
||||
// Determine order type and format label
|
||||
$orderType = $order->type === 'standard'
|
||||
? 'STOCK ORDER'
|
||||
: 'CUSTOM ORDER';
|
||||
|
||||
// Prepare data for the view
|
||||
$data = [
|
||||
'order' => $order,
|
||||
'qrSvg' => $qrSvg,
|
||||
'orderType' => $orderType,
|
||||
'qrUrl' => $qrUrl,
|
||||
'generatedAt' => now()->format('Y-m-d H:i'),
|
||||
];
|
||||
|
||||
// Generate PDF using the sticker template
|
||||
$pdf = Pdf::loadView('stickers.qr-sticker', $data);
|
||||
|
||||
// Configure for A6 landscape
|
||||
// A6 is 105mm × 148mm (landscape: 148mm × 105mm)
|
||||
$pdf->setPaper([0, 0, 420.94, 297.64], 'portrait'); // 148mm × 105mm in points (1mm ≈ 2.834645669 points)
|
||||
|
||||
// Store PDF
|
||||
$filename = "{$order->uuid}.pdf";
|
||||
$path = "qr-stickers/{$filename}";
|
||||
$pdfContent = $pdf->output();
|
||||
Storage::disk('public')->put($path, $pdfContent);
|
||||
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user