feat(shipment): expand schema and enhance PDF generation
Extend the shipment database schema to support additional tracking fields, state information, and financial data. This update also transitions shipment documentation from RTL/Persian to LTR/English layouts and integrates barcode generation for AWB and labels. - feat(db): add migration for missing shipment fields (state, cod_amount, declared_value, etc.) - feat(shipment): update Shipment model with new casts and helper methods - feat(ui): refactor Filament resource to use ShipmentStatus enum and add new input fields - feat(pdf): implement barcode generation using picqer/php-barcode-generator - feat(pdf): redesign AWB, Invoice, and Label templates for LTR/English support - refactor(enum): simplify ShipmentStatus enum and update labels/colors - chore(deps): add picqer/php-barcode-generator dependency
This commit is contained in:
parent
48c88e9bc7
commit
b051ce4496
@ -4,78 +4,46 @@ namespace App\Enums;
|
||||
|
||||
enum ShipmentStatus: string
|
||||
{
|
||||
// ─── وضعیتهای مشتری (قبل از پرداخت) ───
|
||||
case PendingPayment = 'pending_payment'; // در انتظار پرداخت
|
||||
case Cancelled = 'cancelled'; // لغو شده توسط مشتری
|
||||
case PendingPayment = 'pending_payment';
|
||||
case Cancelled = 'cancelled';
|
||||
case Processed = 'processed';
|
||||
case PickedUp = 'picked_up';
|
||||
case InTransit = 'in_transit';
|
||||
case OutForDelivery = 'out_for_delivery';
|
||||
case Failed = 'failed';
|
||||
case Delivered = 'delivered';
|
||||
case Returned = 'returned';
|
||||
case Archived = 'archived';
|
||||
|
||||
// ─── وضعیتهای عملیاتی (بعد از پرداخت) ───
|
||||
case Processed = 'processed'; // پردازش شده (ادمین تأیید کرده)
|
||||
case PickedUp = 'picked_up'; // تحویل گرفته شده
|
||||
case InTransit = 'in_transit'; // در حال حمل
|
||||
case OutForDelivery = 'out_for_delivery'; // در مسیر تحویل
|
||||
case Delivered = 'delivered'; // تحویل داده شده
|
||||
case Failed = 'failed'; // ناموفق
|
||||
case Returned = 'returned'; // برگشتی
|
||||
|
||||
/**
|
||||
* لیبل فارسی
|
||||
*/
|
||||
public function label(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::PendingPayment => 'در انتظار پرداخت',
|
||||
self::Cancelled => 'لغو شده',
|
||||
self::Processed => 'پردازش شده',
|
||||
self::PickedUp => 'تحویل گرفته شده',
|
||||
self::InTransit => 'در حال حمل',
|
||||
self::OutForDelivery => 'در مسیر تحویل',
|
||||
self::Delivered => 'تحویل داده شده',
|
||||
self::Failed => 'ناموفق',
|
||||
self::Returned => 'برگشتی',
|
||||
self::Processed => 'Processed',
|
||||
self::PickedUp => 'Picked Up',
|
||||
self::InTransit => 'In Transit',
|
||||
self::OutForDelivery => 'Out For Delivery',
|
||||
self::Failed => 'Failed',
|
||||
self::Delivered => 'Delivered',
|
||||
self::Returned => 'Returned',
|
||||
self::Archived => 'آرشیو',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* رنگ badge در Filament
|
||||
*/
|
||||
public function color(): string
|
||||
{
|
||||
return match ($this) {
|
||||
self::PendingPayment => 'warning',
|
||||
self::Cancelled => 'gray',
|
||||
self::Processed => 'info',
|
||||
self::Cancelled => 'danger',
|
||||
self::Processed => 'gray',
|
||||
self::PickedUp => 'info',
|
||||
self::InTransit => 'primary',
|
||||
self::OutForDelivery => 'purple',
|
||||
self::Delivered => 'success',
|
||||
self::OutForDelivery => 'success',
|
||||
self::Failed => 'danger',
|
||||
self::Delivered => 'success',
|
||||
self::Returned => 'warning',
|
||||
self::Archived => 'gray',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* آیا این وضعیت به معنای "پرداخت شده" است؟
|
||||
*/
|
||||
public function isPaid(): bool
|
||||
{
|
||||
return in_array($this, [
|
||||
self::Processed,
|
||||
self::PickedUp,
|
||||
self::InTransit,
|
||||
self::OutForDelivery,
|
||||
self::Delivered,
|
||||
self::Failed,
|
||||
self::Returned,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* آیا این وضعیت قابل لغو توسط مشتری است؟
|
||||
*/
|
||||
public function canBeCancelledByCustomer(): bool
|
||||
{
|
||||
return in_array($this, [
|
||||
self::PendingPayment,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Enums\ShipmentStatus;
|
||||
use App\Filament\Resources\ShipmentResource\Pages;
|
||||
use App\Filament\Resources\ShipmentResource\RelationManagers;
|
||||
use App\Models\Shipment;
|
||||
@ -44,40 +45,17 @@ class ShipmentResource extends Resource
|
||||
'DOC_ECONOMY' => 'Doc Economy',
|
||||
'PARCEL' => 'Parcel',
|
||||
])
|
||||
->required(),
|
||||
|
||||
->required()
|
||||
->live(),
|
||||
Forms\Components\Select::make('status')
|
||||
->options([
|
||||
'pending_payment' => 'در انتظار پرداخت',
|
||||
'cancelled' => 'لغو شده',
|
||||
'processed' => 'Processed',
|
||||
'picked_up' => 'Picked Up',
|
||||
'in_transit' => 'In Transit',
|
||||
'out_for_delivery' => 'Out For Delivery',
|
||||
'failed' => 'Failed',
|
||||
'delivered' => 'Delivered',
|
||||
'returned' => 'Returned',
|
||||
])
|
||||
->default('processed')
|
||||
->required(),
|
||||
Forms\Components\Select::make('status')
|
||||
->options([
|
||||
'processed' => 'Processed',
|
||||
'picked_up' => 'Picked Up',
|
||||
'in_transit' => 'In Transit',
|
||||
'out_for_delivery' => 'Out For Delivery',
|
||||
'failed' => 'Failed',
|
||||
'delivered' => 'Delivered',
|
||||
'returned' => 'Returned',
|
||||
])
|
||||
->options(collect(ShipmentStatus::cases())->mapWithKeys(fn ($case) => [
|
||||
$case->value => $case->label(),
|
||||
]))
|
||||
->default('processed')
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('reason_for_export')
|
||||
->nullable()
|
||||
->label('Reason for Export'),
|
||||
Forms\Components\TextInput::make('forwarder')
|
||||
->nullable()
|
||||
->label('Forwarder'),
|
||||
Forms\Components\Select::make('from_country_id')
|
||||
->relationship('fromCountry', 'name')
|
||||
->searchable()
|
||||
@ -88,6 +66,9 @@ class ShipmentResource extends Resource
|
||||
->searchable()
|
||||
->preload()
|
||||
->label('To Country'),
|
||||
Forms\Components\TextInput::make('forwarder')
|
||||
->nullable()
|
||||
->label('Forwarder'),
|
||||
])->columns(3),
|
||||
|
||||
Forms\Components\Section::make('Weight & Dimensions')
|
||||
@ -110,6 +91,17 @@ class ShipmentResource extends Resource
|
||||
Forms\Components\TextInput::make('dimensions')
|
||||
->nullable()
|
||||
->label('Dimensions (WxLxH)'),
|
||||
Forms\Components\TextInput::make('declared_value')
|
||||
->numeric()
|
||||
->nullable()
|
||||
->prefix('USD')
|
||||
->label('Declared Value'),
|
||||
Forms\Components\TextInput::make('content_description')
|
||||
->nullable()
|
||||
->label('Content Description'),
|
||||
Forms\Components\TextInput::make('reason_for_export')
|
||||
->nullable()
|
||||
->label('Reason for Export'),
|
||||
])->columns(4),
|
||||
|
||||
Forms\Components\Section::make('Financial Info')
|
||||
@ -169,6 +161,11 @@ class ShipmentResource extends Resource
|
||||
->nullable()
|
||||
->prefix('ریال')
|
||||
->label('Net Rial'),
|
||||
Forms\Components\TextInput::make('cod_amount')
|
||||
->numeric()
|
||||
->default(0)
|
||||
->prefix('AED')
|
||||
->label('Cash on Delivery'),
|
||||
])->columns(4),
|
||||
|
||||
Forms\Components\Section::make('Sender Info')
|
||||
@ -193,6 +190,9 @@ class ShipmentResource extends Resource
|
||||
Forms\Components\TextInput::make('sender_city')
|
||||
->nullable()
|
||||
->label('City'),
|
||||
Forms\Components\TextInput::make('sender_state')
|
||||
->nullable()
|
||||
->label('State/Province'),
|
||||
Forms\Components\TextInput::make('sender_zip')
|
||||
->nullable()
|
||||
->label('ZIP'),
|
||||
@ -223,6 +223,9 @@ class ShipmentResource extends Resource
|
||||
Forms\Components\TextInput::make('receiver_city')
|
||||
->nullable()
|
||||
->label('City'),
|
||||
Forms\Components\TextInput::make('receiver_state')
|
||||
->nullable()
|
||||
->label('State/Province'),
|
||||
Forms\Components\TextInput::make('receiver_zip')
|
||||
->nullable()
|
||||
->label('ZIP'),
|
||||
@ -270,6 +273,10 @@ class ShipmentResource extends Resource
|
||||
->reorderable()
|
||||
->label('Items'),
|
||||
]),
|
||||
|
||||
Forms\Components\Section::make('Customer Notes')->schema([
|
||||
Forms\Components\Textarea::make('customer_notes')->nullable()->rows(3)->label('Customer Notes'),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -294,6 +301,8 @@ class ShipmentResource extends Resource
|
||||
->label('Type'),
|
||||
Tables\Columns\TextColumn::make('status')
|
||||
->badge()
|
||||
->color(fn (ShipmentStatus $state): string => $state->color())
|
||||
->formatStateUsing(fn (ShipmentStatus $state): string => $state->label())
|
||||
->searchable()
|
||||
->label('Status'),
|
||||
Tables\Columns\TextColumn::make('sender_name')
|
||||
@ -315,17 +324,9 @@ class ShipmentResource extends Resource
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('status')
|
||||
->options([
|
||||
'pending_payment' => 'در انتظار پرداخت',
|
||||
'cancelled' => 'لغو شده',
|
||||
'processed' => 'Processed',
|
||||
'picked_up' => 'Picked Up',
|
||||
'in_transit' => 'In Transit',
|
||||
'out_for_delivery' => 'Out For Delivery',
|
||||
'failed' => 'Failed',
|
||||
'delivered' => 'Delivered',
|
||||
'returned' => 'Returned',
|
||||
]),
|
||||
->options(collect(ShipmentStatus::cases())->mapWithKeys(fn ($case) => [
|
||||
$case->value => $case->label(),
|
||||
])),
|
||||
Tables\Filters\SelectFilter::make('type')
|
||||
->options([
|
||||
'DOC_NORMAL' => 'Doc Normal',
|
||||
|
||||
@ -15,9 +15,10 @@ class Shipment extends Model
|
||||
'warehousing_cost', 'vat_amount', 'discount', 'total_fee', 'net_dirham', 'net_rial',
|
||||
'from_country_id', 'to_country_id',
|
||||
'sender_name', 'sender_company', 'sender_phone', 'sender_email',
|
||||
'sender_address', 'sender_city', 'sender_zip', 'sender_id_number',
|
||||
'sender_address', 'sender_city', 'sender_state', 'sender_zip', 'sender_id_number',
|
||||
'receiver_name', 'receiver_company', 'receiver_phone', 'receiver_email',
|
||||
'receiver_address', 'receiver_city', 'receiver_zip', 'receiver_id_number',
|
||||
'receiver_address', 'receiver_city', 'receiver_state', 'receiver_zip', 'receiver_id_number',
|
||||
'customer_notes', 'cod_amount', 'declared_value', 'content_description',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
@ -26,6 +27,22 @@ class Shipment extends Model
|
||||
'direction' => \App\Enums\ShipmentDirection::class,
|
||||
'type' => \App\Enums\ShipmentType::class,
|
||||
'status' => \App\Enums\ShipmentStatus::class,
|
||||
'weight' => 'decimal:2',
|
||||
'volumetric_weight' => 'decimal:2',
|
||||
'chargeable_weight' => 'decimal:2',
|
||||
'shipping_price' => 'decimal:2',
|
||||
'extra_service' => 'decimal:2',
|
||||
'packing_cost' => 'decimal:2',
|
||||
'domestic_pickup' => 'decimal:2',
|
||||
'domestic_delivery' => 'decimal:2',
|
||||
'warehousing_cost' => 'decimal:2',
|
||||
'vat_amount' => 'decimal:2',
|
||||
'discount' => 'decimal:2',
|
||||
'total_fee' => 'decimal:2',
|
||||
'net_dirham' => 'decimal:2',
|
||||
'net_rial' => 'decimal:2',
|
||||
'cod_amount' => 'decimal:2',
|
||||
'declared_value' => 'decimal:2',
|
||||
];
|
||||
}
|
||||
|
||||
@ -58,4 +75,19 @@ class Shipment extends Model
|
||||
{
|
||||
return $this->hasMany(ShipmentTrackingEvent::class);
|
||||
}
|
||||
|
||||
public function isParcel(): bool
|
||||
{
|
||||
return $this->type === \App\Enums\ShipmentType::Parcel;
|
||||
}
|
||||
|
||||
public function isDocument(): bool
|
||||
{
|
||||
return $this->type !== \App\Enums\ShipmentType::Parcel;
|
||||
}
|
||||
|
||||
public function getInvoiceTotalUsdAttribute(): float
|
||||
{
|
||||
return $this->items->sum('total_usd');
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,10 +3,10 @@
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Shipment;
|
||||
use App\Models\ShipmentItem;
|
||||
use Dompdf\Dompdf;
|
||||
use Dompdf\Options;
|
||||
use Illuminate\Support\Collection;
|
||||
use Picqer\Barcode\BarcodeGeneratorHTML;
|
||||
|
||||
class PdfService
|
||||
{
|
||||
@ -19,12 +19,10 @@ class PdfService
|
||||
'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' => $this->generateBarcode($shipment->awb_no),
|
||||
];
|
||||
|
||||
$html = view('pdfs.awb', $data)->render();
|
||||
|
||||
return $this->generatePdf($html, 'A4', 'portrait');
|
||||
return $this->generatePdf(view('pdfs.awb', $data)->render(), 'A4', 'portrait');
|
||||
}
|
||||
|
||||
public function invoice(Shipment $shipment): string
|
||||
@ -35,13 +33,12 @@ class PdfService
|
||||
'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,
|
||||
'items' => $shipment->isParcel() ? ($shipment->items ?? collect()) : collect(),
|
||||
'invoice_total_usd' => $shipment->invoice_total_usd,
|
||||
'barcode' => $this->generateBarcode($shipment->awb_no),
|
||||
];
|
||||
|
||||
$html = view('pdfs.invoice', $data)->render();
|
||||
|
||||
return $this->generatePdf($html, 'A4', 'portrait');
|
||||
return $this->generatePdf(view('pdfs.invoice', $data)->render(), 'A4', 'portrait');
|
||||
}
|
||||
|
||||
public function label(Shipment $shipment): string
|
||||
@ -52,14 +49,13 @@ class PdfService
|
||||
'shipment' => $shipment,
|
||||
'shipper' => $this->formatAddress($shipment, 'sender'),
|
||||
'receiver' => $this->formatAddress($shipment, 'receiver'),
|
||||
'barcode' => $this->generateBarcode($shipment->awb_no),
|
||||
];
|
||||
|
||||
$html = view('pdfs.label', $data)->render();
|
||||
|
||||
return $this->generatePdf($html, 'A4', 'portrait');
|
||||
return $this->generatePdf(view('pdfs.label', $data)->render(), 'A4', 'portrait');
|
||||
}
|
||||
|
||||
private function generatePdf(string $html, array|string $paper, string $orientation): string
|
||||
private function generatePdf(string $html, string $paper, string $orientation): string
|
||||
{
|
||||
$options = new Options();
|
||||
$options->set('isHtml5ParserEnabled', true);
|
||||
@ -68,13 +64,7 @@ class PdfService
|
||||
|
||||
$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();
|
||||
@ -83,14 +73,25 @@ class PdfService
|
||||
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'},
|
||||
'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"},
|
||||
]);
|
||||
}
|
||||
|
||||
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>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
"laravel/tinker": "^2.10.1",
|
||||
"maatwebsite/excel": "^3.1",
|
||||
"morilog/jalali": "^3.0",
|
||||
"picqer/php-barcode-generator": "^3.3",
|
||||
"spatie/laravel-permission": "^6.25"
|
||||
},
|
||||
"require-dev": {
|
||||
|
||||
82
04_Laravel/composer.lock
generated
82
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": "b8dff8147ac7f29e9282deaf74979683",
|
||||
"content-hash": "a22b2cc91f8cab3cca421c6ab381c544",
|
||||
"packages": [
|
||||
{
|
||||
"name": "anourvalar/eloquent-serialize",
|
||||
@ -4914,6 +4914,86 @@
|
||||
],
|
||||
"time": "2025-12-27T19:41:33+00:00"
|
||||
},
|
||||
{
|
||||
"name": "picqer/php-barcode-generator",
|
||||
"version": "v3.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/picqer/php-barcode-generator.git",
|
||||
"reference": "6beada2a30623832ff41b8c7d307c169736b8552"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/picqer/php-barcode-generator/zipball/6beada2a30623832ff41b8c7d307c169736b8552",
|
||||
"reference": "6beada2a30623832ff41b8c7d307c169736b8552",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^9.5"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-bcmath": "Barcode IMB (Intelligent Mail Barcode) needs bcmath extension",
|
||||
"ext-gd": "For JPG and PNG generators, GD or Imagick is required",
|
||||
"ext-imagick": "For JPG and PNG generators, GD or Imagick is required"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Picqer\\Barcode\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-3.0-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Casper Bakker",
|
||||
"email": "info@picqer.com",
|
||||
"homepage": "https://picqer.com"
|
||||
},
|
||||
{
|
||||
"name": "Nicola Asuni",
|
||||
"email": "info@tecnick.com",
|
||||
"homepage": "http://nicolaasuni.tecnick.com"
|
||||
}
|
||||
],
|
||||
"description": "An easy to use, non-bloated, barcode generator in PHP. Creates SVG, PNG, JPG and HTML images from the most used 1D barcode standards.",
|
||||
"homepage": "https://github.com/picqer/php-barcode-generator",
|
||||
"keywords": [
|
||||
"CODABAR",
|
||||
"Code11",
|
||||
"Code93",
|
||||
"EAN13",
|
||||
"KIX",
|
||||
"KIXCODE",
|
||||
"MSI",
|
||||
"POSTNET",
|
||||
"Pharma",
|
||||
"Standard 2 of 5",
|
||||
"barcode",
|
||||
"barcode generator",
|
||||
"code128",
|
||||
"code39",
|
||||
"ean",
|
||||
"html",
|
||||
"jpeg",
|
||||
"jpg",
|
||||
"php",
|
||||
"png",
|
||||
"svg",
|
||||
"upc"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/picqer/php-barcode-generator/issues",
|
||||
"source": "https://github.com/picqer/php-barcode-generator/tree/v3.3.0"
|
||||
},
|
||||
"time": "2026-08-22T09:50:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/cache",
|
||||
"version": "3.0.0",
|
||||
|
||||
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('shipments', function (Blueprint $table) {
|
||||
// فیلدهای آدرس — State/Province
|
||||
$table->string('sender_state')->nullable()->after('sender_city');
|
||||
$table->string('receiver_state')->nullable()->after('receiver_city');
|
||||
|
||||
// یادداشت مشتری
|
||||
$table->text('customer_notes')->nullable()->after('receiver_id_number');
|
||||
|
||||
// Cash on Delivery (AED) — از اکسل مرجع: ستون AP = COD
|
||||
$table->decimal('cod_amount', 12, 2)->default(0)->unsigned()->after('net_rial');
|
||||
|
||||
// ارزش اظهار شده (USD) — از اکسل مرجع: ستون K = Value
|
||||
$table->decimal('declared_value', 12, 2)->nullable()->unsigned()->after('dimensions');
|
||||
|
||||
// توضیحات محموله — از اکسل مرجع: ستون L = Content
|
||||
$table->string('content_description')->nullable()->after('declared_value');
|
||||
});
|
||||
|
||||
// بهروزرسانی enum status برای افزودن مقادیر جدید
|
||||
DB::statement("ALTER TABLE shipments MODIFY COLUMN status ENUM(
|
||||
'pending_payment',
|
||||
'cancelled',
|
||||
'processed',
|
||||
'picked_up',
|
||||
'in_transit',
|
||||
'out_for_delivery',
|
||||
'failed',
|
||||
'delivered',
|
||||
'returned',
|
||||
'archived'
|
||||
) DEFAULT 'processed'");
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('shipments', function (Blueprint $table) {
|
||||
$table->dropColumn([
|
||||
'sender_state',
|
||||
'receiver_state',
|
||||
'customer_notes',
|
||||
'cod_amount',
|
||||
'declared_value',
|
||||
'content_description',
|
||||
]);
|
||||
});
|
||||
|
||||
DB::statement("ALTER TABLE shipments MODIFY COLUMN status ENUM(
|
||||
'pending_payment',
|
||||
'cancelled',
|
||||
'processed',
|
||||
'picked_up',
|
||||
'in_transit',
|
||||
'out_for_delivery',
|
||||
'failed',
|
||||
'delivered',
|
||||
'returned'
|
||||
) DEFAULT 'pending_payment'");
|
||||
}
|
||||
};
|
||||
@ -1,69 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
AWB PDF TEMPLATE — TODO: تطبیق نهایی با قالب اکسل
|
||||
|
||||
Excel Reference: شیت «AWB» در فایل 01KYWGVNKS5TNMN37RV77PCYNZ.xlsx
|
||||
|
||||
Cell Mapping:
|
||||
- A1: لوگوی IFNEX (image2.png, 169x159px)
|
||||
- C1:D1: «Shipment Waybill»
|
||||
- K11:K12: AWB Number (lookup key)
|
||||
- A6:A13: SHIPPER section
|
||||
- D6:D13: RECEIVER section
|
||||
- A14:A22: SHIPMENT section
|
||||
- D14:D22: PAYMENT section
|
||||
- D24:E24: Shipper Signature line
|
||||
- D28:E28: Track URL
|
||||
|
||||
VLOOKUP Source: شیت «List» (A3:AD3938)
|
||||
|
||||
Required Fields:
|
||||
- Shipper: Name, Company, Phone, Email, Address, City, Zip, ID Number
|
||||
- Receiver: Name, Company, Phone, Email, Address, City, Zip
|
||||
- Shipment: Gross Weight, Volumetric Weight, Chargeable Weight, Dimensions, Service Type, Direction, From/To Country, Forwarder, Content
|
||||
- Payment: Shipping Price (AED), Extra Service (IRR), Packing Cost (IRR), Domestic Pickup (IRR), Domestic Delivery (IRR), Warehousing Cost (IRR), Discount (IRR), Total Fee (IRR), Net Dirham (AED)
|
||||
|
||||
Font: باید فونت رسمی IFNEX استفاده شود (در حال حاضر DejaVu Sans جایگزین شده)
|
||||
Colors: عنوانها نارنجی #f37021، حاشیهها #ddd، پسزمینه مالی #f9f9f9
|
||||
Page: A4 Portrait
|
||||
-->
|
||||
<html lang="fa" dir="rtl">
|
||||
<html lang="en" dir="ltr">
|
||||
<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 { width: 210mm; min-height: 297mm; padding: 15mm; margin: 0 auto; }
|
||||
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; }
|
||||
|
||||
.header { display: flex; align-items: center; margin-bottom: 15px; border-bottom: 2px solid #f37021; padding-bottom: 10px; }
|
||||
.logo { width: 60px; height: auto; margin-left: 10px; }
|
||||
.title-area { flex: 1; }
|
||||
.title { font-size: 18px; font-weight: bold; color: #f37021; }
|
||||
.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; }
|
||||
|
||||
.awb-number { font-size: 14px; font-weight: bold; color: #f37021; margin-top: 5px; }
|
||||
.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; }
|
||||
|
||||
.main-content { display: flex; gap: 15px; margin-bottom: 15px; }
|
||||
.section { flex: 1; 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 { margin-bottom: 4px; }
|
||||
.label { font-weight: bold; color: #555; display: inline-block; width: 100px; }
|
||||
.value { display: inline-block; }
|
||||
|
||||
.shipment-details { border: 1px solid #ddd; padding: 10px; margin-bottom: 15px; }
|
||||
.shipment-title { font-weight: bold; color: #f37021; margin-bottom: 8px; font-size: 12px; }
|
||||
|
||||
.payment-section { border: 1px solid #ddd; padding: 10px; margin-bottom: 15px; background-color: #f9f9f9; }
|
||||
.payment-title { font-weight: bold; color: #f37021; margin-bottom: 8px; font-size: 12px; }
|
||||
|
||||
.two-col { display: flex; gap: 20px; }
|
||||
.two-col { display: flex; gap: 10px; margin-bottom: 10px; }
|
||||
.col { flex: 1; }
|
||||
|
||||
.signature { margin-top: 20px; display: flex; justify-content: space-between; }
|
||||
.row { margin-bottom: 3px; }
|
||||
.label { font-weight: bold; color: #555; display: inline-block; width: 110px; }
|
||||
.value { display: inline-block; }
|
||||
|
||||
.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; }
|
||||
|
||||
.total-row { font-weight: bold; color: #f37021; font-size: 12px; border-top: 1px solid #ddd; padding-top: 4px; margin-top: 4px; }
|
||||
|
||||
.disclaimer { background-color: #fafafa; border: 1px solid #eee; padding: 8px; margin-bottom: 10px; font-size: 8px; color: #666; line-height: 1.4; }
|
||||
|
||||
.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 { margin-top: 15px; text-align: center; font-size: 9px; color: #999; border-top: 1px solid #ddd; padding-top: 10px; }
|
||||
.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; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -72,73 +46,112 @@
|
||||
<img src="{{ public_path('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>
|
||||
<div class="awb-number">AWB No: {{ $shipment->awb_no }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<table>
|
||||
<tr>
|
||||
<td width="50%" style="padding-right: 5px;">
|
||||
<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">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="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">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 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">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">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="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">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 class="row"><span class="label">Zip Code:</span><span class="value">{{ $receiver['zip'] }}</span></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="shipment-details">
|
||||
<div class="shipment-title">SHIPMENT</div>
|
||||
<div class="two-col">
|
||||
<div class="col">
|
||||
<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>
|
||||
<table>
|
||||
<tr>
|
||||
<td width="50%" style="padding-right: 5px;">
|
||||
<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">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">Direction:</span><span class="value">{{ $shipment->direction?->label() }}</span></div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<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="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>
|
||||
|
||||
@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;">
|
||||
<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>
|
||||
</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>
|
||||
</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 (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" style="font-weight: bold; color: #f37021; font-size: 12px;"><span class="label">Total Fee (IRR):</span><span class="value">{{ number_format($shipment->total_fee ?? 0) }}</span></div>
|
||||
<div class="row" style="font-weight: bold; color: #f37021; font-size: 12px;"><span class="label">Net Dirham (AED):</span><span class="value">{{ number_format($shipment->net_dirham ?? 0, 2) }}</span></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>
|
||||
</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 class="signature">
|
||||
<div class="signature-box">
|
||||
Shipper Name & Signature<br>
|
||||
Date: {{ now()->format('Y-m-d') }}
|
||||
Date: ....../....../........
|
||||
</div>
|
||||
<div class="signature-box">
|
||||
Track Your Shipment at:<br>
|
||||
|
||||
@ -1,157 +1,164 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
INVOICE PDF TEMPLATE — TODO: تطبیق نهایی با قالب اکسل
|
||||
|
||||
Excel Reference: شیت «INVOICE + label» در فایل 01KYWGVNKS5TNMN37RV77PCYNZ.xlsx
|
||||
|
||||
Cell Mapping:
|
||||
- B1: «INVOICE»
|
||||
- E2:F2: DATE
|
||||
- F3: INVOICE NO (AWB number, lookup key)
|
||||
- A4:B9: SHIPPER info (Name, Company, Contact, Address, City/Zip, Email)
|
||||
- A10:B16: CONSIGNEE info
|
||||
- A18:F18: Content description
|
||||
- A20:F20: Items table headers (NO, DESCRIPTION, H.S. Code, QUANTITY, UNIT PRICE, TOTAL IN USD)
|
||||
- A21:F29: 9 item rows (VLOOKUP from List sheet)
|
||||
- C30:F30: TOTAL INVOICE AMOUNT IN USD
|
||||
- A31:B32: Weight & Dimension
|
||||
- A34:B34: REASON FOR EXPORT
|
||||
- A36:F36: Legal declaration
|
||||
- F40:F45: AWB echo, Weight, Origin/Destination country
|
||||
|
||||
VLOOKUP Source: شیت «List» (A3:CH3938)
|
||||
|
||||
Required Fields:
|
||||
- Shipper: Name, Company, Contact, Address, City/Zip, Email
|
||||
- Consignee: Name, Company, Contact, Address, City/Zip, Email
|
||||
- Content: Description, Reason for Export
|
||||
- Items: up to 9 rows (Description, HS Code, Quantity, Unit Price, Total USD)
|
||||
- Shipment: Weight, Volumetric Weight, Dimensions, Service, Direction
|
||||
- Total Invoice Amount in USD
|
||||
|
||||
Font: باید فونت رسمی IFNEX استفاده شود
|
||||
Colors: عنوانها نارنجی #f37021، جدولها با حاشیه #ddd
|
||||
Page: A4 Portrait
|
||||
-->
|
||||
<html lang="fa" dir="rtl">
|
||||
<html lang="en" dir="ltr">
|
||||
<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; }
|
||||
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; }
|
||||
|
||||
.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; }
|
||||
|
||||
.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; }
|
||||
|
||||
.two-col { display: flex; gap: 10px; margin-bottom: 10px; }
|
||||
.col { flex: 1; }
|
||||
|
||||
.row { margin-bottom: 3px; }
|
||||
.label { font-weight: bold; color: #555; display: inline-block; width: 110px; }
|
||||
.value { display: inline-block; }
|
||||
|
||||
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; }
|
||||
th, td { border: 1px solid #ddd; padding: 5px; text-align: left; font-size: 10px; }
|
||||
th { background-color: #f5f5f5; font-weight: bold; color: #333; }
|
||||
|
||||
.content-box { border: 1px solid #ccc; padding: 8px; margin-bottom: 10px; }
|
||||
.content-title { font-weight: bold; color: #f37021; margin-bottom: 6px; font-size: 12px; }
|
||||
|
||||
.info-row { display: flex; justify-content: space-between; margin-bottom: 10px; }
|
||||
.info-box { flex: 1; border: 1px solid #ccc; padding: 8px; }
|
||||
|
||||
.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; }
|
||||
|
||||
.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; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>INVOICE / فاکتور</h1>
|
||||
<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>
|
||||
<span>INVOICE NO: {{ $shipment->awb_no }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="two-col">
|
||||
<div class="col section">
|
||||
<div class="section-title">SHIPPER / فرستنده</div>
|
||||
<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">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">City/Zip:</span><span class="value">{{ $shipper['city'] }} {{ $shipper['zip'] }}</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="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">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">City/Zip:</span><span class="value">{{ $receiver['city'] }} {{ $receiver['zip'] }}</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="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 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">CONTENT / محموله</div>
|
||||
<p style="margin-bottom: 10px;">{{ $shipment->reason_for_export ?: 'General Goods' }}</p>
|
||||
<div class="section-title">SHIPMENT ITEMS</div>
|
||||
<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>
|
||||
<th style="width: 40px;">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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($items as $item)
|
||||
@foreach($items as $item)
|
||||
<tr>
|
||||
<td class="text-center">{{ $item->row_number }}</td>
|
||||
<td style="text-align: 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>
|
||||
<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>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="6" class="text-center">No items</td></tr>
|
||||
@endforelse
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
<div style="margin-top: 10px; text-align: left;">
|
||||
<strong>TOTAL INVOICE AMOUNT IN USD: {{ number_format($invoice_total_usd, 2) }}</strong>
|
||||
<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>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
<div class="declaration">
|
||||
I HEREBY STATE THAT THE ABOVE INFORMATION IS TRUE AND CORRECT TO THE BEST OF MY KNOWLEDGE.
|
||||
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">
|
||||
<div class="signature-box">
|
||||
NAME - SIGNATURE
|
||||
</div>
|
||||
<div class="signature-box">
|
||||
NAME - SIGNATURE
|
||||
</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 !!}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
IFNEX Logistics — We Deliver Value | AWB: {{ $shipment->awb_no }} | Generated on {{ now()->format('Y-m-d H:i') }}
|
||||
IFNEX Logistics — We Deliver Value | Generated on {{ now()->format('Y-m-d H:i') }}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@ -1,75 +1,49 @@
|
||||
<!DOCTYPE html>
|
||||
<!--
|
||||
LABEL PDF TEMPLATE — TODO: تطبیق نهایی با قالب اکسل
|
||||
|
||||
Excel Reference: شیت «label» در فایل 01KYWGVNKS5TNMN37RV77PCYNZ.xlsx
|
||||
|
||||
Cell Mapping:
|
||||
- C1: AWB number echo
|
||||
- C3: AWB Number (lookup key)
|
||||
- J2: Weight in KG
|
||||
- J3: Weight echo
|
||||
- A2:B2: Gross Weight
|
||||
- A4: W*L*H formula
|
||||
- J4: Origin Country
|
||||
- J5: Destination Country
|
||||
- J7: Destination Country Code
|
||||
- A7:B7: Volumetric Weight
|
||||
|
||||
VLOOKUP Source: شیت «List»
|
||||
|
||||
Required Fields:
|
||||
- AWB Number ( prominently displayed )
|
||||
- Shipper: Name, Company, Phone, Address
|
||||
- Receiver: Name, Company, Phone, Address
|
||||
- Shipment: Gross Weight, Volumetric Weight, Dimensions, From/To Country
|
||||
|
||||
Current Implementation: A4 size for laser printer + sticker paper
|
||||
Original Template: Compact thermal label format
|
||||
|
||||
TODO: اگر اندازه لیبل کوچکتر (مثل 100x150mm) نیاز است، صفحهبندی را تغییر دهید.
|
||||
TODO: فونت و اندازه متن را طبق پرینتر لیبلزن تنظیم کنید.
|
||||
TODO: در صورت نیاز به بارکد/QR، کتابخانه بارکد اضافه کنید.
|
||||
|
||||
Font: باید فونت رسمی IFNEX استفاده شود
|
||||
Colors: لوگو نارنجی #f37021
|
||||
Page: فعلی A4 Portrait — قابل تغییر به اندازه سفارشی
|
||||
-->
|
||||
<html lang="fa" dir="rtl">
|
||||
<html lang="en" dir="ltr">
|
||||
<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: rtl; text-align: right; }
|
||||
.page { width: 210mm; min-height: 297mm; padding: 10mm; margin: 0 auto; }
|
||||
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: 2px solid #333;
|
||||
border: 3px solid #000;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
page-break-inside: avoid;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header { display: flex; align-items: center; border-bottom: 2px solid #f37021; padding-bottom: 10px; margin-bottom: 15px; }
|
||||
.logo { width: 50px; height: auto; margin-left: 10px; }
|
||||
.company-info { flex: 1; }
|
||||
.company-name { font-size: 16px; font-weight: bold; color: #f37021; }
|
||||
.awb-display { font-size: 18px; font-weight: bold; background: #f37021; color: white; padding: 5px 15px; display: inline-block; margin-top: 5px; }
|
||||
.logo { width: 70px; height: auto; }
|
||||
.company-name { font-size: 18px; font-weight: bold; color: #f37021; margin-left: 10px; }
|
||||
|
||||
.grid { display: flex; flex-wrap: wrap; gap: 15px; }
|
||||
.col { flex: 1; min-width: 45%; }
|
||||
.awb-badge {
|
||||
background-color: #f37021;
|
||||
color: #fff;
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding: 8px;
|
||||
margin-bottom: 15px;
|
||||
letter-spacing: 3px;
|
||||
}
|
||||
|
||||
.section-title { font-weight: bold; color: #f37021; font-size: 11px; margin-bottom: 8px; text-transform: uppercase; }
|
||||
.barcode { text-align: center; margin-bottom: 15px; }
|
||||
|
||||
.row { margin-bottom: 5px; }
|
||||
.grid { display: flex; gap: 15px; margin-bottom: 15px; }
|
||||
.col { flex: 1; }
|
||||
|
||||
.section-title { font-weight: bold; color: #f37021; font-size: 12px; margin-bottom: 8px; text-transform: uppercase; border-bottom: 1px solid #eee; padding-bottom: 3px; }
|
||||
|
||||
.row { margin-bottom: 4px; }
|
||||
.label { font-weight: bold; color: #555; display: inline-block; width: 100px; }
|
||||
.value { display: inline-block; }
|
||||
|
||||
.shipment-info { background-color: #f9f9f9; padding: 10px; border: 1px solid #ddd; margin-top: 15px; }
|
||||
.shipment-grid { display: flex; flex-wrap: wrap; gap: 10px; }
|
||||
.shipment-item { flex: 1; min-width: 45%; }
|
||||
.shipment-info { background-color: #f9f9f9; padding: 10px; border: 1px solid #ddd; margin-bottom: 15px; }
|
||||
.shipment-grid { display: flex; gap: 15px; }
|
||||
.shipment-item { flex: 1; }
|
||||
|
||||
.footer { margin-top: 15px; text-align: center; font-size: 10px; color: #666; border-top: 1px solid #ddd; padding-top: 10px; }
|
||||
.footer { text-align: center; font-size: 10px; color: #666; border-top: 1px solid #ddd; padding-top: 10px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -77,22 +51,27 @@
|
||||
<div class="label-box">
|
||||
<div class="header">
|
||||
<img src="{{ public_path('logo.png') }}" class="logo" alt="IFNEX Logo">
|
||||
<div class="company-info">
|
||||
<div class="company-name">IFNEX LOGISTICS</div>
|
||||
<div class="awb-display">{{ $shipment->awb_no }}</div>
|
||||
</div>
|
||||
|
||||
<div class="awb-badge">
|
||||
{{ $shipment->awb_no }}
|
||||
</div>
|
||||
|
||||
<div class="barcode">
|
||||
{!! $barcode !!}
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="col">
|
||||
<div class="section-title">SHIPPER / فرستنده</div>
|
||||
<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="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>
|
||||
@ -104,9 +83,9 @@
|
||||
<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:</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">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>
|
||||
</div>
|
||||
<div class="shipment-item">
|
||||
<div class="row"><span class="label">From:</span><span class="value">{{ $shipment->fromCountry?->name }}</span></div>
|
||||
@ -117,7 +96,7 @@
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
IFNEX Logistics | We Deliver Value | {{ $shipment->awb_no }}
|
||||
IFNEX Logistics — We Deliver Value
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user