feat(shipment): implement PDF generation for AWB, invoice, and labels

Introduce PDF generation capabilities for shipments using laravel-dompdf.
This includes a new service layer, dedicated controller, and routes
to handle the generation of Air Waybills (AWB), Invoices, and Shipping
Labels.

- Add `PdfService` to encapsulate PDF generation logic.
- Add `ShipmentPdfController` to handle PDF download requests.
- Integrate `barryvdh/laravel-dompdf` dependency.
- Add Filament header actions to view shipment pages for quick PDF access.
- Update order success view to provide direct download links for documents.
- Add label methods to `ShipmentDirection` and `ShipmentType` enums.
- Update project status documentation to reflect initial PDF implementation.
This commit is contained in:
Kazem Alghasi 2026-08-04 09:20:23 +03:30
parent 24fc56feaf
commit 5874568c63
14 changed files with 1006 additions and 4 deletions

View File

@ -237,7 +237,7 @@
- [ ] موتور قیمت‌گذاری کامل (PriceCalculatorService) — نوشته شد، نیاز به تست با داده‌های واقعی
- [ ] جدول `shipping_rates` — ۴۰۴ رکورد import شد، نیاز به تکمیل
- [ ] فرم ثبت سفارش آنلاین با ۹ ردیف کالای گمرکی
- [ ] تولید PDF: AWB، INVOICE، Label مطابق قالب اکسل
- [ ] تولید PDF: AWB، INVOICE، Label مطابق قالب اکسل — اولیه پیاده شد، نیاز به تطبیق دقیق با قالب‌های اکسل و رفع ساختار فعلی
- [ ] ماژول ایمپورت اکسل تعرفه‌ها
- [ ] صفحه استعلام قیمت واقعی

View File

@ -6,4 +6,12 @@ enum ShipmentDirection: string
{
case Import = 'import';
case Export = 'export';
public function label(): string
{
return match($this) {
self::Import => 'Import',
self::Export => 'Export',
};
}
}

View File

@ -7,4 +7,13 @@ enum ShipmentType: string
case DocNormal = 'DOC_NORMAL';
case DocEconomy = 'DOC_ECONOMY';
case Parcel = 'PARCEL';
public function label(): string
{
return match($this) {
self::DocNormal => 'Document Normal',
self::DocEconomy => 'Document Economy',
self::Parcel => 'Parcel',
};
}
}

View File

@ -9,4 +9,28 @@ use Filament\Resources\Pages\ViewRecord;
class ViewShipment extends ViewRecord
{
protected static string $resource = ShipmentResource::class;
protected function getHeaderActions(): array
{
return [
Actions\Action::make('print_label')
->label('چاپ لیبل')
->icon('heroicon-o-printer')
->color('success')
->url(fn ($record) => route('shipments.pdf.label', $record))
->openUrlInNewTab(),
Actions\Action::make('download_awb')
->label('دانلود AWB')
->icon('heroicon-o-document-text')
->color('warning')
->url(fn ($record) => route('shipments.pdf.awb', $record)),
Actions\Action::make('download_invoice')
->label('دانلود INVOICE')
->icon('heroicon-o-currency-dollar')
->color('info')
->url(fn ($record) => route('shipments.pdf.invoice', $record)),
];
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Http\Controllers;
use App\Models\Shipment;
use App\Services\PdfService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class ShipmentPdfController extends Controller
{
public function __construct(protected PdfService $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');
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', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
]);
return response('PDF generation failed: ' . $e->getMessage(), 500);
}
}
public function invoice(Shipment $shipment)
{
try {
$content = $this->pdf->invoice($shipment);
return response($content, 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'attachment; filename="INVOICE-' . $shipment->awb_no . '.pdf"',
]);
} catch (\Throwable $e) {
Log::error('Invoice PDF generation failed', ['error' => $e->getMessage()]);
return response('PDF generation failed: ' . $e->getMessage(), 500);
}
}
public function label(Shipment $shipment)
{
try {
$content = $this->pdf->label($shipment);
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()]);
return response('PDF generation failed: ' . $e->getMessage(), 500);
}
}
}

View File

