- Redesign AWB PDF with barcode, From/To, Value, Service, Type - Redesign Invoice PDF with 9-row table, legal declaration, barcode - Redesign Label PDF to A5 landscape with large barcode - Add DOC/PARCEL condition for Invoice (only PARCEL has invoice) - Fix ShipmentType enum names (DocNormal/DocEconomy) - Add migration for shipment_items (row_number, unit_price) - Add migration to make name nullable - Update PdfService with base64 barcode embedding - Update ShipmentPdfController with DOC type handling - Add test_pdf_generation.php script Phase 3.5 — PDF redesign complete"
180 lines
6.1 KiB
PHP
180 lines
6.1 KiB
PHP
<?php
|
||
|
||
namespace App\Services;
|
||
|
||
use App\Enums\ShipmentType;
|
||
use App\Models\Shipment;
|
||
use App\Models\ShipmentItem;
|
||
use Dompdf\Dompdf;
|
||
use Dompdf\Options;
|
||
use Illuminate\Support\Collection;
|
||
use Illuminate\Support\Facades\Log;
|
||
use Picqer\Barcode\BarcodeGeneratorPNG;
|
||
|
||
class PdfService
|
||
{
|
||
/**
|
||
* تولید AWB PDF (برای همه نوع محمولهها)
|
||
*/
|
||
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,
|
||
'barcode_base64' => $this->generateBarcode($shipment->awb_no),
|
||
'service_label' => $this->getServiceLabel($shipment),
|
||
'type_label' => $this->getTypeLabel($shipment),
|
||
];
|
||
|
||
$html = view('pdfs.awb', $data)->render();
|
||
|
||
return $this->generatePdf($html, 'A4', 'portrait');
|
||
}
|
||
|
||
/**
|
||
* تولید Invoice PDF — فقط برای PARCEL و DOC_NORMAL/DOC_ECONOMY
|
||
* (نه برای محمولههای DOC خالص که کالا ندارن)
|
||
*/
|
||
public function invoice(Shipment $shipment): string
|
||
{
|
||
// اگه محموله DOC هست و آیتم نداره، invoice تولید نکن
|
||
if ($shipment->type === ShipmentType::DocNormal || $shipment->type === ShipmentType::DocEconomy) {
|
||
if ($shipment->items->isEmpty()) {
|
||
throw new \InvalidArgumentException(
|
||
'فاکتور فقط برای محمولههای دارای کالا (PARCEL) صادر میشود. محمولههای DOC بدون کالا فاکتور ندارند.'
|
||
);
|
||
}
|
||
}
|
||
|
||
$shipment->loadMissing(['fromCountry', 'toCountry', 'items']);
|
||
|
||
$data = [
|
||
'shipment' => $shipment,
|
||
'shipper' => $this->formatAddress($shipment, 'sender'),
|
||
'receiver' => $this->formatAddress($shipment, 'receiver'),
|
||
'items' => $shipment->items ?? collect(),
|
||
'invoice_total_usd' => optional($shipment->items)->sum('total_usd') ?? 0,
|
||
'barcode_base64' => $this->generateBarcode($shipment->awb_no),
|
||
];
|
||
|
||
$html = view('pdfs.invoice', $data)->render();
|
||
|
||
return $this->generatePdf($html, 'A4', 'portrait');
|
||
}
|
||
|
||
/**
|
||
* تولید Label PDF — اندازه استاندارد لیبل پستی (100x150mm)
|
||
*/
|
||
public function label(Shipment $shipment): string
|
||
{
|
||
$shipment->loadMissing(['fromCountry', 'toCountry']);
|
||
|
||
$data = [
|
||
'shipment' => $shipment,
|
||
'barcode_base64' => $this->generateBarcode($shipment->awb_no),
|
||
'origin_iso' => $shipment->fromCountry?->iso_code ?? 'IR',
|
||
'dest_iso' => $shipment->toCountry?->iso_code ?? '',
|
||
];
|
||
|
||
$html = view('pdfs.label', $data)->render();
|
||
|
||
// A5 افقی (Landscape) — استاندارد لیبل پستی: 210×148 mm
|
||
return $this->generatePdf($html, 'A5', 'landscape');
|
||
}
|
||
|
||
/**
|
||
* تولید بارکد از AWB number — base64 embed (مطمئنترین روش)
|
||
*/
|
||
private function generateBarcode(string $awbNo): string
|
||
{
|
||
try {
|
||
$generator = new BarcodeGeneratorPNG();
|
||
$barcode = $generator->getBarcode($awbNo, $generator::TYPE_CODE_128, 2, 40);
|
||
|
||
// تبدیل به base64 و استفاده از data URI
|
||
return 'data:image/png;base64,' . base64_encode($barcode);
|
||
} catch (\Throwable $e) {
|
||
Log::error('Barcode generation failed', [
|
||
'awb' => $awbNo,
|
||
'error' => $e->getMessage(),
|
||
]);
|
||
return '';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* برچسب Service برای AWB (Outbound/Inbound)
|
||
*/
|
||
private function getServiceLabel(Shipment $shipment): string
|
||
{
|
||
return match ($shipment->direction?->value) {
|
||
'export' => 'Outbound',
|
||
'import' => 'Inbound',
|
||
default => 'Outbound',
|
||
};
|
||
}
|
||
|
||
/**
|
||
* برچسب Type برای AWB (NON DOC / DOC)
|
||
*/
|
||
private function getTypeLabel(Shipment $shipment): string
|
||
{
|
||
return match ($shipment->type) {
|
||
ShipmentType::Parcel => 'NON DOC',
|
||
ShipmentType::DocNormal => 'DOC NORMAL',
|
||
ShipmentType::DocEconomy => 'DOC ECONOMY',
|
||
default => 'NON DOC',
|
||
};
|
||
}
|
||
|
||
/**
|
||
* تولید PDF از HTML
|
||
*/
|
||
private function generatePdf(string $html, array|string $paper, string $orientation): string
|
||
{
|
||
$options = new Options();
|
||
$options->set('isHtml5ParserEnabled', true);
|
||
$options->set('isRemoteEnabled', true); // مهم: برای data URI
|
||
$options->set('defaultFont', 'DejaVu Sans');
|
||
$options->set('dpi', 150);
|
||
$options->set('debugKeepTemp', false);
|
||
$options->set('debugCss', false);
|
||
$options->set('debugLayout', false);
|
||
|
||
$dompdf = new Dompdf($options);
|
||
$dompdf->loadHtml($html);
|
||
|
||
if (is_array($paper)) {
|
||
$dompdf->setPaper($paper, $orientation);
|
||
} else {
|
||
$dompdf->setPaper($paper, $orientation);
|
||
}
|
||
|
||
$dompdf->render();
|
||
|
||
return $dompdf->output();
|
||
}
|
||
|
||
/**
|
||
* فرمتبندی آدرس فرستنده/گیرنده
|
||
*/
|
||
private function formatAddress(Shipment $shipment, string $prefix): Collection
|
||
{
|
||
return collect([
|
||
'name' => $shipment->{$prefix . '_name'},
|
||
'company' => $shipment->{$prefix . '_company'},
|
||
'phone' => $shipment->{$prefix . '_phone'},
|
||
'email' => $shipment->{$prefix . '_email'},
|
||
'address' => $shipment->{$prefix . '_address'},
|
||
'city' => $shipment->{$prefix . '_city'},
|
||
'zip' => $shipment->{$prefix . '_zip'},
|
||
'id_number' => $shipment->{$prefix . '_id_number'},
|
||
]);
|
||
}
|
||
}
|