fix: Generate A6 sticker PDF with inline HTML instead of blade template
Build HTML directly in service without relying on view files. This eliminates dependency on blade template file existing on production server. Include all styling inline in heredoc string with: - A6 dimensions (148mm 105mm) - Order number, customer surname, design name - Order status and date - QR code in SVG format - Clean, printable layout Add helper methods: - truncate(): Truncate design name to fit sticker - formatStatus(): Format order status for display
This commit is contained in:
@@ -8,7 +8,6 @@ use chillerlan\QRCode\QRCode;
|
||||
use chillerlan\QRCode\QROptions;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\View;
|
||||
|
||||
class QrStickerService
|
||||
{
|
||||
@@ -17,6 +16,10 @@ class QrStickerService
|
||||
*/
|
||||
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]);
|
||||
@@ -29,7 +32,10 @@ class QrStickerService
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -76,11 +82,6 @@ class QrStickerService
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
@@ -95,61 +96,185 @@ class QrStickerService
|
||||
$qrCode = new QRCode($options);
|
||||
$qrSvg = $qrCode->render($qrUrl);
|
||||
|
||||
// Determine order type and format label
|
||||
$orderType = $order->type === 'standard'
|
||||
? 'STOCK ORDER'
|
||||
: 'CUSTOM ORDER';
|
||||
// Determine order type
|
||||
$orderType = $order->type === 'standard' ? 'STOCK ORDER' : 'CUSTOM ORDER';
|
||||
|
||||
// Extract design name from first order item
|
||||
// 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 (last word of name)
|
||||
// Extract customer surname
|
||||
$customerName = $order->user?->name ?? '';
|
||||
$nameParts = explode(' ', trim($customerName));
|
||||
$customerSurname = end($nameParts);
|
||||
|
||||
// Prepare data for the view
|
||||
$data = [
|
||||
'order' => $order,
|
||||
'qrSvg' => $qrSvg,
|
||||
'orderType' => $orderType,
|
||||
'designName' => $designName,
|
||||
'customerSurname' => $customerSurname,
|
||||
'qrUrl' => $qrUrl,
|
||||
'generatedAt' => now()->format('Y-m-d H:i'),
|
||||
];
|
||||
|
||||
// Render the view to HTML first
|
||||
try {
|
||||
$viewPath = resource_path('views/stickers/qr-sticker.blade.php');
|
||||
if (!file_exists($viewPath)) {
|
||||
throw new \Exception("View file not found at: {$viewPath}");
|
||||
}
|
||||
|
||||
// Compile and render the view
|
||||
$html = View::file($viewPath, $data)->render();
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Failed to render sticker view', [
|
||||
'order_uuid' => $order->uuid,
|
||||
'error' => $e->getMessage(),
|
||||
'view_path' => $viewPath ?? 'unknown',
|
||||
]);
|
||||
throw $e;
|
||||
// Build HTML directly (no view file dependency)
|
||||
$html = <<<HTML
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: Arial, sans-serif; }
|
||||
.sticker {
|
||||
width: 148mm;
|
||||
height: 105mm;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 5mm;
|
||||
background: white;
|
||||
}
|
||||
.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: 55mm;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.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;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: white;
|
||||
}
|
||||
.qr-code svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.footer {
|
||||
font-size: 6pt;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
border-top: 0.5pt solid #eee;
|
||||
padding-top: 1mm;
|
||||
margin-top: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sticker">
|
||||
<div class="container">
|
||||
<div class="info-section">
|
||||
<div class="header">
|
||||
<span class="order-type">{$orderType}</span>
|
||||
<div class="order-number">{$order->order_number}</div>
|
||||
</div>
|
||||
<div class="customer-info">
|
||||
<div class="label">Customer</div>
|
||||
<div class="name">{$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;">
|
||||
{$this->truncate($designName, 12)}
|
||||
</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Date:</span>
|
||||
<span class="detail-value">{$order->created_at->format('M d')}</span>
|
||||
</div>
|
||||
<div class="detail-row">
|
||||
<span class="detail-label">Status:</span>
|
||||
<span class="detail-value" style="text-transform: capitalize;">
|
||||
{$this->formatStatus($order->status)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
Gen: {$order->created_at->format('Y-m-d H:i')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="qr-section">
|
||||
<div class="qr-code">
|
||||
{$qrSvg}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
HTML;
|
||||
|
||||
// Generate PDF from HTML using DomPDF
|
||||
try {
|
||||
$pdf = Pdf::loadHTML($html);
|
||||
|
||||
// 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)
|
||||
$pdf->setPaper([0, 0, 420.94, 297.64], 'portrait');
|
||||
|
||||
// Store PDF
|
||||
$filename = "{$order->uuid}.pdf";
|
||||
$path = "qr-stickers/{$filename}";
|
||||
$pdfContent = $pdf->output();
|
||||
@@ -164,4 +289,20 @@ class QrStickerService
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user