@ -0,0 +1,96 @@
<?php
namespace App\Services;
use App\Models\Shipment;
use App\Models\ShipmentItem;
use Dompdf\Dompdf;
use Dompdf\Options;
use Illuminate\Support\Collection;
class PdfService
{
public function awb(Shipment $shipment): string
{
$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,
];
$html = view('pdfs.awb', $data)->render();
return $this->generatePdf($html, 'A4', 'portrait');
}
public function invoice(Shipment $shipment): string
{
$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,
];
$html = view('pdfs.invoice', $data)->render();
return $this->generatePdf($html, 'A4', 'portrait');
}
public function label(Shipment $shipment): string
{
$shipment->loadMissing(['fromCountry', 'toCountry']);
$data = [
'shipment' => $shipment,
'shipper' => $this->formatAddress($shipment, 'sender'),
'receiver' => $this->formatAddress($shipment, 'receiver'),
];
$html = view('pdfs.label', $data)->render();
return $this->generatePdf($html, [0, 0, 80, 120], 'portrait');
}
private function generatePdf(string $html, array|string $paper, string $orientation): string
{
$options = new Options();
$options->set('isHtml5ParserEnabled', true);
$options->set('isRemoteEnabled', true);
$options->set('defaultFont', 'DejaVu Sans');
$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'},
'zip' => $shipment->{$prefix . '_zip'},
'id_number' => $shipment->{$prefix . '_id_number'},
]);
}
}

View File

