ifnex/04_Laravel/app/Services/PdfService.php
Kazem Alghasi f12dff5338 - Add picqer/php-barcode-generator dependency
- Redesign AWB PDF with barcode, From/To, Value, Service, Type
- Redesign Invoice PDF with 9-row table, legal declaration, barcode
- Redesign Label PDF to A5 landscape with large barcode
- Add DOC/PARCEL condition for Invoice (only PARCEL has invoice)
- Fix ShipmentType enum names (DocNormal/DocEconomy)
- Add migration for shipment_items (row_number, unit_price)
- Add migration to make name nullable
- Update PdfService with base64 barcode embedding
- Update ShipmentPdfController with DOC type handling
- Add test_pdf_generation.php script

Phase 3.5 — PDF redesign complete"
2026-08-29 06:40:08 +03:30

180 lines
6.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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'},
]);
}
}