- Add picqer/php-barcode-generator dependency
- Redesign AWB PDF with barcode, From/To, Value, Service, Type - Redesign Invoice PDF with 9-row table, legal declaration, barcode - Redesign Label PDF to A5 landscape with large barcode - Add DOC/PARCEL condition for Invoice (only PARCEL has invoice) - Fix ShipmentType enum names (DocNormal/DocEconomy) - Add migration for shipment_items (row_number, unit_price) - Add migration to make name nullable - Update PdfService with base64 barcode embedding - Update ShipmentPdfController with DOC type handling - Add test_pdf_generation.php script Phase 3.5 — PDF redesign complete"
This commit is contained in:
parent
b5e136a76d
commit
f12dff5338
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\ShipmentType;
|
||||
use App\Models\Shipment;
|
||||
use App\Services\PdfService;
|
||||
use Illuminate\Http\Request;
|
||||
@ -11,49 +12,119 @@ class ShipmentPdfController extends Controller
|
||||
{
|
||||
public function __construct(protected PdfService $pdf) {}
|
||||
|
||||
/**
|
||||
* تولید AWB PDF (برای همه نوع محمولهها)
|
||||
*/
|
||||
public function awb(Shipment $shipment)
|
||||
{
|
||||
try {
|
||||
Log::info('Generating AWB PDF', ['shipment_id' => $shipment->id]);
|
||||
|
||||
$content = $this->pdf->awb($shipment);
|
||||
Log::info('AWB PDF generated successfully');
|
||||
|
||||
Log::info('AWB PDF generated successfully', ['awb' => $shipment->awb_no]);
|
||||
|
||||
return response($content, 200, [
|
||||
'Content-Type' => 'application/pdf',
|
||||
'Content-Disposition' => 'attachment; filename="AWB-' . $shipment->awb_no . '.pdf"',
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('AWB PDF generation failed', [
|
||||
'shipment_id' => $shipment->id,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response('PDF generation failed: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* تولید Invoice PDF — فقط برای محمولههای دارای کالا (PARCEL)
|
||||
*
|
||||
* محمولههای DOC (DOC_NORMAL/DOC_ECONOMY) که کالا ندارن،
|
||||
* فاکتور صادراتی ندارن و این متد براشون ارور برمیگردونه.
|
||||
*/
|
||||
public function invoice(Shipment $shipment)
|
||||
{
|
||||
try {
|
||||
// بارگذاری items برای بررسی
|
||||
$shipment->loadMissing(['items']);
|
||||
|
||||
// بررسی نوع محموله
|
||||
$isDocType = in_array($shipment->type, [
|
||||
ShipmentType::DocNormal,
|
||||
ShipmentType::DocEconomy,
|
||||
]);
|
||||
|
||||
if ($isDocType && $shipment->items->isEmpty()) {
|
||||
Log::info('Invoice PDF not available for DOC shipment without items', [
|
||||
'shipment_id' => $shipment->id,
|
||||
'type' => $shipment->type?->value,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'فاکتور فقط برای محمولههای دارای کالا (PARCEL) صادر میشود.',
|
||||
'hint' => 'محمولههای DOC (مدارک) فاکتور صادراتی ندارند.',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$content = $this->pdf->invoice($shipment);
|
||||
|
||||
Log::info('Invoice PDF generated successfully', [
|
||||
'awb' => $shipment->awb_no,
|
||||
'type' => $shipment->type?->value,
|
||||
'items_count' => $shipment->items->count(),
|
||||
]);
|
||||
|
||||
return response($content, 200, [
|
||||
'Content-Type' => 'application/pdf',
|
||||
'Content-Disposition' => 'attachment; filename="INVOICE-' . $shipment->awb_no . '.pdf"',
|
||||
]);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
// خطای مربوط به نوع محموله
|
||||
Log::info('Invoice PDF skipped', [
|
||||
'shipment_id' => $shipment->id,
|
||||
'reason' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => $e->getMessage(),
|
||||
], 400);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Invoice PDF generation failed', ['error' => $e->getMessage()]);
|
||||
Log::error('Invoice PDF generation failed', [
|
||||
'shipment_id' => $shipment->id,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response('PDF generation failed: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* تولید Label PDF — اندازه استاندارد لیبل پستی (100x150mm)
|
||||
*/
|
||||
public function label(Shipment $shipment)
|
||||
{
|
||||
try {
|
||||
$content = $this->pdf->label($shipment);
|
||||
|
||||
Log::info('Label PDF generated successfully', ['awb' => $shipment->awb_no]);
|
||||
|
||||
return response($content, 200, [
|
||||
'Content-Type' => 'application/pdf',
|
||||
'Content-Disposition' => 'attachment; filename="LABEL-' . $shipment->awb_no . '.pdf"',
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Label PDF generation failed', ['error' => $e->getMessage()]);
|
||||
Log::error('Label PDF generation failed', [
|
||||
'shipment_id' => $shipment->id,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response('PDF generation failed: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,30 +2,21 @@
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Enums\ShipmentType;
|
||||
use App\Models\Shipment;
|
||||
use App\Models\ShipmentItem;
|
||||
use Dompdf\Dompdf;
|
||||
use Dompdf\Options;
|
||||
use Illuminate\Support\Collection;
|
||||
use Picqer\Barcode\BarcodeGeneratorHTML;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Picqer\Barcode\BarcodeGeneratorPNG;
|
||||
|
||||
class PdfService
|
||||
{
|
||||
/**
|
||||
* تولید AWB PDF (برای همه نوع محمولهها)
|
||||
*/
|
||||
public function awb(Shipment $shipment): string
|
||||
{
|
||||
$shipment->loadMissing(['fromCountry', 'toCountry', 'items', 'packages']);
|
||||
|
||||
$data = [
|
||||
'shipment' => $shipment,
|
||||
'shipper' => $this->formatAddress($shipment, 'sender'),
|
||||
'receiver' => $this->formatAddress($shipment, 'receiver'),
|
||||
'items' => $shipment->items ?? collect(),
|
||||
'barcode' => $this->generateBarcode($shipment->awb_no),
|
||||
];
|
||||
|
||||
return $this->generatePdf(view('pdfs.awb', $data)->render(), 'A4', 'portrait');
|
||||
}
|
||||
|
||||
public function invoice(Shipment $shipment): string
|
||||
{
|
||||
$shipment->loadMissing(['fromCountry', 'toCountry', 'items']);
|
||||
|
||||
@ -33,65 +24,156 @@ class PdfService
|
||||
'shipment' => $shipment,
|
||||
'shipper' => $this->formatAddress($shipment, 'sender'),
|
||||
'receiver' => $this->formatAddress($shipment, 'receiver'),
|
||||
'items' => $shipment->isParcel() ? ($shipment->items ?? collect()) : collect(),
|
||||
'invoice_total_usd' => $shipment->invoice_total_usd,
|
||||
'barcode' => $this->generateBarcode($shipment->awb_no),
|
||||
'items' => $shipment->items ?? collect(),
|
||||
'invoice_total_usd' => optional($shipment->items)->sum('total_usd') ?? 0,
|
||||
'barcode_base64' => $this->generateBarcode($shipment->awb_no),
|
||||
'service_label' => $this->getServiceLabel($shipment),
|
||||
'type_label' => $this->getTypeLabel($shipment),
|
||||
];
|
||||
|
||||
return $this->generatePdf(view('pdfs.invoice', $data)->render(), 'A4', 'portrait');
|
||||
$html = view('pdfs.awb', $data)->render();
|
||||
|
||||
return $this->generatePdf($html, 'A4', 'portrait');
|
||||
}
|
||||
|
||||
/**
|
||||
* تولید Invoice PDF — فقط برای PARCEL و DOC_NORMAL/DOC_ECONOMY
|
||||
* (نه برای محمولههای DOC خالص که کالا ندارن)
|
||||
*/
|
||||
public function invoice(Shipment $shipment): string
|
||||
{
|
||||
// اگه محموله DOC هست و آیتم نداره، invoice تولید نکن
|
||||
if ($shipment->type === ShipmentType::DocNormal || $shipment->type === ShipmentType::DocEconomy) {
|
||||
if ($shipment->items->isEmpty()) {
|
||||
throw new \InvalidArgumentException(
|
||||
'فاکتور فقط برای محمولههای دارای کالا (PARCEL) صادر میشود. محمولههای DOC بدون کالا فاکتور ندارند.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$shipment->loadMissing(['fromCountry', 'toCountry', 'items']);
|
||||
|
||||
$data = [
|
||||
'shipment' => $shipment,
|
||||
'shipper' => $this->formatAddress($shipment, 'sender'),
|
||||
'receiver' => $this->formatAddress($shipment, 'receiver'),
|
||||
'items' => $shipment->items ?? collect(),
|
||||
'invoice_total_usd' => optional($shipment->items)->sum('total_usd') ?? 0,
|
||||
'barcode_base64' => $this->generateBarcode($shipment->awb_no),
|
||||
];
|
||||
|
||||
$html = view('pdfs.invoice', $data)->render();
|
||||
|
||||
return $this->generatePdf($html, 'A4', 'portrait');
|
||||
}
|
||||
|
||||
/**
|
||||
* تولید Label PDF — اندازه استاندارد لیبل پستی (100x150mm)
|
||||
*/
|
||||
public function label(Shipment $shipment): string
|
||||
{
|
||||
$shipment->loadMissing(['fromCountry', 'toCountry']);
|
||||
|
||||
$data = [
|
||||
'shipment' => $shipment,
|
||||
'shipper' => $this->formatAddress($shipment, 'sender'),
|
||||
'receiver' => $this->formatAddress($shipment, 'receiver'),
|
||||
'barcode' => $this->generateBarcode($shipment->awb_no),
|
||||
'barcode_base64' => $this->generateBarcode($shipment->awb_no),
|
||||
'origin_iso' => $shipment->fromCountry?->iso_code ?? 'IR',
|
||||
'dest_iso' => $shipment->toCountry?->iso_code ?? '',
|
||||
];
|
||||
|
||||
return $this->generatePdf(view('pdfs.label', $data)->render(), 'A4', 'portrait');
|
||||
$html = view('pdfs.label', $data)->render();
|
||||
|
||||
// A5 افقی (Landscape) — استاندارد لیبل پستی: 210×148 mm
|
||||
return $this->generatePdf($html, 'A5', 'landscape');
|
||||
}
|
||||
|
||||
private function generatePdf(string $html, string $paper, string $orientation): string
|
||||
/**
|
||||
* تولید بارکد از AWB number — base64 embed (مطمئنترین روش)
|
||||
*/
|
||||
private function generateBarcode(string $awbNo): string
|
||||
{
|
||||
try {
|
||||
$generator = new BarcodeGeneratorPNG();
|
||||
$barcode = $generator->getBarcode($awbNo, $generator::TYPE_CODE_128, 2, 40);
|
||||
|
||||
// تبدیل به base64 و استفاده از data URI
|
||||
return 'data:image/png;base64,' . base64_encode($barcode);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Barcode generation failed', [
|
||||
'awb' => $awbNo,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* برچسب Service برای AWB (Outbound/Inbound)
|
||||
*/
|
||||
private function getServiceLabel(Shipment $shipment): string
|
||||
{
|
||||
return match ($shipment->direction?->value) {
|
||||
'export' => 'Outbound',
|
||||
'import' => 'Inbound',
|
||||
default => 'Outbound',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* برچسب Type برای AWB (NON DOC / DOC)
|
||||
*/
|
||||
private function getTypeLabel(Shipment $shipment): string
|
||||
{
|
||||
return match ($shipment->type) {
|
||||
ShipmentType::Parcel => 'NON DOC',
|
||||
ShipmentType::DocNormal => 'DOC NORMAL',
|
||||
ShipmentType::DocEconomy => 'DOC ECONOMY',
|
||||
default => 'NON DOC',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* تولید PDF از HTML
|
||||
*/
|
||||
private function generatePdf(string $html, array|string $paper, string $orientation): string
|
||||
{
|
||||
$options = new Options();
|
||||
$options->set('isHtml5ParserEnabled', true);
|
||||
$options->set('isRemoteEnabled', true);
|
||||
$options->set('isRemoteEnabled', true); // مهم: برای data URI
|
||||
$options->set('defaultFont', 'DejaVu Sans');
|
||||
$options->set('dpi', 150);
|
||||
$options->set('debugKeepTemp', false);
|
||||
$options->set('debugCss', false);
|
||||
$options->set('debugLayout', false);
|
||||
|
||||
$dompdf = new Dompdf($options);
|
||||
$dompdf->loadHtml($html);
|
||||
|
||||
if (is_array($paper)) {
|
||||
$dompdf->setPaper($paper, $orientation);
|
||||
} else {
|
||||
$dompdf->setPaper($paper, $orientation);
|
||||
}
|
||||
|
||||
$dompdf->render();
|
||||
|
||||
return $dompdf->output();
|
||||
}
|
||||
|
||||
/**
|
||||
* فرمتبندی آدرس فرستنده/گیرنده
|
||||
*/
|
||||
private function formatAddress(Shipment $shipment, string $prefix): Collection
|
||||
{
|
||||
return collect([
|
||||
'name' => $shipment->{"{$prefix}_name"},
|
||||
'company' => $shipment->{"{$prefix}_company"},
|
||||
'phone' => $shipment->{"{$prefix}_phone"},
|
||||
'email' => $shipment->{"{$prefix}_email"},
|
||||
'address' => $shipment->{"{$prefix}_address"},
|
||||
'city' => $shipment->{"{$prefix}_city"},
|
||||
'state' => $shipment->{"{$prefix}_state"},
|
||||
'zip' => $shipment->{"{$prefix}_zip"},
|
||||
'id_number' => $shipment->{"{$prefix}_id_number"},
|
||||
'name' => $shipment->{$prefix . '_name'},
|
||||
'company' => $shipment->{$prefix . '_company'},
|
||||
'phone' => $shipment->{$prefix . '_phone'},
|
||||
'email' => $shipment->{$prefix . '_email'},
|
||||
'address' => $shipment->{$prefix . '_address'},
|
||||
'city' => $shipment->{$prefix . '_city'},
|
||||
'zip' => $shipment->{$prefix . '_zip'},
|
||||
'id_number' => $shipment->{$prefix . '_id_number'},
|
||||
]);
|
||||
}
|
||||
|
||||
private function generateBarcode(string $code): string
|
||||
{
|
||||
try {
|
||||
$generator = new BarcodeGeneratorHTML();
|
||||
return $generator->getBarcode($code, $generator::TYPE_CODE_128, 2, 60);
|
||||
} catch (\Throwable $e) {
|
||||
return '<div style="color:#999;font-size:10px;">Barcode unavailable</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"barryvdh/laravel-dompdf": "*",
|
||||
"doctrine/dbal": "^4.4",
|
||||
"filament/filament": "3.3.*",
|
||||
"laravel/framework": "^11.0",
|
||||
"laravel/sanctum": "^4.0",
|
||||
|
||||
2
04_Laravel/composer.lock
generated
2
04_Laravel/composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "a22b2cc91f8cab3cca421c6ab381c544",
|
||||
"content-hash": "318d369da85cca6a3cf11f3c2f6a55de",
|
||||
"packages": [
|
||||
{
|
||||
"name": "anourvalar/eloquent-serialize",
|
||||
|
||||
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('shipment_items', function (Blueprint $table) {
|
||||
// اضافهکردن row_number اگر وجود نداره
|
||||
if (!Schema::hasColumn('shipment_items', 'row_number')) {
|
||||
$table->unsignedTinyInteger('row_number')->default(1)->after('shipment_id');
|
||||
}
|
||||
|
||||
// اضافهکردن unit_price اگر وجود نداره (متفاوت از unit_price_usd)
|
||||
if (!Schema::hasColumn('shipment_items', 'unit_price')) {
|
||||
$table->decimal('unit_price', 12, 2)->default(0)->after('quantity');
|
||||
}
|
||||
});
|
||||
|
||||
// کپی دادهها از unit_price_usd به unit_price اگر مقدار داره
|
||||
\DB::statement('UPDATE shipment_items SET unit_price = unit_price_usd WHERE unit_price_usd IS NOT NULL AND unit_price = 0');
|
||||
|
||||
// کپی دادهها از name به description اگر description خالیه
|
||||
\DB::statement('UPDATE shipment_items SET description = name WHERE (description IS NULL OR description = "") AND name IS NOT NULL');
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('shipment_items', function (Blueprint $table) {
|
||||
if (Schema::hasColumn('shipment_items', 'row_number')) {
|
||||
$table->dropColumn('row_number');
|
||||
}
|
||||
if (Schema::hasColumn('shipment_items', 'unit_price')) {
|
||||
$table->dropColumn('unit_price');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('shipment_items', function (Blueprint $table) {
|
||||
$table->string('name')->nullable()->change();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('shipment_items', function (Blueprint $table) {
|
||||
$table->string('name')->nullable(false)->change();
|
||||
});
|
||||
}
|
||||
};
|
||||
BIN
04_Laravel/public/barcodes/barcode_IFN-2026-92701.png
Normal file
BIN
04_Laravel/public/barcodes/barcode_IFN-2026-92701.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 220 B |
@ -1,200 +1,358 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: DejaVu Sans, sans-serif; font-size: 11px; direction: ltr; text-align: left; color: #333; }
|
||||
.container { width: 190mm; min-height: 277mm; padding: 10mm; margin: 0 auto; }
|
||||
body {
|
||||
font-family: DejaVu Sans, Arial, sans-serif;
|
||||
font-size: 10px;
|
||||
color: #000;
|
||||
background: #fff;
|
||||
}
|
||||
.page {
|
||||
width: 210mm;
|
||||
min-height: 297mm;
|
||||
padding: 8mm;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header { display: flex; align-items: center; margin-bottom: 12px; border-bottom: 2px solid #f37021; padding-bottom: 8px; }
|
||||
.logo { width: 70px; height: auto; }
|
||||
.title-area { flex: 1; text-align: center; }
|
||||
.title { font-size: 20px; font-weight: bold; color: #f37021; }
|
||||
.subtitle { font-size: 12px; color: #666; }
|
||||
.awb-number { font-size: 14px; font-weight: bold; color: #f37021; text-align: right; }
|
||||
/* ─── HEADER ─── */
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 2px solid #f37021;
|
||||
padding-bottom: 6px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.logo-area {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.logo {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
.brand {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #f37021;
|
||||
}
|
||||
.header-title {
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
}
|
||||
.header-title h1 {
|
||||
font-size: 18px;
|
||||
color: #000;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.header-tagline {
|
||||
font-size: 9px;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
.header-date {
|
||||
font-size: 10px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.section { border: 1px solid #ccc; padding: 8px; margin-bottom: 10px; }
|
||||
.section-title { font-weight: bold; color: #f37021; margin-bottom: 6px; font-size: 12px; border-bottom: 1px solid #eee; padding-bottom: 3px; }
|
||||
/* ─── AWB BARCODE ─── */
|
||||
.awb-barcode-area {
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
padding: 8px;
|
||||
border: 2px solid #000;
|
||||
}
|
||||
.awb-barcode-area img {
|
||||
height: 50px;
|
||||
}
|
||||
.awb-no-display {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.two-col { display: flex; gap: 10px; margin-bottom: 10px; }
|
||||
.col { flex: 1; }
|
||||
/* ─── MAIN SECTIONS ─── */
|
||||
.section-row {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.section {
|
||||
flex: 1;
|
||||
border: 1px solid #000;
|
||||
padding: 6px;
|
||||
}
|
||||
.section-title {
|
||||
background: #f37021;
|
||||
color: #fff;
|
||||
padding: 3px 6px;
|
||||
font-weight: bold;
|
||||
font-size: 11px;
|
||||
margin: -6px -6px 6px -6px;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
margin-bottom: 3px;
|
||||
font-size: 9px;
|
||||
}
|
||||
.label {
|
||||
width: 80px;
|
||||
font-weight: bold;
|
||||
color: #444;
|
||||
}
|
||||
.value {
|
||||
flex: 1;
|
||||
color: #000;
|
||||
}
|
||||
.from-to {
|
||||
font-weight: bold;
|
||||
font-size: 11px;
|
||||
color: #f37021;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.row { margin-bottom: 3px; }
|
||||
.label { font-weight: bold; color: #555; display: inline-block; width: 110px; }
|
||||
.value { display: inline-block; }
|
||||
/* ─── SHIPMENT & PAYMENT ─── */
|
||||
.shipment-row {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.shipment-section {
|
||||
flex: 1;
|
||||
border: 1px solid #000;
|
||||
padding: 6px;
|
||||
}
|
||||
.shipment-section.payment {
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.payment-section { border: 1px solid #ccc; padding: 8px; margin-bottom: 10px; background-color: #f9f9f9; }
|
||||
.payment-title { font-weight: bold; color: #f37021; margin-bottom: 6px; font-size: 12px; }
|
||||
/* ─── SHIPMENT DETAILS ─── */
|
||||
.shipment-details {
|
||||
border: 1px solid #000;
|
||||
padding: 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.shipment-details table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 9px;
|
||||
}
|
||||
.shipment-details th,
|
||||
.shipment-details td {
|
||||
border: 1px solid #ccc;
|
||||
padding: 3px;
|
||||
text-align: left;
|
||||
}
|
||||
.shipment-details th {
|
||||
background: #eee;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.total-row { font-weight: bold; color: #f37021; font-size: 12px; border-top: 1px solid #ddd; padding-top: 4px; margin-top: 4px; }
|
||||
/* ─── NOTES ─── */
|
||||
.notes {
|
||||
border: 1px solid #000;
|
||||
padding: 6px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 8px;
|
||||
line-height: 1.5;
|
||||
background: #fffde7;
|
||||
}
|
||||
.notes strong {
|
||||
color: #f37021;
|
||||
}
|
||||
|
||||
.disclaimer { background-color: #fafafa; border: 1px solid #eee; padding: 8px; margin-bottom: 10px; font-size: 8px; color: #666; line-height: 1.4; }
|
||||
/* ─── SIGNATURE ─── */
|
||||
.signature-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.signature-box {
|
||||
width: 45%;
|
||||
text-align: center;
|
||||
font-size: 9px;
|
||||
}
|
||||
.signature-line {
|
||||
border-top: 1px solid #000;
|
||||
margin-top: 25px;
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
.signature { margin-top: 15px; display: flex; justify-content: space-between; }
|
||||
.signature-box { width: 45%; border-top: 1px solid #333; padding-top: 5px; text-align: center; font-size: 10px; }
|
||||
/* ─── FOOTER ─── */
|
||||
.footer {
|
||||
border-top: 2px solid #f37021;
|
||||
padding-top: 5px;
|
||||
text-align: center;
|
||||
font-size: 9px;
|
||||
}
|
||||
.footer-brand {
|
||||
color: #f37021;
|
||||
font-weight: bold;
|
||||
}
|
||||
.track-url {
|
||||
font-size: 10px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.footer { margin-top: 10px; text-align: center; font-size: 10px; color: #999; border-top: 1px solid #ddd; padding-top: 8px; }
|
||||
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
td { vertical-align: top; padding: 0; }
|
||||
/* ─── UTILITY ─── */
|
||||
.total-row {
|
||||
background: #f37021;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
.total-row .label,
|
||||
.total-row .value {
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="page">
|
||||
<!-- HEADER -->
|
||||
<div class="header">
|
||||
<img src="{{ asset('logo.png') }}" class="logo" alt="IFNEX Logo">
|
||||
<div class="title-area">
|
||||
<div class="title">Shipment Waybill</div>
|
||||
<div class="subtitle">We Deliver Value</div>
|
||||
<div class="logo-area">
|
||||
<img src="{{ public_path('logo.png') }}" class="logo" alt="IFNEX">
|
||||
<div class="brand">IFNEx</div>
|
||||
</div>
|
||||
<div class="header-title">
|
||||
<h1>Shipment Waybill</h1>
|
||||
<div class="header-tagline">We Deliver Value</div>
|
||||
</div>
|
||||
<div class="header-date">
|
||||
Date: {{ $shipment->created_at?->format('m/d/Y') }}
|
||||
</div>
|
||||
<div class="awb-number">AWB No: {{ $shipment->awb_no }}</div>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="50%" style="padding-right: 5px;">
|
||||
<!-- AWB BARCODE -->
|
||||
<div class="awb-barcode-area">
|
||||
@if($barcode_base64)
|
||||
<img src="{{ $barcode_base64 }}" alt="barcode">
|
||||
@endif
|
||||
<div class="awb-no-display">{{ $shipment->awb_no }}</div>
|
||||
</div>
|
||||
|
||||
<!-- SHIPPER & RECEIVER -->
|
||||
<div class="section-row">
|
||||
<div class="section">
|
||||
<div class="section-title">SHIPPER</div>
|
||||
<div class="row"><span class="label">From:</span><span class="value">{{ $shipment->fromCountry?->name }}</span></div>
|
||||
<div class="row"><span class="label">Company Name:</span><span class="value">{{ $shipper['company'] }}</span></div>
|
||||
<div class="from-to">From: {{ $shipper['city'] }}, {{ $shipment->fromCountry?->name }} ({{ $shipment->fromCountry?->iso_code }})</div>
|
||||
<div class="row"><span class="label">Company Name:</span><span class="value">{{ $shipper['company'] ?: '—' }}</span></div>
|
||||
<div class="row"><span class="label">Contact Name:</span><span class="value">{{ $shipper['name'] }}</span></div>
|
||||
<div class="row"><span class="label">Tel / Mob:</span><span class="value">{{ $shipper['phone'] }}</span></div>
|
||||
<div class="row"><span class="label">Email:</span><span class="value">{{ $shipper['email'] }}</span></div>
|
||||
<div class="row"><span class="label">Email:</span><span class="value">{{ $shipper['email'] ?: '—' }}</span></div>
|
||||
<div class="row"><span class="label">Address:</span><span class="value">{{ $shipper['address'] }}</span></div>
|
||||
<div class="row"><span class="label">ID Number:</span><span class="value">{{ $shipper['id_number'] }}</span></div>
|
||||
<div class="row"><span class="label">Zip Code:</span><span class="value">{{ $shipper['zip'] }}</span></div>
|
||||
<div class="row"><span class="label">ID Number:</span><span class="value">{{ $shipper['id_number'] ?: '—' }}</span></div>
|
||||
<div class="row"><span class="label">Zip Code:</span><span class="value">{{ $shipper['zip'] ?: '—' }}</span></div>
|
||||
</div>
|
||||
</td>
|
||||
<td width="50%" style="padding-left: 5px;">
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">RECEIVER</div>
|
||||
<div class="row"><span class="label">To:</span><span class="value">{{ $shipment->toCountry?->name }}</span></div>
|
||||
<div class="row"><span class="label">Company Name:</span><span class="value">{{ $receiver['company'] }}</span></div>
|
||||
<div class="from-to">To: {{ $receiver['city'] }}, {{ $shipment->toCountry?->name }} ({{ $shipment->toCountry?->iso_code }})</div>
|
||||
<div class="row"><span class="label">Company Name:</span><span class="value">{{ $receiver['company'] ?: '—' }}</span></div>
|
||||
<div class="row"><span class="label">Contact Name:</span><span class="value">{{ $receiver['name'] }}</span></div>
|
||||
<div class="row"><span class="label">Tel / Mobile:</span><span class="value">{{ $receiver['phone'] }}</span></div>
|
||||
<div class="row"><span class="label">Email:</span><span class="value">{{ $receiver['email'] }}</span></div>
|
||||
<div class="row"><span class="label">Email:</span><span class="value">{{ $receiver['email'] ?: '—' }}</span></div>
|
||||
<div class="row"><span class="label">Address:</span><span class="value">{{ $receiver['address'] }}</span></div>
|
||||
<div class="row"><span class="label">City:</span><span class="value">{{ $receiver['city'] }}</span></div>
|
||||
<div class="row"><span class="label">Zip Code:</span><span class="value">{{ $receiver['zip'] }}</span></div>
|
||||
<div class="row"><span class="label">City:</span><span class="value">{{ $receiver['city'] ?: '—' }}</span></div>
|
||||
<div class="row"><span class="label">Zip Code:</span><span class="value">{{ $receiver['zip'] ?: '—' }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="50%" style="padding-right: 5px;">
|
||||
<div class="section">
|
||||
<!-- SHIPMENT & PAYMENT -->
|
||||
<div class="shipment-row">
|
||||
<div class="shipment-section">
|
||||
<div class="section-title">SHIPMENT</div>
|
||||
<div class="row"><span class="label">Gross Weight:</span><span class="value">{{ $shipment->weight }} Kg</span></div>
|
||||
<div class="row"><span class="label">Dimensions:</span><span class="value">{{ $shipment->dimensions }} cm</span></div>
|
||||
<div class="row"><span class="label">Volumetric Weight:</span><span class="value">{{ $shipment->volumetric_weight }} Kg</span></div>
|
||||
<div class="row"><span class="label">Chargeable Weight:</span><span class="value">{{ $shipment->chargeable_weight }} Kg</span></div>
|
||||
<div class="row"><span class="label">Value:</span><span class="value">${{ number_format($shipment->declared_value ?? 0, 2) }} USD</span></div>
|
||||
<div class="row"><span class="label">Service:</span><span class="value">{{ $shipment->type?->label() }}</span></div>
|
||||
<div class="row"><span class="label">Type:</span><span class="value">{{ $shipment->direction?->value === 'export' ? 'Export' : 'Import' }}</span></div>
|
||||
<div class="row"><span class="label">Content:</span><span class="value">{{ $shipment->content_description ?: 'General Goods' }}</span></div>
|
||||
<div class="row"><span class="label">Gross Weight:</span><span class="value">{{ $shipment->weight }} (Kg)</span></div>
|
||||
<div class="row"><span class="label">Dimensions:</span><span class="value">{{ $shipment->dimensions ?: '—' }} (cm)</span></div>
|
||||
<div class="row"><span class="label">Volumetric Weight:</span><span class="value">{{ $shipment->volumetric_weight }} (Kg)</span></div>
|
||||
<div class="row"><span class="label">Chargeable Weight:</span><span class="value">{{ $shipment->chargeable_weight ?: max($shipment->weight, $shipment->volumetric_weight) }} (Kg)</span></div>
|
||||
<div class="row"><span class="label">Value:</span><span class="value">{{ $invoice_total_usd > 0 ? number_format($invoice_total_usd, 0) . ' (USD)' : '—' }}</span></div>
|
||||
<div class="row"><span class="label">Service:</span><span class="value">{{ $service_label }}</span></div>
|
||||
<div class="row"><span class="label">Type:</span><span class="value">{{ $type_label }}</span></div>
|
||||
<div class="row"><span class="label">Content:</span><span class="value">{{ $shipment->content_description ?: $shipment->reason_for_export ?: 'General Goods' }}</span></div>
|
||||
</div>
|
||||
|
||||
@if($shipment->isParcel() && $items->count() > 0)
|
||||
<div style="margin-top: 8px; border-top: 1px solid #eee; padding-top: 6px;">
|
||||
<div class="section-title" style="margin-bottom: 4px;">Shipment Details</div>
|
||||
<table style="width: 100%; font-size: 10px; border-collapse: collapse;">
|
||||
<div class="shipment-section payment">
|
||||
<div class="section-title">PAYMENT</div>
|
||||
<div class="row"><span class="label">Shipping Price:</span><span class="value">{{ $shipment->shipping_price ? number_format($shipment->shipping_price, 2) : '—' }}</span></div>
|
||||
<div class="row"><span class="label">Extra Service:</span><span class="value">{{ number_format($shipment->extra_service ?? 0) }}</span></div>
|
||||
<div class="row"><span class="label">Domestic Pickup:</span><span class="value">{{ number_format($shipment->domestic_pickup ?? 0) }}</span></div>
|
||||
<div class="row"><span class="label">Packing Cost:</span><span class="value">{{ number_format($shipment->packing_cost ?? 0) }}</span></div>
|
||||
<div class="row"><span class="label">Domestic Delivery:</span><span class="value">{{ number_format($shipment->domestic_delivery ?? 0) }}</span></div>
|
||||
<div class="row"><span class="label">Warehousing:</span><span class="value">{{ number_format($shipment->warehousing_cost ?? 0) }}</span></div>
|
||||
<div class="row"><span class="label">Discount:</span><span class="value">{{ number_format($shipment->discount ?? 0) }}</span></div>
|
||||
<div class="row total-row"><span class="label">Total Fee:</span><span class="value">{{ number_format($shipment->total_fee ?? 0) }} IRR</span></div>
|
||||
<div class="row"><span class="label">Cash on Delivery:</span><span class="value">{{ $shipment->cod_amount ? number_format($shipment->cod_amount, 2) . ' AED' : '—' }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SHIPMENT DETAILS (Items) -->
|
||||
@if($items->isNotEmpty())
|
||||
<div class="shipment-details">
|
||||
<div class="section-title" style="background: #f37021; color: #fff; padding: 3px 6px; margin: -6px -6px 6px -6px;">Shipment Details</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr style="background-color: #f37021; color: #fff;">
|
||||
<th style="border: 1px solid #ddd; padding: 3px; text-align: left;">Row</th>
|
||||
<th style="border: 1px solid #ddd; padding: 3px; text-align: left;">Description</th>
|
||||
<th style="border: 1px solid #ddd; padding: 3px; text-align: left;">HS Code</th>
|
||||
<th style="border: 1px solid #ddd; padding: 3px; text-align: left;">Qty</th>
|
||||
<th style="border: 1px solid #ddd; padding: 3px; text-align: left;">Unit Price (USD)</th>
|
||||
<th style="border: 1px solid #ddd; padding: 3px; text-align: left;">Total (USD)</th>
|
||||
<tr>
|
||||
<th>No</th>
|
||||
<th>Description</th>
|
||||
<th>H.S. Code</th>
|
||||
<th>Qty</th>
|
||||
<th>Unit Price (USD)</th>
|
||||
<th>Total (USD)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($items as $item)
|
||||
<tr>
|
||||
<td style="border: 1px solid #ddd; padding: 3px;">{{ $item->row_number }}</td>
|
||||
<td style="border: 1px solid #ddd; padding: 3px;">{{ $item->description }}</td>
|
||||
<td style="border: 1px solid #ddd; padding: 3px;">{{ $item->hs_code }}</td>
|
||||
<td style="border: 1px solid #ddd; padding: 3px;">{{ $item->quantity }}</td>
|
||||
<td style="border: 1px solid #ddd; padding: 3px;">{{ number_format($item->unit_price, 2) }}</td>
|
||||
<td style="border: 1px solid #ddd; padding: 3px;">{{ number_format($item->total_usd, 2) }}</td>
|
||||
<td>{{ $item->row_number }}</td>
|
||||
<td>{{ $item->description }}</td>
|
||||
<td>{{ $item->hs_code }}</td>
|
||||
<td>{{ $item->quantity }}</td>
|
||||
<td>{{ number_format($item->unit_price, 2) }}</td>
|
||||
<td>{{ number_format($item->total_usd, 2) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
<td width="50%" style="padding-left: 5px;">
|
||||
<div class="payment-section">
|
||||
<div class="payment-title">PAYMENT</div>
|
||||
<div class="row"><span class="label">Shipping Price:</span><span class="value">{{ number_format($shipment->shipping_price ?? 0, 2) }} IRR</span></div>
|
||||
<div class="row"><span class="label">Extra Service:</span><span class="value">{{ number_format($shipment->extra_service ?? 0) }} IRR</span></div>
|
||||
<div class="row"><span class="label">Domestic Pickup:</span><span class="value">{{ number_format($shipment->domestic_pickup ?? 0) }} IRR</span></div>
|
||||
<div class="row"><span class="label">Packing Cost:</span><span class="value">{{ number_format($shipment->packing_cost ?? 0) }} IRR</span></div>
|
||||
<div class="row"><span class="label">Domestic Delivery:</span><span class="value">{{ number_format($shipment->domestic_delivery ?? 0) }} IRR</span></div>
|
||||
<div class="row"><span class="label">Warehousing Cost:</span><span class="value">{{ number_format($shipment->warehousing_cost ?? 0) }} IRR</span></div>
|
||||
<div class="row"><span class="label">Discount:</span><span class="value">{{ number_format($shipment->discount ?? 0) }} IRR</span></div>
|
||||
<div class="row total-row"><span class="label">Total Fee:</span><span class="value">{{ number_format($shipment->total_fee ?? 0) }} IRR</span></div>
|
||||
<div class="row total-row"><span class="label">Cash on Delivery:</span><span class="value">{{ number_format($shipment->cod_amount ?? 0, 2) }} AED</span></div>
|
||||
</div>
|
||||
</td>
|
||||
<tr style="background: #eee; font-weight: bold;">
|
||||
<td colspan="5" style="text-align: right;">Total Invoice Amount in USD:</td>
|
||||
<td>{{ number_format($invoice_total_usd, 2) }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="disclaimer">
|
||||
Please note that: Door-to-door Services is only applicable when the shipment is general cargo. For special cargo, dangerous goods, or items requiring special handling, additional charges may apply. The receiver is responsible for customs clearance and any associated fees. Claims must be filed within 30 days of delivery. Shipper declares that the contents are accurately described and properly packed for transport.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@if($shipment->relationLoaded('packages') && $shipment->packages->count() > 1)
|
||||
<div class="section">
|
||||
<div class="section-title">PACKAGES ({{ $shipment->packages->count() }})</div>
|
||||
<table style="width:100%; font-size:10px; border-collapse:collapse; margin-top:6px;">
|
||||
<thead>
|
||||
<tr style="background-color:#f37021; color:#fff;">
|
||||
<th style="border:1px solid #ddd; padding:3px;">#</th>
|
||||
<th style="border:1px solid #ddd; padding:3px;">Weight (kg)</th>
|
||||
<th style="border:1px solid #ddd; padding:3px;">Vol. Wt (kg)</th>
|
||||
<th style="border:1px solid #ddd; padding:3px;">Chg. Wt (kg)</th>
|
||||
<th style="border:1px solid #ddd; padding:3px;">Dimensions</th>
|
||||
<th style="border:1px solid #ddd; padding:3px;">Value (USD)</th>
|
||||
<th style="border:1px solid #ddd; padding:3px;">Content</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($shipment->packages as $pkg)
|
||||
<tr>
|
||||
<td style="border:1px solid #ddd; padding:3px; text-align:center;">{{ $pkg->package_no }}</td>
|
||||
<td style="border:1px solid #ddd; padding:3px;">{{ number_format($pkg->weight, 2) }}</td>
|
||||
<td style="border:1px solid #ddd; padding:3px;">{{ number_format($pkg->volumetric_weight, 2) }}</td>
|
||||
<td style="border:1px solid #ddd; padding:3px;">{{ number_format($pkg->chargeable_weight, 2) }}</td>
|
||||
<td style="border:1px solid #ddd; padding:3px;">{{ $pkg->dimensions }}</td>
|
||||
<td style="border:1px solid #ddd; padding:3px;">{{ number_format($pkg->declared_value, 2) }}</td>
|
||||
<td style="border:1px solid #ddd; padding:3px;">{{ $pkg->content_description }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="signature">
|
||||
<!-- NOTES -->
|
||||
<div class="notes">
|
||||
<strong>Please note that:</strong><br>
|
||||
Door-to-door Services is only applicable when the shipment is general cargo and does not require any special approvals or regulations in origin & destination.<br>
|
||||
Only Consignee is aware of the importing rules in the destination and consignee is the person who is in contact with the shipper.<br>
|
||||
The Courier Company cannot guarantee the clearance since All shipments are subjected to customs approval at Origin and Destination.
|
||||
</div>
|
||||
|
||||
<!-- SIGNATURE -->
|
||||
<div class="signature-row">
|
||||
<div class="signature-box">
|
||||
Shipper Name & Signature<br>
|
||||
Date: ....../....../........
|
||||
Shipper Name & Signature:
|
||||
<div class="signature-line"></div>
|
||||
</div>
|
||||
<div class="signature-box">
|
||||
Track Your Shipment at:<br>
|
||||
http://ifnex.net
|
||||
Date:
|
||||
<div class="signature-line">...../...../..........</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div class="footer">
|
||||
IFNEX Logistics — We Deliver Value | Generated on {{ now()->format('Y-m-d H:i') }}
|
||||
<div class="footer-brand">IFNEx Logistics — We Deliver Value</div>
|
||||
<div class="track-url">Track Your Shipment at: http://ifnex.net</div>
|
||||
<div style="margin-top: 3px; font-size: 8px; color: #999;">
|
||||
AWB: {{ $shipment->awb_no }} | Generated: {{ now()->format('Y-m-d H:i') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@ -1,164 +1,362 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: DejaVu Sans, sans-serif; font-size: 11px; direction: ltr; text-align: left; color: #333; }
|
||||
.container { width: 190mm; min-height: 277mm; padding: 10mm; margin: 0 auto; }
|
||||
body {
|
||||
font-family: DejaVu Sans, Arial, sans-serif;
|
||||
font-size: 10px;
|
||||
color: #000;
|
||||
background: #fff;
|
||||
}
|
||||
.page {
|
||||
width: 210mm;
|
||||
min-height: 297mm;
|
||||
padding: 10mm;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header { text-align: center; margin-bottom: 12px; border-bottom: 2px solid #f37021; padding-bottom: 8px; position: relative; }
|
||||
.header h1 { color: #f37021; font-size: 24px; font-weight: bold; }
|
||||
.header .meta { display: flex; justify-content: space-between; font-size: 10px; color: #666; margin-top: 5px; }
|
||||
/* ─── HEADER ─── */
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 12px;
|
||||
border-bottom: 2px solid #f37021;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
.header h1 {
|
||||
font-size: 22px;
|
||||
color: #f37021;
|
||||
margin-bottom: 4px;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
.header-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 10px;
|
||||
color: #666;
|
||||
}
|
||||
.header-meta .awb-box {
|
||||
background: #f9f9f9;
|
||||
padding: 2px 8px;
|
||||
border: 1px dashed #f37021;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.section { border: 1px solid #ccc; padding: 8px; margin-bottom: 10px; }
|
||||
.section-title { font-weight: bold; color: #f37021; margin-bottom: 6px; font-size: 12px; border-bottom: 1px solid #eee; padding-bottom: 3px; }
|
||||
/* ─── SECTIONS ─── */
|
||||
.two-col {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.section {
|
||||
flex: 1;
|
||||
border: 1px solid #000;
|
||||
padding: 6px;
|
||||
}
|
||||
.section-title {
|
||||
background: #f37021;
|
||||
color: #fff;
|
||||
padding: 3px 6px;
|
||||
font-weight: bold;
|
||||
font-size: 11px;
|
||||
margin: -6px -6px 6px -6px;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
margin-bottom: 3px;
|
||||
font-size: 9px;
|
||||
}
|
||||
.label {
|
||||
width: 110px;
|
||||
font-weight: bold;
|
||||
color: #444;
|
||||
}
|
||||
.value {
|
||||
flex: 1;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.two-col { display: flex; gap: 10px; margin-bottom: 10px; }
|
||||
.col { flex: 1; }
|
||||
/* ─── CONTENT ─── */
|
||||
.content-section {
|
||||
border: 1px solid #000;
|
||||
padding: 6px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.content-section .section-title {
|
||||
background: #f37021;
|
||||
color: #fff;
|
||||
}
|
||||
.content-desc {
|
||||
font-size: 10px;
|
||||
margin-bottom: 6px;
|
||||
padding: 4px;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.row { margin-bottom: 3px; }
|
||||
.label { font-weight: bold; color: #555; display: inline-block; width: 110px; }
|
||||
.value { display: inline-block; }
|
||||
/* ─── ITEMS TABLE ─── */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 9px;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #ccc;
|
||||
padding: 4px;
|
||||
text-align: left;
|
||||
}
|
||||
th {
|
||||
background: #f37021;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
td.text-center { text-align: center; }
|
||||
td.text-right { text-align: right; }
|
||||
.total-row {
|
||||
background: #f9f9f9;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
|
||||
th, td { border: 1px solid #ddd; padding: 5px; text-align: left; font-size: 10px; }
|
||||
th { background-color: #f5f5f5; font-weight: bold; color: #333; }
|
||||
/* ─── WEIGHT & DIMENSION ─── */
|
||||
.weight-section {
|
||||
border: 1px solid #000;
|
||||
padding: 6px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.weight-box {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 6px;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
.weight-label {
|
||||
font-size: 8px;
|
||||
color: #666;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.weight-value {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #f37021;
|
||||
}
|
||||
|
||||
.content-box { border: 1px solid #ccc; padding: 8px; margin-bottom: 10px; }
|
||||
.content-title { font-weight: bold; color: #f37021; margin-bottom: 6px; font-size: 12px; }
|
||||
/* ─── REASON FOR EXPORT ─── */
|
||||
.reason-section {
|
||||
border: 1px solid #000;
|
||||
padding: 6px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.reason-section .section-title {
|
||||
background: #f37021;
|
||||
color: #fff;
|
||||
}
|
||||
.reason-text {
|
||||
font-size: 10px;
|
||||
font-style: italic;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.info-row { display: flex; justify-content: space-between; margin-bottom: 10px; }
|
||||
.info-box { flex: 1; border: 1px solid #ccc; padding: 8px; }
|
||||
/* ─── DECLARATION ─── */
|
||||
.declaration {
|
||||
border: 1px solid #000;
|
||||
padding: 8px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 9px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.declaration strong {
|
||||
color: #f37021;
|
||||
}
|
||||
.signature-area {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.signature-box {
|
||||
width: 45%;
|
||||
text-align: center;
|
||||
font-size: 9px;
|
||||
}
|
||||
.signature-line {
|
||||
border-top: 1px solid #000;
|
||||
margin-top: 25px;
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
.declaration { border: 1px solid #ddd; padding: 10px; margin-bottom: 10px; font-size: 10px; color: #555; text-align: center; line-height: 1.5; }
|
||||
.signature-line { margin-top: 15px; display: flex; justify-content: space-between; }
|
||||
.signature-box { width: 45%; border-top: 1px solid #333; padding-top: 5px; text-align: center; font-size: 10px; }
|
||||
/* ─── BOTTOM BARCODE ─── */
|
||||
.bottom-barcode {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
padding: 6px;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
.bottom-barcode img {
|
||||
height: 35px;
|
||||
}
|
||||
.bottom-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 9px;
|
||||
color: #666;
|
||||
margin-top: 6px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.label-section { border: 2px solid #000; padding: 10px; margin-bottom: 10px; }
|
||||
.label-title { font-weight: bold; font-size: 12px; margin-bottom: 6px; }
|
||||
.label-row { display: flex; justify-content: space-between; margin-bottom: 5px; }
|
||||
.barcode { text-align: center; margin-top: 8px; }
|
||||
|
||||
.footer { margin-top: 10px; text-align: center; font-size: 10px; color: #999; border-top: 1px solid #ddd; padding-top: 8px; }
|
||||
|
||||
.total-row { font-weight: bold; color: #f37021; font-size: 12px; border-top: 1px solid #ddd; padding-top: 4px; margin-top: 4px; text-align: right; }
|
||||
/* ─── FOOTER ─── */
|
||||
.footer {
|
||||
margin-top: 10px;
|
||||
border-top: 2px solid #f37021;
|
||||
padding-top: 5px;
|
||||
text-align: center;
|
||||
font-size: 9px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="page">
|
||||
<!-- HEADER -->
|
||||
<div class="header">
|
||||
<h1>INVOICE</h1>
|
||||
<div class="meta">
|
||||
<span>DATE: {{ $shipment->created_at?->format('Y-m-d') }}</span>
|
||||
<span>INVOICE NO: {{ $shipment->awb_no }}</span>
|
||||
<div class="header-meta">
|
||||
<span>DATE: {{ $shipment->created_at?->format('m/d/Y') }}</span>
|
||||
<span class="awb-box">INVOICE NO: {{ $shipment->awb_no }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SHIPPER & CONSIGNEE -->
|
||||
<div class="two-col">
|
||||
<div class="col section">
|
||||
<div class="section-title">SHIPPER</div>
|
||||
<div class="row"><span class="label">Name:</span><span class="value">{{ $shipper['name'] }}</span></div>
|
||||
<div class="row"><span class="label">Company Name:</span><span class="value">{{ $shipper['company'] }}</span></div>
|
||||
<div class="row"><span class="label">Contact Person:</span><span class="value">{{ $shipper['name'] }}</span></div>
|
||||
<div class="row"><span class="label">Address:</span><span class="value">{{ $shipper['address'] }}</span></div>
|
||||
<div class="row"><span class="label">Phone:</span><span class="value">{{ $shipper['phone'] }}</span></div>
|
||||
<div class="row"><span class="label">Email:</span><span class="value">{{ $shipper['email'] }}</span></div>
|
||||
</div>
|
||||
|
||||
<div class="col section">
|
||||
<div class="section-title">CONSIGNEE</div>
|
||||
<div class="row"><span class="label">Name:</span><span class="value">{{ $receiver['name'] }}</span></div>
|
||||
<div class="row"><span class="label">Company Name:</span><span class="value">{{ $receiver['company'] }}</span></div>
|
||||
<div class="row"><span class="label">Contact Person:</span><span class="value">{{ $receiver['name'] }}</span></div>
|
||||
<div class="row"><span class="label">Address:</span><span class="value">{{ $receiver['address'] }}</span></div>
|
||||
<div class="row"><span class="label">Zip Code & City:</span><span class="value">{{ $receiver['zip'] }} {{ $receiver['city'] }}</span></div>
|
||||
<div class="row"><span class="label">Phone:</span><span class="value">{{ $receiver['phone'] }}</span></div>
|
||||
<div class="row"><span class="label">Email:</span><span class="value">{{ $receiver['email'] }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-box">
|
||||
<div class="content-title">CONTENT</div>
|
||||
<p>{{ $shipment->content_description ?: 'General Goods' }}</p>
|
||||
</div>
|
||||
|
||||
@if($shipment->isParcel() && $items->count() > 0)
|
||||
<div class="section">
|
||||
<div class="section-title">SHIPMENT ITEMS</div>
|
||||
<div class="section-title">SHIPPER</div>
|
||||
<div class="row"><span class="label">SHIPPER:</span><span class="value">{{ $shipper['city'] }}, {{ $shipment->fromCountry?->name }} ({{ $shipment->fromCountry?->iso_code }})</span></div>
|
||||
<div class="row"><span class="label">COMPANY NAME:</span><span class="value">{{ $shipper['company'] ?: '—' }}</span></div>
|
||||
<div class="row"><span class="label">CONTACT PERSON:</span><span class="value">{{ $shipper['name'] }}</span></div>
|
||||
<div class="row"><span class="label">ADDRESS:</span><span class="value">{{ $shipper['address'] }}</span></div>
|
||||
<div class="row"><span class="label">PHONE:</span><span class="value">{{ $shipper['phone'] }}</span></div>
|
||||
<div class="row"><span class="label">EMAIL:</span><span class="value">{{ $shipper['email'] ?: '—' }}</span></div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">CONSIGNEE</div>
|
||||
<div class="row"><span class="label">CONSIGNEE:</span><span class="value">{{ $receiver['city'] }}, {{ $shipment->toCountry?->name }} ({{ $shipment->toCountry?->iso_code }})</span></div>
|
||||
<div class="row"><span class="label">COMPANY NAME:</span><span class="value">{{ $receiver['company'] ?: '—' }}</span></div>
|
||||
<div class="row"><span class="label">CONTACT PERSON:</span><span class="value">{{ $receiver['name'] }}</span></div>
|
||||
<div class="row"><span class="label">ADDRESS:</span><span class="value">{{ $receiver['address'] }}</span></div>
|
||||
<div class="row"><span class="label">Zip Code & City:</span><span class="value">{{ $receiver['zip'] }} {{ $receiver['city'] }}</span></div>
|
||||
<div class="row"><span class="label">PHONE:</span><span class="value">{{ $receiver['phone'] }}</span></div>
|
||||
<div class="row"><span class="label">EMAIL:</span><span class="value">{{ $receiver['email'] ?: '—' }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CONTENT -->
|
||||
<div class="content-section">
|
||||
<div class="section-title">Content</div>
|
||||
<div class="content-desc">{{ $shipment->content_description ?: 'General Goods' }}</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 40px;">NO</th>
|
||||
<th style="width: 30px">NO</th>
|
||||
<th>DESCRIPTION</th>
|
||||
<th style="width: 100px;">H.S. CODE</th>
|
||||
<th style="width: 70px;">QUANTITY</th>
|
||||
<th style="width: 90px;">UNIT PRICE (USD)</th>
|
||||
<th style="width: 100px;">TOTAL IN USD</th>
|
||||
<th style="width: 90px">H.S. CODE</th>
|
||||
<th style="width: 60px">QUANTITY</th>
|
||||
<th style="width: 80px">UNIT PRICE</th>
|
||||
<th style="width: 90px">TOTAL IN USD</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($items as $item)
|
||||
@forelse($items as $item)
|
||||
<tr>
|
||||
<td style="text-align: center;">{{ $item->row_number }}</td>
|
||||
<td class="text-center">{{ $item->row_number }}</td>
|
||||
<td>{{ $item->description }}</td>
|
||||
<td style="text-align: center;">{{ $item->hs_code }}</td>
|
||||
<td style="text-align: center;">{{ $item->quantity }}</td>
|
||||
<td style="text-align: center;">{{ number_format($item->unit_price, 2) }}</td>
|
||||
<td style="text-align: center;">{{ number_format($item->total_usd, 2) }}</td>
|
||||
<td class="text-center">{{ $item->hs_code }}</td>
|
||||
<td class="text-center">{{ $item->quantity }}</td>
|
||||
<td class="text-right">{{ number_format($item->unit_price, 2) }}</td>
|
||||
<td class="text-right">{{ number_format($item->total_usd, 2) }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="6" class="text-center">No items</td></tr>
|
||||
@endforelse
|
||||
|
||||
{{-- Fill empty rows to match reference (9 rows) --}}
|
||||
@for($i = $items->count() + 1; $i <= 9; $i++)
|
||||
<tr>
|
||||
<td class="text-center">{{ $i }}</td>
|
||||
<td> </td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
@endfor
|
||||
|
||||
<tr class="total-row">
|
||||
<td colspan="5" class="text-right">TOTAL INVOICE AMOUNT IN USD</td>
|
||||
<td class="text-right">{{ number_format($invoice_total_usd, 2) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="total-row">
|
||||
TOTAL INVOICE AMOUNT IN USD: {{ number_format($invoice_total_usd, 2) }}
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="info-row">
|
||||
<div class="info-box">
|
||||
<div class="row"><span class="label">WEIGHT:</span><span class="value">{{ $shipment->weight }} KG</span></div>
|
||||
<div class="row"><span class="label">DIMENSION:</span><span class="value">{{ $shipment->dimensions }} CM</span></div>
|
||||
<!-- WEIGHT & DIMENSION -->
|
||||
<div class="weight-section">
|
||||
<div class="weight-box">
|
||||
<div class="weight-label">Gross Weight</div>
|
||||
<div class="weight-value">{{ $shipment->weight }} KG</div>
|
||||
</div>
|
||||
<div class="info-box">
|
||||
<div class="row"><span class="label">REASON FOR EXPORT:</span><span class="value">{{ $shipment->reason_for_export ?: 'N/A' }}</span></div>
|
||||
<div class="weight-box">
|
||||
<div class="weight-label">Volumetric Weight</div>
|
||||
<div class="weight-value">{{ $shipment->volumetric_weight }} KG</div>
|
||||
</div>
|
||||
<div class="weight-box">
|
||||
<div class="weight-label">W * L * H (cm)</div>
|
||||
<div class="weight-value">{{ $shipment->dimensions ?: '—' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- REASON FOR EXPORT -->
|
||||
<div class="reason-section">
|
||||
<div class="section-title">REASON FOR EXPORT</div>
|
||||
<div class="reason-text">
|
||||
{{ strtoupper($shipment->reason_for_export ?: 'ITEM BEING SENT AS A SAMPLE, NOT FOR SALE') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- DECLARATION -->
|
||||
<div class="declaration">
|
||||
I HEREBY STATE THAT THE ABOVE INFORMATION IS TRUE AND I, THE UNDERSIGNED, AM LIABLE FOR ANY CONSEQUENCES THAT MAY ARISE DUE TO NON-DISCLOSURE OF FACTS
|
||||
</div>
|
||||
|
||||
<div class="signature-line">
|
||||
<strong>I HEREBY STATE THAT THE ABOVE INFORMATION IS TRUE AND CORRECT TO THE BEST OF MY KNOWLEDGE AND I, THE UNDERSIGNED, AM LIABLE FOR ANY CONSEQUENCES THAT MAY ARISE DUE TO NON-DISCLOSURE OF FACTS.</strong>
|
||||
<div class="signature-area">
|
||||
<div class="signature-box">
|
||||
NAME - SIGNATURE
|
||||
<div class="signature-line"></div>
|
||||
</div>
|
||||
<div class="signature-box">
|
||||
NAME - SIGNATURE
|
||||
DATE
|
||||
<div class="signature-line">...../...../..........</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="label-section">
|
||||
<div class="label-title">LABEL</div>
|
||||
<div class="label-row">
|
||||
<span><strong>AWB:</strong> {{ $shipment->awb_no }}</span>
|
||||
<span><strong>Weight:</strong> {{ $shipment->weight }} KG</span>
|
||||
</div>
|
||||
<div class="label-row">
|
||||
<span><strong>Gross Weight:</strong> {{ $shipment->weight }} KG</span>
|
||||
<span><strong>Volumetric Weight:</strong> {{ $shipment->volumetric_weight }} KG</span>
|
||||
</div>
|
||||
<div class="label-row">
|
||||
<span><strong>W*L*H:</strong> {{ $shipment->dimensions }}</span>
|
||||
</div>
|
||||
<div class="barcode">
|
||||
{!! $barcode !!}
|
||||
<!-- BOTTOM BARCODE & INFO -->
|
||||
<div class="bottom-barcode">
|
||||
@if($barcode_base64)
|
||||
<img src="{{ $barcode_base64 }}" alt="barcode">
|
||||
@endif
|
||||
<div class="bottom-info">
|
||||
<span><strong>{{ $shipment->awb_no }}</strong></span>
|
||||
<span>{{ $shipment->weight }} KG</span>
|
||||
<span>{{ $shipment->created_at?->format('m/d/Y') }}</span>
|
||||
<span>{{ $shipment->fromCountry?->name }} → {{ $shipment->toCountry?->name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div class="footer">
|
||||
IFNEX Logistics — We Deliver Value | Generated on {{ now()->format('Y-m-d H:i') }}
|
||||
IFNEX Logistics — We Deliver Value | AWB: {{ $shipment->awb_no }} | Generated: {{ now()->format('Y-m-d H:i') }}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@ -1,103 +1,248 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: DejaVu Sans, sans-serif; font-size: 12px; direction: ltr; text-align: left; color: #333; }
|
||||
.page { width: 190mm; min-height: 277mm; padding: 10mm; margin: 0 auto; }
|
||||
|
||||
.label-box {
|
||||
border: 3px solid #000;
|
||||
padding: 15px;
|
||||
body {
|
||||
font-family: DejaVu Sans, Arial, sans-serif;
|
||||
color: #000;
|
||||
background: #fff;
|
||||
}
|
||||
/* A5 افقی: 210x148 mm — استفاده از min-height نه height */
|
||||
.label-page {
|
||||
width: 200mm;
|
||||
padding: 4mm;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header { display: flex; align-items: center; border-bottom: 2px solid #f37021; padding-bottom: 10px; margin-bottom: 15px; }
|
||||
.logo { width: 70px; height: auto; }
|
||||
.company-name { font-size: 18px; font-weight: bold; color: #f37021; margin-left: 10px; }
|
||||
|
||||
.awb-badge {
|
||||
background-color: #f37021;
|
||||
color: #fff;
|
||||
font-size: 22px;
|
||||
/* ─── HEADER ─── */
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 3px solid #f37021;
|
||||
padding-bottom: 2mm;
|
||||
margin-bottom: 3mm;
|
||||
}
|
||||
.brand-name {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
margin-bottom: 15px;
|
||||
letter-spacing: 3px;
|
||||
color: #f37021;
|
||||
}
|
||||
.brand-tagline {
|
||||
font-size: 8px;
|
||||
color: #666;
|
||||
}
|
||||
.header-date {
|
||||
font-size: 10px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.barcode { text-align: center; margin-bottom: 15px; }
|
||||
/* ─── MAIN: 2 ستون ─── */
|
||||
.main {
|
||||
display: flex;
|
||||
gap: 4mm;
|
||||
}
|
||||
|
||||
.grid { display: flex; gap: 15px; margin-bottom: 15px; }
|
||||
.col { flex: 1; }
|
||||
/* ─── ستون چپ: بارکد ─── */
|
||||
.barcode-col {
|
||||
width: 45%;
|
||||
border: 2px solid #000;
|
||||
padding: 3mm;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.barcode-col img {
|
||||
max-width: 100%;
|
||||
height: 25mm;
|
||||
width: auto;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.awb-label {
|
||||
font-size: 8px;
|
||||
color: #666;
|
||||
margin-top: 2mm;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.awb-display {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
letter-spacing: 2px;
|
||||
margin-top: 1mm;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
.section-title { font-weight: bold; color: #f37021; font-size: 12px; margin-bottom: 8px; text-transform: uppercase; border-bottom: 1px solid #eee; padding-bottom: 3px; }
|
||||
/* ─── ستون راست: اطلاعات ─── */
|
||||
.info-col {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2mm;
|
||||
}
|
||||
|
||||
.row { margin-bottom: 4px; }
|
||||
.label { font-weight: bold; color: #555; display: inline-block; width: 100px; }
|
||||
.value { display: inline-block; }
|
||||
/* ─── مسیر کشورها ─── */
|
||||
.route {
|
||||
border: 2px solid #000;
|
||||
padding: 2mm;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
.country-box {
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
}
|
||||
.country-label {
|
||||
font-size: 7px;
|
||||
color: #666;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.country-name {
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.country-iso {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #f37021;
|
||||
}
|
||||
.arrow {
|
||||
font-size: 20px;
|
||||
color: #f37021;
|
||||
padding: 0 2mm;
|
||||
}
|
||||
|
||||
.shipment-info { background-color: #f9f9f9; padding: 10px; border: 1px solid #ddd; margin-bottom: 15px; }
|
||||
.shipment-grid { display: flex; gap: 15px; }
|
||||
.shipment-item { flex: 1; }
|
||||
/* ─── جدول وزن ─── */
|
||||
.weights {
|
||||
display: flex;
|
||||
border: 2px solid #000;
|
||||
}
|
||||
.weight-cell {
|
||||
flex: 1;
|
||||
border-right: 1px solid #000;
|
||||
padding: 2mm;
|
||||
text-align: center;
|
||||
}
|
||||
.weight-cell:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
.weight-label {
|
||||
font-size: 7px;
|
||||
color: #666;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.weight-value {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin-top: 1mm;
|
||||
}
|
||||
|
||||
.footer { text-align: center; font-size: 10px; color: #666; border-top: 1px solid #ddd; padding-top: 10px; }
|
||||
/* ─── ZIP ─── */
|
||||
.zip {
|
||||
border: 2px solid #000;
|
||||
padding: 2mm;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: #fffde7;
|
||||
}
|
||||
.zip-label {
|
||||
font-size: 8px;
|
||||
color: #666;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.zip-value {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
font-family: 'Courier New', monospace;
|
||||
letter-spacing: 2px;
|
||||
}
|
||||
|
||||
/* ─── FOOTER ─── */
|
||||
.footer {
|
||||
border-top: 2px solid #f37021;
|
||||
padding-top: 2mm;
|
||||
margin-top: 3mm;
|
||||
font-size: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: #666;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="label-box">
|
||||
<div class="label-page">
|
||||
<!-- HEADER -->
|
||||
<div class="header">
|
||||
<img src="{{ asset('logo.png') }}" class="logo" alt="IFNEX Logo">
|
||||
<div class="company-name">IFNEX LOGISTICS</div>
|
||||
<div>
|
||||
<div class="brand-name">IFNEx LOGISTICS</div>
|
||||
<div class="brand-tagline">We Deliver Value</div>
|
||||
</div>
|
||||
<div class="header-date">Date: {{ $shipment->created_at?->format('m/d/Y') }}</div>
|
||||
</div>
|
||||
|
||||
<div class="awb-badge">
|
||||
{{ $shipment->awb_no }}
|
||||
<!-- MAIN -->
|
||||
<div class="main">
|
||||
<!-- BARCODE -->
|
||||
<div class="barcode-col">
|
||||
@if($barcode_base64)
|
||||
<img src="{{ $barcode_base64 }}" alt="barcode">
|
||||
@endif
|
||||
<div class="awb-label">AWB Number</div>
|
||||
<div class="awb-display">{{ $shipment->awb_no }}</div>
|
||||
</div>
|
||||
|
||||
<div class="barcode">
|
||||
{!! $barcode !!}
|
||||
<!-- INFO -->
|
||||
<div class="info-col">
|
||||
<!-- ROUTE -->
|
||||
<div class="route">
|
||||
<div class="country-box">
|
||||
<div class="country-label">ORIGIN</div>
|
||||
<div class="country-name">{{ $shipment->fromCountry?->name ?? '—' }}</div>
|
||||
<div class="country-iso">{{ $origin_iso }}</div>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="col">
|
||||
<div class="section-title">SHIPPER</div>
|
||||
<div class="row"><span class="label">Name:</span><span class="value">{{ $shipper['name'] }}</span></div>
|
||||
<div class="row"><span class="label">Company:</span><span class="value">{{ $shipper['company'] }}</span></div>
|
||||
<div class="row"><span class="label">Phone:</span><span class="value">{{ $shipper['phone'] }}</span></div>
|
||||
<div class="row"><span class="label">Address:</span><span class="value">{{ $shipper['address'] }}</span></div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="section-title">RECEIVER</div>
|
||||
<div class="row"><span class="label">Name:</span><span class="value">{{ $receiver['name'] }}</span></div>
|
||||
<div class="row"><span class="label">Company:</span><span class="value">{{ $receiver['company'] }}</span></div>
|
||||
<div class="row"><span class="label">Phone:</span><span class="value">{{ $receiver['phone'] }}</span></div>
|
||||
<div class="row"><span class="label">Address:</span><span class="value">{{ $receiver['address'] }}</span></div>
|
||||
<div class="arrow">→</div>
|
||||
<div class="country-box">
|
||||
<div class="country-label">DESTINATION</div>
|
||||
<div class="country-name">{{ $shipment->toCountry?->name ?? '—' }}</div>
|
||||
<div class="country-iso">{{ $dest_iso }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="shipment-info">
|
||||
<div class="section-title">SHIPMENT DETAILS</div>
|
||||
<div class="shipment-grid">
|
||||
<div class="shipment-item">
|
||||
<div class="row"><span class="label">Gross Weight:</span><span class="value">{{ $shipment->weight }} KG</span></div>
|
||||
<div class="row"><span class="label">Volumetric Weight:</span><span class="value">{{ $shipment->volumetric_weight }} KG</span></div>
|
||||
<div class="row"><span class="label">Dimensions:</span><span class="value">{{ $shipment->dimensions }} CM</span></div>
|
||||
<!-- WEIGHTS -->
|
||||
<div class="weights">
|
||||
<div class="weight-cell">
|
||||
<div class="weight-label">Gross Weight</div>
|
||||
<div class="weight-value">{{ $shipment->weight }} kg</div>
|
||||
</div>
|
||||
<div class="shipment-item">
|
||||
<div class="row"><span class="label">From:</span><span class="value">{{ $shipment->fromCountry?->name }}</span></div>
|
||||
<div class="row"><span class="label">To:</span><span class="value">{{ $shipment->toCountry?->name }}</span></div>
|
||||
<div class="row"><span class="label">Service:</span><span class="value">{{ $shipment->type?->label() }}</span></div>
|
||||
<div class="weight-cell">
|
||||
<div class="weight-label">Volumetric</div>
|
||||
<div class="weight-value">{{ $shipment->volumetric_weight }} kg</div>
|
||||
</div>
|
||||
<div class="weight-cell">
|
||||
<div class="weight-label">W*L*H</div>
|
||||
<div class="weight-value">{{ $shipment->dimensions ?: '—' }} cm</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ZIP -->
|
||||
<div class="zip">
|
||||
<span class="zip-label">DEST ZIP</span>
|
||||
<span class="zip-value">{{ $shipment->receiver_zip ?: '—' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div class="footer">
|
||||
IFNEX Logistics — We Deliver Value
|
||||
</div>
|
||||
<span>IFNEx Logistics — We Deliver Value</span>
|
||||
<span><strong>{{ $shipment->awb_no }}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
157
04_Laravel/scripts/test_pdf_generation.php
Normal file
157
04_Laravel/scripts/test_pdf_generation.php
Normal file
@ -0,0 +1,157 @@
|
||||
<?php
|
||||
/**
|
||||
* اسکریپت تست تولید PDF برای IFNEX
|
||||
*
|
||||
* این اسکریپت یه محموله نمونه میسازه و سه تا PDF (AWB, Invoice, Label) تولید میکنه
|
||||
* و اونها رو توی پوشه storage/app/pdf-test/ ذخیره میکنه.
|
||||
*
|
||||
* استفاده:
|
||||
* php test_pdf_generation.php
|
||||
*
|
||||
* پس از اجرا، فایلهای تولیدشده رو بررسی کنید:
|
||||
* storage/app/pdf-test/AWB-test.pdf
|
||||
* storage/app/pdf-test/INVOICE-test.pdf
|
||||
* storage/app/pdf-test/LABEL-test.pdf
|
||||
*/
|
||||
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
$app = require_once __DIR__ . '/bootstrap/app.php';
|
||||
$app->make('Illuminate\Contracts\Console\Kernel')->bootstrap();
|
||||
|
||||
use App\Models\Shipment;
|
||||
use App\Models\ShipmentItem;
|
||||
use App\Models\Country;
|
||||
use App\Services\PdfService;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
echo "═══════════════════════════════════════════\n";
|
||||
echo "🧪 IFNEX PDF Generation Test\n";
|
||||
echo "═══════════════════════════════════════════\n\n";
|
||||
|
||||
// ─── ۱. بررسی نصب بودن کتابخانه بارکد ───
|
||||
echo "1️⃣ Checking barcode library...\n";
|
||||
if (class_exists(\Picqer\Barcode\BarcodeGeneratorPNG::class)) {
|
||||
echo " ✅ picqer/php-barcode-generator is installed\n\n";
|
||||
} else {
|
||||
echo " ❌ picqer/php-barcode-generator is NOT installed\n";
|
||||
echo " Run: composer require picqer/php-barcode-generator\n\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// ─── ۱.۵. تست تولید بارکد ───
|
||||
echo "1.5️⃣ Testing barcode generation...\n";
|
||||
try {
|
||||
$generator = new \Picqer\Barcode\BarcodeGeneratorPNG();
|
||||
$barcode = $generator->getBarcode('TEST123', $generator::TYPE_CODE_128, 3, 80);
|
||||
$b64 = 'data:image/png;base64,' . base64_encode($barcode);
|
||||
echo " ✅ Barcode generated, length: " . strlen($b64) . " chars\n";
|
||||
echo " Preview: " . substr($b64, 0, 50) . "...\n\n";
|
||||
} catch (\Throwable $e) {
|
||||
echo " ❌ Barcode generation failed: " . $e->getMessage() . "\n\n";
|
||||
}
|
||||
|
||||
// ─── ۲. ایجاد پوشه تست ───
|
||||
$testDir = storage_path('app/pdf-test');
|
||||
if (!File::exists($testDir)) {
|
||||
File::makeDirectory($testDir, 0755, true);
|
||||
echo "2️⃣ Created test directory: {$testDir}\n\n";
|
||||
} else {
|
||||
echo "2️⃣ Test directory exists: {$testDir}\n\n";
|
||||
}
|
||||
|
||||
// ─── ۳. پیدا کردن یه محموله نمونه ───
|
||||
echo "3️⃣ Finding sample shipment...\n";
|
||||
$shipment = Shipment::with(['fromCountry', 'toCountry', 'items'])->latest()->first();
|
||||
|
||||
if (!$shipment) {
|
||||
echo " ❌ No shipment found in database. Please create one first.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo " ✅ Found shipment: {$shipment->awb_no}\n";
|
||||
echo " - Type: {$shipment->type?->value}\n";
|
||||
echo " - Direction: {$shipment->direction?->value}\n";
|
||||
echo " - From: {$shipment->fromCountry?->name}\n";
|
||||
echo " - To: {$shipment->toCountry?->name}\n";
|
||||
echo " - Items: {$shipment->items->count()}\n\n";
|
||||
|
||||
// ─── ۴. اگر آیتم نداره، یه آیتم تستی اضافه کن ───
|
||||
if ($shipment->items->isEmpty()) {
|
||||
echo " ⚠️ Shipment has no items. Adding test item...\n";
|
||||
ShipmentItem::create([
|
||||
'shipment_id' => $shipment->id,
|
||||
'row_number' => 1,
|
||||
'description' => 'Electronics PCB Board',
|
||||
'hs_code' => '8542390001',
|
||||
'quantity' => 104,
|
||||
'unit_price' => 1.10,
|
||||
'total_usd' => 114.40,
|
||||
]);
|
||||
$shipment->load('items');
|
||||
echo " ✅ Added test item\n\n";
|
||||
}
|
||||
|
||||
// ─── ۵. تولید AWB PDF ───
|
||||
echo "4️⃣ Generating AWB PDF...\n";
|
||||
try {
|
||||
$pdfService = app(PdfService::class);
|
||||
$awbContent = $pdfService->awb($shipment);
|
||||
$awbPath = $testDir . '/AWB-' . $shipment->awb_no . '.pdf';
|
||||
File::put($awbPath, $awbContent);
|
||||
echo " ✅ AWB PDF saved: {$awbPath}\n";
|
||||
echo " Size: " . number_format(strlen($awbContent) / 1024, 2) . " KB\n\n";
|
||||
} catch (\Throwable $e) {
|
||||
echo " ❌ AWB PDF failed: " . $e->getMessage() . "\n\n";
|
||||
}
|
||||
|
||||
// ─── ۶. تولید Invoice PDF ───
|
||||
echo "5️⃣ Generating Invoice PDF...\n";
|
||||
try {
|
||||
$invoiceContent = $pdfService->invoice($shipment);
|
||||
$invoicePath = $testDir . '/INVOICE-' . $shipment->awb_no . '.pdf';
|
||||
File::put($invoicePath, $invoiceContent);
|
||||
echo " ✅ Invoice PDF saved: {$invoicePath}\n";
|
||||
echo " Size: " . number_format(strlen($invoiceContent) / 1024, 2) . " KB\n\n";
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
echo " ⚠️ Invoice skipped: " . $e->getMessage() . "\n";
|
||||
echo " (این طبیعی است اگر محموله DOC است)\n\n";
|
||||
} catch (\Throwable $e) {
|
||||
echo " ❌ Invoice PDF failed: " . $e->getMessage() . "\n\n";
|
||||
}
|
||||
|
||||
// ─── ۷. تولید Label PDF ───
|
||||
echo "6️⃣ Generating Label PDF...\n";
|
||||
try {
|
||||
$labelContent = $pdfService->label($shipment);
|
||||
$labelPath = $testDir . '/LABEL-' . $shipment->awb_no . '.pdf';
|
||||
File::put($labelPath, $labelContent);
|
||||
echo " ✅ Label PDF saved: {$labelPath}\n";
|
||||
echo " Size: " . number_format(strlen($labelContent) / 1024, 2) . " KB\n\n";
|
||||
} catch (\Throwable $e) {
|
||||
echo " ❌ Label PDF failed: " . $e->getMessage() . "\n\n";
|
||||
}
|
||||
|
||||
// ─── ۸. تست محموله DOC (بدون فاکتور) ───
|
||||
echo "7️⃣ Testing DOC shipment (should skip invoice)...\n";
|
||||
$docShipment = Shipment::where('type', 'DOC_NORMAL')->first();
|
||||
if ($docShipment) {
|
||||
try {
|
||||
$docShipment->load('items');
|
||||
if ($docShipment->items->isEmpty()) {
|
||||
$pdfService->invoice($docShipment);
|
||||
echo " ❌ ERROR: Should have thrown exception for DOC shipment!\n\n";
|
||||
} else {
|
||||
echo " ℹ️ DOC shipment has items, invoice would work\n\n";
|
||||
}
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
echo " ✅ Correctly skipped invoice for DOC: " . $e->getMessage() . "\n\n";
|
||||
}
|
||||
} else {
|
||||
echo " ℹ️ No DOC shipment found for testing (skipped)\n\n";
|
||||
}
|
||||
|
||||
echo "═══════════════════════════════════════════\n";
|
||||
echo "✅ Test completed!\n";
|
||||
echo "═══════════════════════════════════════════\n\n";
|
||||
echo "📁 Check the generated PDFs at:\n";
|
||||
echo " {$testDir}\n\n";
|
||||
145
04_Laravel/test_pdf_generation.php
Normal file
145
04_Laravel/test_pdf_generation.php
Normal file
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
/**
|
||||
* اسکریپت تست تولید PDF برای IFNEX
|
||||
*
|
||||
* این اسکریپت یه محموله نمونه میسازه و سه تا PDF (AWB, Invoice, Label) تولید میکنه
|
||||
* و اونها رو توی پوشه storage/app/pdf-test/ ذخیره میکنه.
|
||||
*
|
||||
* استفاده:
|
||||
* php test_pdf_generation.php
|
||||
*
|
||||
* پس از اجرا، فایلهای تولیدشده رو بررسی کنید:
|
||||
* storage/app/pdf-test/AWB-test.pdf
|
||||
* storage/app/pdf-test/INVOICE-test.pdf
|
||||
* storage/app/pdf-test/LABEL-test.pdf
|
||||
*/
|
||||
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
$app = require_once __DIR__ . '/bootstrap/app.php';
|
||||
$app->make('Illuminate\Contracts\Console\Kernel')->bootstrap();
|
||||
|
||||
use App\Models\Shipment;
|
||||
use App\Models\ShipmentItem;
|
||||
use App\Models\Country;
|
||||
use App\Services\PdfService;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
echo "═══════════════════════════════════════════\n";
|
||||
echo "🧪 IFNEX PDF Generation Test\n";
|
||||
echo "═══════════════════════════════════════════\n\n";
|
||||
|
||||
// ─── ۱. بررسی نصب بودن کتابخانه بارکد ───
|
||||
echo "1️⃣ Checking barcode library...\n";
|
||||
if (class_exists(\Picqer\Barcode\BarcodeGeneratorPNG::class)) {
|
||||
echo " ✅ picqer/php-barcode-generator is installed\n\n";
|
||||
} else {
|
||||
echo " ❌ picqer/php-barcode-generator is NOT installed\n";
|
||||
echo " Run: composer require picqer/php-barcode-generator\n\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// ─── ۲. ایجاد پوشه تست ───
|
||||
$testDir = storage_path('app/pdf-test');
|
||||
if (!File::exists($testDir)) {
|
||||
File::makeDirectory($testDir, 0755, true);
|
||||
echo "2️⃣ Created test directory: {$testDir}\n\n";
|
||||
} else {
|
||||
echo "2️⃣ Test directory exists: {$testDir}\n\n";
|
||||
}
|
||||
|
||||
// ─── ۳. پیدا کردن یه محموله نمونه ───
|
||||
echo "3️⃣ Finding sample shipment...\n";
|
||||
$shipment = Shipment::with(['fromCountry', 'toCountry', 'items'])->latest()->first();
|
||||
|
||||
if (!$shipment) {
|
||||
echo " ❌ No shipment found in database. Please create one first.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
echo " ✅ Found shipment: {$shipment->awb_no}\n";
|
||||
echo " - Type: {$shipment->type?->value}\n";
|
||||
echo " - Direction: {$shipment->direction?->value}\n";
|
||||
echo " - From: {$shipment->fromCountry?->name}\n";
|
||||
echo " - To: {$shipment->toCountry?->name}\n";
|
||||
echo " - Items: {$shipment->items->count()}\n\n";
|
||||
|
||||
// ─── ۴. اگر آیتم نداره، یه آیتم تستی اضافه کن ───
|
||||
if ($shipment->items->isEmpty()) {
|
||||
echo " ⚠️ Shipment has no items. Adding test item...\n";
|
||||
ShipmentItem::create([
|
||||
'shipment_id' => $shipment->id,
|
||||
'row_number' => 1,
|
||||
'description' => 'Electronics PCB Board',
|
||||
'hs_code' => '8542390001',
|
||||
'quantity' => 104,
|
||||
'unit_price' => 1.10,
|
||||
'total_usd' => 114.40,
|
||||
]);
|
||||
$shipment->load('items');
|
||||
echo " ✅ Added test item\n\n";
|
||||
}
|
||||
|
||||
// ─── ۵. تولید AWB PDF ───
|
||||
echo "4️⃣ Generating AWB PDF...\n";
|
||||
try {
|
||||
$pdfService = app(PdfService::class);
|
||||
$awbContent = $pdfService->awb($shipment);
|
||||
$awbPath = $testDir . '/AWB-' . $shipment->awb_no . '.pdf';
|
||||
File::put($awbPath, $awbContent);
|
||||
echo " ✅ AWB PDF saved: {$awbPath}\n";
|
||||
echo " Size: " . number_format(strlen($awbContent) / 1024, 2) . " KB\n\n";
|
||||
} catch (\Throwable $e) {
|
||||
echo " ❌ AWB PDF failed: " . $e->getMessage() . "\n\n";
|
||||
}
|
||||
|
||||
// ─── ۶. تولید Invoice PDF ───
|
||||
echo "5️⃣ Generating Invoice PDF...\n";
|
||||
try {
|
||||
$invoiceContent = $pdfService->invoice($shipment);
|
||||
$invoicePath = $testDir . '/INVOICE-' . $shipment->awb_no . '.pdf';
|
||||
File::put($invoicePath, $invoiceContent);
|
||||
echo " ✅ Invoice PDF saved: {$invoicePath}\n";
|
||||
echo " Size: " . number_format(strlen($invoiceContent) / 1024, 2) . " KB\n\n";
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
echo " ⚠️ Invoice skipped: " . $e->getMessage() . "\n";
|
||||
echo " (این طبیعی است اگر محموله DOC است)\n\n";
|
||||
} catch (\Throwable $e) {
|
||||
echo " ❌ Invoice PDF failed: " . $e->getMessage() . "\n\n";
|
||||
}
|
||||
|
||||
// ─── ۷. تولید Label PDF ───
|
||||
echo "6️⃣ Generating Label PDF...\n";
|
||||
try {
|
||||
$labelContent = $pdfService->label($shipment);
|
||||
$labelPath = $testDir . '/LABEL-' . $shipment->awb_no . '.pdf';
|
||||
File::put($labelPath, $labelContent);
|
||||
echo " ✅ Label PDF saved: {$labelPath}\n";
|
||||
echo " Size: " . number_format(strlen($labelContent) / 1024, 2) . " KB\n\n";
|
||||
} catch (\Throwable $e) {
|
||||
echo " ❌ Label PDF failed: " . $e->getMessage() . "\n\n";
|
||||
}
|
||||
|
||||
// ─── ۸. تست محموله DOC (بدون فاکتور) ───
|
||||
echo "7️⃣ Testing DOC shipment (should skip invoice)...\n";
|
||||
$docShipment = Shipment::where('type', 'DOC_NORMAL')->first();
|
||||
if ($docShipment) {
|
||||
try {
|
||||
$docShipment->load('items');
|
||||
if ($docShipment->items->isEmpty()) {
|
||||
$pdfService->invoice($docShipment);
|
||||
echo " ❌ ERROR: Should have thrown exception for DOC shipment!\n\n";
|
||||
} else {
|
||||
echo " ℹ️ DOC shipment has items, invoice would work\n\n";
|
||||
}
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
echo " ✅ Correctly skipped invoice for DOC: " . $e->getMessage() . "\n\n";
|
||||
}
|
||||
} else {
|
||||
echo " ℹ️ No DOC shipment found for testing (skipped)\n\n";
|
||||
}
|
||||
|
||||
echo "═══════════════════════════════════════════\n";
|
||||
echo "✅ Test completed!\n";
|
||||
echo "═══════════════════════════════════════════\n\n";
|
||||
echo "📁 Check the generated PDFs at:\n";
|
||||
echo " {$testDir}\n\n";
|
||||
Loading…
Reference in New Issue
Block a user