@ -7,6 +7,7 @@
"license": "MIT",
"require": {
"php": "^8.2",
"barryvdh/laravel-dompdf": "*",
"filament/filament": "3.3.*",
"laravel/framework": "^11.0",
"laravel/tinker": "^2.10.1",

457
04_Laravel/composer.lock generated
View File

@ -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": "36a7ef85e0bbcd022965ce2b7234718f",
"content-hash": "c0d4db086122fe759e56ff3bf438bc30",
"packages": [
{
"name": "anourvalar/eloquent-serialize",
@ -71,6 +71,83 @@
},
"time": "2026-06-20T14:30:25+00:00"
},
{
"name": "barryvdh/laravel-dompdf",
"version": "v3.1.2",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-dompdf.git",
"reference": "ee3b72b19ccdf57d0243116ecb2b90261344dedc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/ee3b72b19ccdf57d0243116ecb2b90261344dedc",
"reference": "ee3b72b19ccdf57d0243116ecb2b90261344dedc",
"shasum": ""
},
"require": {
"dompdf/dompdf": "^3.0",
"illuminate/support": "^9|^10|^11|^12|^13.0",
"php": "^8.1"
},
"require-dev": {
"larastan/larastan": "^2.7|^3.0",
"orchestra/testbench": "^7|^8|^9.16|^10|^11.0",
"phpro/grumphp": "^2.5",
"squizlabs/php_codesniffer": "^3.5"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"PDF": "Barryvdh\\DomPDF\\Facade\\Pdf",
"Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf"
},
"providers": [
"Barryvdh\\DomPDF\\ServiceProvider"
]
},
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"Barryvdh\\DomPDF\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
}
],
"description": "A DOMPDF Wrapper for Laravel",
"keywords": [
"dompdf",
"laravel",
"pdf"
],
"support": {
"issues": "https://github.com/barryvdh/laravel-dompdf/issues",
"source": "https://github.com/barryvdh/laravel-dompdf/tree/v3.1.2"
},
"funding": [
{
"url": "https://fruitcake.nl",
"type": "custom"
},
{
"url": "https://github.com/barryvdh",
"type": "github"
}
],
"time": "2026-02-21T08:51:10+00:00"
},
{
"name": "beberlei/assert",
"version": "v3.3.4",
@ -1071,6 +1148,161 @@
],
"time": "2024-02-05T11:56:58+00:00"
},
{
"name": "dompdf/dompdf",
"version": "v3.1.6",
"source": {
"type": "git",
"url": "https://github.com/dompdf/dompdf.git",
"reference": "6d4b4eb8500f7a786da8868ba463a71b725a4005"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/dompdf/zipball/6d4b4eb8500f7a786da8868ba463a71b725a4005",
"reference": "6d4b4eb8500f7a786da8868ba463a71b725a4005",
"shasum": ""
},
"require": {
"dompdf/php-font-lib": "^1.0.0",
"dompdf/php-svg-lib": "^1.0.0",
"ext-dom": "*",
"ext-mbstring": "*",
"masterminds/html5": "^2.0",
"php": "^7.1 || ^8.0"
},
"require-dev": {
"ext-gd": "*",
"ext-json": "*",
"ext-zip": "*",
"mockery/mockery": "^1.3",
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11",
"squizlabs/php_codesniffer": "^3.5",
"symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0"
},
"suggest": {
"ext-gd": "Needed to process images",
"ext-gmagick": "Improves image processing performance",
"ext-imagick": "Improves image processing performance",
"ext-zlib": "Needed for pdf stream compression"
},
"type": "library",
"autoload": {
"psr-4": {
"Dompdf\\": "src/"
},
"classmap": [
"lib/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1"
],
"authors": [
{
"name": "The Dompdf Community",
"homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md"
}
],
"description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
"homepage": "https://github.com/dompdf/dompdf",
"support": {
"issues": "https://github.com/dompdf/dompdf/issues",
"source": "https://github.com/dompdf/dompdf/tree/v3.1.6"
},
"time": "2026-07-20T12:29:38+00:00"
},
{
"name": "dompdf/php-font-lib",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/dompdf/php-font-lib.git",
"reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a6e9a688a2a80016ac080b97be73d3e10c444c9a",
"reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^7.1 || ^8.0"
},
"require-dev": {
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11 || ^12"
},
"type": "library",
"autoload": {
"psr-4": {
"FontLib\\": "src/FontLib"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-or-later"
],
"authors": [
{
"name": "The FontLib Community",
"homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md"
}
],
"description": "A library to read, parse, export and make subsets of different types of font files.",
"homepage": "https://github.com/dompdf/php-font-lib",
"support": {
"issues": "https://github.com/dompdf/php-font-lib/issues",
"source": "https://github.com/dompdf/php-font-lib/tree/1.0.2"
},
"time": "2026-01-20T14:10:26+00:00"
},
{
"name": "dompdf/php-svg-lib",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/dompdf/php-svg-lib.git",
"reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/8259ffb930817e72b1ff1caef5d226501f3dfeb1",
"reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^7.1 || ^8.0",
"sabberworm/php-css-parser": "^8.4 || ^9.0"
},
"require-dev": {
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11"
},
"type": "library",
"autoload": {
"psr-4": {
"Svg\\": "src/Svg"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0-or-later"
],
"authors": [
{
"name": "The SvgLib Community",
"homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md"
}
],
"description": "A library to read, parse and export to PDF SVG files.",
"homepage": "https://github.com/dompdf/php-svg-lib",
"support": {
"issues": "https://github.com/dompdf/php-svg-lib/issues",
"source": "https://github.com/dompdf/php-svg-lib/tree/1.0.2"
},
"time": "2026-01-02T16:01:13+00:00"
},
{
"name": "dragonmantank/cron-expression",
"version": "v3.6.0",
@ -5435,6 +5667,86 @@
],
"time": "2026-03-19T10:36:26+00:00"
},
{
"name": "sabberworm/php-css-parser",
"version": "v9.4.0",
"source": {
"type": "git",
"url": "https://github.com/MyIntervals/PHP-CSS-Parser.git",
"reference": "fd3bf9fb173e0df649bc4e3e0d088a1b2417c08f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/fd3bf9fb173e0df649bc4e3e0d088a1b2417c08f",
"reference": "fd3bf9fb173e0df649bc4e3e0d088a1b2417c08f",
"shasum": ""
},
"require": {
"ext-iconv": "*",
"php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
"thecodingmachine/safe": "^1.3 || ^2.5 || ^3.4"
},
"require-dev": {
"php-parallel-lint/php-parallel-lint": "1.4.0",
"phpstan/extension-installer": "1.4.3",
"phpstan/phpstan": "1.12.33 || 2.2.2",
"phpstan/phpstan-phpunit": "1.4.2 || 2.0.16",
"phpstan/phpstan-strict-rules": "1.6.2 || 2.0.11",
"phpunit/phpunit": "8.5.52",
"rawr/phpunit-data-provider": "3.3.1",
"rector/rector": "1.2.10 || 2.4.6",
"rector/type-perfect": "1.0.0 || 2.1.3",
"squizlabs/php_codesniffer": "4.0.1",
"thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.3"
},
"suggest": {
"ext-mbstring": "for parsing UTF-8 CSS"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "9.5.x-dev"
}
},
"autoload": {
"files": [
"src/Rule/Rule.php",
"src/RuleSet/RuleContainer.php"
],
"psr-4": {
"Sabberworm\\CSS\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Raphael Schweikert"
},
{
"name": "Oliver Klee",
"email": "github@oliverklee.de"
},
{
"name": "Jake Hotson",
"email": "jake.github@qzdesign.co.uk"
}
],
"description": "Parser for CSS Files written in PHP",
"homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser",
"keywords": [
"css",
"parser",
"stylesheet"
],
"support": {
"issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues",
"source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.4.0"
},
"time": "2026-06-18T15:10:53+00:00"
},
{
"name": "spatie/color",
"version": "1.8.0",
@ -8117,6 +8429,149 @@
],
"time": "2026-07-21T15:13:06+00:00"
},
{
"name": "thecodingmachine/safe",
"version": "v3.4.0",
"source": {
"type": "git",
"url": "https://github.com/thecodingmachine/safe.git",
"reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19",
"reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19",
"shasum": ""
},
"require": {
"php": "^8.1"
},
"require-dev": {
"php-parallel-lint/php-parallel-lint": "^1.4",
"phpstan/phpstan": "^2",
"phpunit/phpunit": "^10",
"squizlabs/php_codesniffer": "^3.2"
},
"type": "library",
"autoload": {
"files": [
"lib/special_cases.php",
"generated/apache.php",
"generated/apcu.php",
"generated/array.php",
"generated/bzip2.php",
"generated/calendar.php",
"generated/classobj.php",
"generated/com.php",
"generated/cubrid.php",
"generated/curl.php",
"generated/datetime.php",
"generated/dir.php",
"generated/eio.php",
"generated/errorfunc.php",
"generated/exec.php",
"generated/fileinfo.php",
"generated/filesystem.php",
"generated/filter.php",
"generated/fpm.php",
"generated/ftp.php",
"generated/funchand.php",
"generated/gettext.php",
"generated/gmp.php",
"generated/gnupg.php",
"generated/hash.php",
"generated/ibase.php",
"generated/ibmDb2.php",
"generated/iconv.php",
"generated/image.php",
"generated/imap.php",
"generated/info.php",
"generated/inotify.php",
"generated/json.php",
"generated/ldap.php",
"generated/libxml.php",
"generated/lzf.php",
"generated/mailparse.php",
"generated/mbstring.php",
"generated/misc.php",
"generated/mysql.php",
"generated/mysqli.php",
"generated/network.php",
"generated/oci8.php",
"generated/opcache.php",
"generated/openssl.php",
"generated/outcontrol.php",
"generated/pcntl.php",
"generated/pcre.php",
"generated/pgsql.php",
"generated/posix.php",
"generated/ps.php",
"generated/pspell.php",
"generated/readline.php",
"generated/rnp.php",
"generated/rpminfo.php",
"generated/rrd.php",
"generated/sem.php",
"generated/session.php",
"generated/shmop.php",
"generated/sockets.php",
"generated/sodium.php",
"generated/solr.php",
"generated/spl.php",
"generated/sqlsrv.php",
"generated/ssdeep.php",
"generated/ssh2.php",
"generated/stream.php",
"generated/strings.php",
"generated/swoole.php",
"generated/uodbc.php",
"generated/uopz.php",
"generated/url.php",
"generated/var.php",
"generated/xdiff.php",
"generated/xml.php",
"generated/xmlrpc.php",
"generated/yaml.php",
"generated/yaz.php",
"generated/zip.php",
"generated/zlib.php"
],
"classmap": [
"lib/DateTime.php",
"lib/DateTimeImmutable.php",
"lib/Exceptions/",
"generated/Exceptions/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "PHP core functions that throw exceptions instead of returning FALSE on error",
"support": {
"issues": "https://github.com/thecodingmachine/safe/issues",
"source": "https://github.com/thecodingmachine/safe/tree/v3.4.0"
},
"funding": [
{
"url": "https://github.com/OskarStark",
"type": "github"
},
{
"url": "https://github.com/shish",
"type": "github"
},
{
"url": "https://github.com/silasjoisten",
"type": "github"
},
{
"url": "https://github.com/staabm",
"type": "github"
}
],
"time": "2026-02-04T18:08:13+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
"version": "v2.4.0",

View File

@ -0,0 +1,35 @@
<?php
require 'C:/xampp/htdocs/IFNEX-Logistics/04_Laravel/vendor/autoload.php';
$reader = new \PhpOffice\PhpSpreadsheet\IOFactory();
$spreadsheet = $reader->load('C:/xampp/htdocs/IFNEX-Logistics/04_Laravel/storage/app/public/01KYWGVNKS5TNMN37RV77PCYNZ.xlsx');
$sheets = $spreadsheet->getSheetNames();
echo "=== ALL SHEET NAMES ===" . PHP_EOL;
echo implode(', ', $sheets) . PHP_EOL . PHP_EOL;
// Inspect AWB sheet
echo "=== AWB SHEET ===" . PHP_EOL;
$awbSheet = $spreadsheet->getSheetByName('AWB');
if ($awbSheet) {
echo "Dimensions: " . $awbSheet->calculateWorksheetDimensions() . PHP_EOL;
echo "Highest row: " . $awbSheet->getHighestRow() . PHP_EOL;
echo "Highest column: " . $awbSheet->getHighestColumn() . PHP_EOL;
echo PHP_EOL;
// Print first 50 rows
for ($row = 1; $row <= min(50, $awbSheet->getHighestRow()); $row++) {
$rowData = [];
for ($col = 1; $col <= 15; $col++) {
$cell = $awbSheet->getCellByColumnAndRow($col, $row);
$value = $cell->getValue();
if ($value !== null) {
$rowData[] = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::stringFromColumnIndex($col) . $row . "=" . json_encode($value);
}
}
if (!empty($rowData)) {
echo "Row $row: " . implode(" | ", $rowData) . PHP_EOL;
}
}
} else {
echo "AWB sheet not found!" . PHP_EOL;
}

View File

@ -14,8 +14,21 @@
<span class="text-2xl font-mono font-bold text-orange-600">{{ $shipment->awb_no ?? 'N/A' }}</span>
</div>
<p class="text-sm text-gray-500 mb-8">کارشناسان ما در اسرع وقت با شما تماس خواهند گرفت.</p>
<div class="flex justify-center gap-4">
<a href="{{ route('orders.create') }}" class="inline-flex items-center px-4 py-2 bg-orange-600 border border-transparent rounded-md font-semibold text-white hover:bg-orange-700">
<div class="flex flex-wrap justify-center gap-3 mb-6">
<a href="{{ route('shipments.pdf.awb', $shipment) }}" class="inline-flex items-center px-4 py-2 bg-orange-600 border border-transparent rounded-md font-semibold text-white hover:bg-orange-700">
دانلود AWB
</a>
<a href="{{ route('shipments.pdf.invoice', $shipment) }}" class="inline-flex items-center px-4 py-2 bg-blue-600 border border-transparent rounded-md font-semibold text-white hover:bg-blue-700">
دانلود INVOICE
</a>
<a href="{{ route('shipments.pdf.label', $shipment) }}" class="inline-flex items-center px-4 py-2 bg-gray-700 border border-transparent rounded-md font-semibold text-white hover:bg-gray-800">
دانلود LABEL
</a>
</div>
<div class="flex justify-center">
<a href="{{ route('orders.create') }}" class="inline-flex items-center px-4 py-2 bg-white border border-gray-300 rounded-md font-semibold text-gray-700 hover:bg-gray-50">
ثبت سفارش جدید
</a>
</div>

View File

@ -0,0 +1,113 @@
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<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: rtl; text-align: right; }
.container { padding: 20px; }
.header { text-align: center; margin-bottom: 20px; border-bottom: 2px solid #f37021; padding-bottom: 10px; }
.header h1 { color: #f37021; font-size: 18px; margin-bottom: 5px; }
.header p { color: #666; font-size: 10px; }
.logo { font-size: 20px; font-weight: bold; color: #f37021; margin-bottom: 5px; }
.section { margin-bottom: 15px; border: 1px solid #ddd; padding: 10px; }
.section-title { font-weight: bold; color: #f37021; margin-bottom: 8px; font-size: 12px; border-bottom: 1px solid #eee; padding-bottom: 3px; }
.row { display: flex; margin-bottom: 5px; }
.label { width: 120px; font-weight: bold; color: #555; }
.value { flex: 1; }
.two-col { display: flex; gap: 20px; }
.col { flex: 1; }
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
th, td { border: 1px solid #ddd; padding: 6px; text-align: right; font-size: 10px; }
th { background-color: #f5f5f5; font-weight: bold; }
.financial { background-color: #f9f9f9; }
.total { font-weight: bold; color: #f37021; font-size: 12px; }
.signature { margin-top: 30px; 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 { margin-top: 20px; text-align: center; font-size: 9px; color: #999; border-top: 1px solid #ddd; padding-top: 10px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="logo">IFNEX LOGISTICS</div>
<h1>AIR WAYBILL</h1>
<p>AWB No: {{ $shipment->awb_no }}</p>
</div>
<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:</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">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">City:</span><span class="value">{{ $shipper['city'] }}</span></div>
<div class="row"><span class="label">Zip:</span><span class="value">{{ $shipper['zip'] }}</span></div>
<div class="row"><span class="label">ID:</span><span class="value">{{ $shipper['id_number'] }}</span></div>
</div>
<div class="col section">
<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">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:</span><span class="value">{{ $receiver['zip'] }}</span></div>
<div class="row"><span class="label">ID:</span><span class="value">{{ $receiver['id_number'] }}</span></div>
</div>
</div>
<div class="section">
<div class="section-title">SHIPMENT DETAILS</div>
<div class="row">
<div style="width: 50%">
<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">Chargeable Weight:</span><span class="value">{{ $shipment->chargeable_weight }} kg</span></div>
<div class="row"><span class="label">Dimensions:</span><span class="value">{{ $shipment->dimensions }}</span></div>
<div class="row"><span class="label">Service:</span><span class="value">{{ $shipment->type?->label() }}</span></div>
<div class="row"><span class="label">Direction:</span><span class="value">{{ $shipment->direction?->label() }}</span></div>
</div>
<div style="width: 50%">
<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">Forwarder:</span><span class="value">{{ $shipment->forwarder }}</span></div>
<div class="row"><span class="label">Content:</span><span class="value">{{ $shipment->reason_for_export ?: 'General Goods' }}</span></div>
</div>
</div>
</div>
<div class="section financial">
<div class="section-title">PAYMENT / پرداخت</div>
<div class="row"><span class="label">Shipping Price (AED):</span><span class="value">{{ number_format($shipment->shipping_price ?? 0, 2) }}</span></div>
<div class="row"><span class="label">Extra Service (IRR):</span><span class="value">{{ number_format($shipment->extra_service ?? 0) }}</span></div>
<div class="row"><span class="label">Packing Cost (IRR):</span><span class="value">{{ number_format($shipment->packing_cost ?? 0) }}</span></div>
<div class="row"><span class="label">Domestic Pickup (IRR):</span><span class="value">{{ number_format($shipment->domestic_pickup ?? 0) }}</span></div>
<div class="row"><span class="label">Domestic Delivery (IRR):</span><span class="value">{{ number_format($shipment->domestic_delivery ?? 0) }}</span></div>
<div class="row"><span class="label">Warehousing (IRR):</span><span class="value">{{ number_format($shipment->warehousing_cost ?? 0) }}</span></div>
<div class="row"><span class="label">Discount (IRR):</span><span class="value">{{ number_format($shipment->discount ?? 0) }}</span></div>
<div class="row total"><span class="label">Total Fee (IRR):</span><span class="value">{{ number_format($shipment->total_fee ?? 0) }}</span></div>
<div class="row total"><span class="label">Net Dirham (AED):</span><span class="value">{{ number_format($shipment->net_dirham ?? 0, 2) }}</span></div>
</div>
<div class="signature">
<div class="signature-box">
Shipper Name & Signature<br>
Date: {{ now()->format('Y-m-d') }}
</div>
<div class="signature-box">
Track Your Shipment at:<br>
http://ifnex.net
</div>
</div>
<div class="footer">
IFNEX Logistics We Deliver Value | Generated on {{ now()->format('Y-m-d H:i') }}
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,124 @@
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<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: rtl; text-align: right; }
.container { padding: 20px; }
.header { text-align: center; margin-bottom: 20px; border-bottom: 2px solid #f37021; padding-bottom: 10px; }
.header h1 { color: #f37021; font-size: 20px; margin-bottom: 5px; }
.header .meta { display: flex; justify-content: space-between; font-size: 10px; color: #666; }
.section { margin-bottom: 15px; border: 1px solid #ddd; padding: 10px; }
.section-title { font-weight: bold; color: #f37021; margin-bottom: 8px; font-size: 12px; border-bottom: 1px solid #eee; padding-bottom: 3px; }
.row { display: flex; margin-bottom: 4px; }
.label { width: 130px; font-weight: bold; color: #555; }
.value { flex: 1; }
.two-col { display: flex; gap: 20px; }
.col { flex: 1; }
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
th, td { border: 1px solid #ddd; padding: 6px; text-align: right; font-size: 10px; }
th { background-color: #f5f5f5; font-weight: bold; }
.text-right { text-align: right; }
.text-center { text-align: center; }
.total { font-weight: bold; color: #f37021; }
.footer { margin-top: 20px; border-top: 1px solid #ddd; padding-top: 10px; font-size: 9px; color: #666; }
.declaration { font-size: 9px; color: #666; margin-top: 15px; line-height: 1.5; }
.awb-box { background-color: #f9f9f9; padding: 5px 10px; display: inline-block; border: 1px dashed #f37021; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>INVOICE / فاکتور</h1>
<div class="meta">
<span>DATE: {{ $shipment->created_at?->format('Y-m-d') }}</span>
<span class="awb-box">INVOICE NO: {{ $shipment->awb_no }}</span>
</div>
</div>
<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:</span><span class="value">{{ $shipper['company'] }}</span></div>
<div class="row"><span class="label">Contact:</span><span class="value">{{ $shipper['phone'] }}</span></div>
<div class="row"><span class="label">Address:</span><span class="value">{{ $shipper['address'] }}</span></div>
<div class="row"><span class="label">City/Zip:</span><span class="value">{{ $shipper['city'] }} {{ $shipper['zip'] }}</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:</span><span class="value">{{ $receiver['company'] }}</span></div>
<div class="row"><span class="label">Contact:</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="row"><span class="label">City/Zip:</span><span class="value">{{ $receiver['city'] }} {{ $receiver['zip'] }}</span></div>
<div class="row"><span class="label">Email:</span><span class="value">{{ $receiver['email'] }}</span></div>
</div>
</div>
<div class="section">
<div class="section-title">SHIPMENT DETAILS</div>
<div class="row">
<div style="width: 50%">
<div class="row"><span class="label">Weight:</span><span class="value">{{ $shipment->weight }} kg</span></div>
<div class="row"><span class="label">Volumetric:</span><span class="value">{{ $shipment->volumetric_weight }} kg</span></div>
<div class="row"><span class="label">Dimensions:</span><span class="value">{{ $shipment->dimensions }}</span></div>
<div class="row"><span class="label">Service:</span><span class="value">{{ $shipment->type?->label() }}</span></div>
<div class="row"><span class="label">Direction:</span><span class="value">{{ $shipment->direction?->label() }}</span></div>
</div>
<div style="width: 50%">
<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">Forwarder:</span><span class="value">{{ $shipment->forwarder }}</span></div>
<div class="row"><span class="label">Reason:</span><span class="value">{{ $shipment->reason_for_export ?: 'N/A' }}</span></div>
</div>
</div>
</div>
<div class="section">
<div class="section-title">CONTENT / محموله</div>
<p style="margin-bottom: 10px;">{{ $shipment->reason_for_export ?: 'General Goods' }}</p>
<table>
<thead>
<tr>
<th style="width: 40px">No</th>
<th>Description</th>
<th style="width: 100px">H.S. Code</th>
<th style="width: 70px">Qty</th>
<th style="width: 90px">Unit Price (USD)</th>
<th style="width: 100px">Total (USD)</th>
</tr>
</thead>
<tbody>
@forelse($items as $item)
<tr>
<td class="text-center">{{ $item->row_number }}</td>
<td>{{ $item->description }}</td>
<td class="text-center">{{ $item->hs_code }}</td>
<td class="text-center">{{ $item->quantity }}</td>
<td class="text-center">{{ number_format($item->unit_price, 2) }}</td>
<td class="text-center">{{ number_format($item->total_usd, 2) }}</td>
</tr>
@empty
<tr><td colspan="6" class="text-center">No items</td></tr>
@endforelse
</tbody>
</table>
<div style="margin-top: 10px; text-align: left;">
<strong>TOTAL INVOICE AMOUNT IN USD: {{ number_format($invoice_total_usd, 2) }}</strong>
</div>
</div>
<div class="declaration">
I HEREBY STATE THAT THE ABOVE INFORMATION IS TRUE AND CORRECT TO THE BEST OF MY KNOWLEDGE.
</div>
<div class="footer">
IFNEX Logistics We Deliver Value | AWB: {{ $shipment->awb_no }} | Generated on {{ now()->format('Y-m-d H:i') }}
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: DejaVu Sans, sans-serif; font-size: 10px; direction: rtl; text-align: right; }
.container { padding: 10px; }
.header { text-align: center; margin-bottom: 10px; }
.header .logo { font-size: 14px; font-weight: bold; color: #f37021; }
.header .awb { font-size: 16px; font-weight: bold; background: #f37021; color: white; padding: 3px 8px; display: inline-block; margin-top: 3px; }
.section { margin-bottom: 8px; border: 1px solid #ddd; padding: 6px; }
.section-title { font-weight: bold; color: #f37021; font-size: 10px; margin-bottom: 3px; }
.row { display: flex; margin-bottom: 2px; }
.label { width: 90px; font-weight: bold; color: #555; }
.value { flex: 1; }
.footer { text-align: center; font-size: 8px; color: #999; margin-top: 8px; border-top: 1px solid #ddd; padding-top: 5px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="logo">IFNEX LOGISTICS</div>
<div class="awb">{{ $shipment->awb_no }}</div>
</div>
<div class="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:</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="section">
<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>
<div class="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">Volumetric:</span><span class="value">{{ $shipment->volumetric_weight }} kg</span></div>
<div class="row"><span class="label">Dimensions:</span><span class="value">{{ $shipment->dimensions }}</span></div>
<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>
<div class="footer">
IFNEX Logistics | {{ $shipment->awb_no }}
</div>
</div>
</body>
</html>

View File

@ -1,6 +1,7 @@
<?php
use App\Http\Controllers\OrderController;
use App\Http\Controllers\ShipmentPdfController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
@ -10,3 +11,9 @@ Route::get('/', function () {
Route::get('/order', [OrderController::class, 'create'])->name('orders.create');
Route::post('/order', [OrderController::class, 'store'])->name('orders.store');
Route::get('/order/success/{shipment}', [OrderController::class, 'success'])->name('orders.success');
Route::middleware('auth')->group(function () {
Route::get('/shipments/{shipment}/pdf/awb', [ShipmentPdfController::class, 'awb'])->name('shipments.pdf.awb');
Route::get('/shipments/{shipment}/pdf/invoice', [ShipmentPdfController::class, 'invoice'])->name('shipments.pdf.invoice');
Route::get('/shipments/{shipment}/pdf/label', [ShipmentPdfController::class, 'label'])->name('shipments.pdf.label');
});