Feat: Shipping functionalities redesign, elta courier integration, commenting out acs since no integration can happen, flow updates
This commit is contained in:
@@ -4,6 +4,8 @@ namespace Modules\Core\Shipping\Carriers\Elta;
|
||||
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
use Modules\Core\Shipping\Models\Shipment;
|
||||
use Picqer\Barcode\BarcodeGeneratorPNG;
|
||||
|
||||
@@ -28,15 +30,88 @@ use Picqer\Barcode\BarcodeGeneratorPNG;
|
||||
*/
|
||||
class EltaLabelRenderer
|
||||
{
|
||||
/**
|
||||
* ELTA's own wording for each surcharge, as its client prints them in
|
||||
* the label's "Πρόσθετες Υπηρεσίες" box (Sydeta.cs::print_vg()).
|
||||
*/
|
||||
private const SERVICE_TEXT = [
|
||||
'special_handling' => '002 ΕΙΔΙΚΗ ΔΙΑΧΕΙΡΗΣΗ',
|
||||
'timed_delivery' => '003 ΠΡΟΚΑΘΟΡΙΣΜΕΝΗ ΩΡΑ',
|
||||
'saturday_delivery' => '004 ΕΠΙΔΟΣΗ ΣΑΒΒΑΤΟΥ',
|
||||
];
|
||||
|
||||
public function __construct(private readonly AreaResolver $areaResolver) {}
|
||||
|
||||
public function render(Shipment $shipment): string
|
||||
{
|
||||
return $this->renderMany(collect([$shipment]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Several labels in one PDF (the Pending Vouchers screen's "Print
|
||||
* selected"). Each label view is a full page — a single `.page` block
|
||||
* inside <body> — so the pages are stacked under the first view's
|
||||
* <head> (the styles are the same for every label) with a page break
|
||||
* between them.
|
||||
*
|
||||
* @param Collection<int, Shipment> $shipments
|
||||
*/
|
||||
public function renderMany(Collection $shipments): string
|
||||
{
|
||||
$paperSize = config('elta.label_paper_size', 'a4');
|
||||
$view = $paperSize === 'a6'
|
||||
? 'core::shipping.carriers.elta.label-a6'
|
||||
: 'core::shipping.carriers.elta.label-a4';
|
||||
|
||||
$head = null;
|
||||
$pages = [];
|
||||
|
||||
foreach ($shipments as $shipment) {
|
||||
$html = view($view, $this->data($shipment))->render();
|
||||
|
||||
$head ??= Str::before($html, '<body');
|
||||
$pages[] = Str::beforeLast(Str::after(Str::after($html, '<body'), '>'), '</body>');
|
||||
}
|
||||
|
||||
$html = $head
|
||||
.'<style>.page { page-break-after: always; } .page:last-child { page-break-after: auto; }</style>'
|
||||
.'<body>'.implode('', $pages).'</body></html>';
|
||||
|
||||
$pdf = Pdf::loadHTML($html);
|
||||
|
||||
// A6 = 104mm x 148mm, per RDLCPrinter.cs's own DeviceInfo
|
||||
// override for printer_size==2 (1mm ≈ 2.8346pt) — note this is
|
||||
// ELTA's own thermal-label size, not the ISO A6 (105x148mm). A
|
||||
// 4-value paper array plus an orientation string together confuse
|
||||
// dompdf into doubling the canvas and silently overflowing to a
|
||||
// second blank page; the array alone is already portrait (height >
|
||||
// width), so no orientation argument.
|
||||
$paperSize === 'a6'
|
||||
? $pdf->setPaper([0, 0, 294.80, 419.53])
|
||||
: $pdf->setPaper('a4');
|
||||
|
||||
return $pdf->output();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function data(Shipment $shipment): array
|
||||
{
|
||||
$order = $shipment->order;
|
||||
$address = $order->shippingAddress;
|
||||
$meta = $shipment->meta?->toArray() ?? [];
|
||||
|
||||
$destinationStation = $this->areaResolver->resolve($address->postcode);
|
||||
// Issuing a pending voucher (PELVG01NEW1) returns the real
|
||||
// destination station and service; vouchers issued before that flow
|
||||
// existed don't have them, so fall back to the postcode lookup.
|
||||
$station = filled($meta['rec_station'] ?? null)
|
||||
? ['code' => $meta['rec_station'], 'name' => $meta['rec_station_t'] ?? '']
|
||||
: (fn ($s) => ['code' => $s->code, 'name' => $s->name])($this->areaResolver->resolve($address->postcode));
|
||||
|
||||
$services = (array) ($meta['services'] ?? []);
|
||||
$surcharges = $this->surchargeSlots($services);
|
||||
$cod = $this->codFields(isset($meta['cod_amount']) ? (float) $meta['cod_amount'] : null);
|
||||
|
||||
$dateTime = (string) ($meta['date_time'] ?? '');
|
||||
$occurredAt = strlen($dateTime) >= 12
|
||||
@@ -49,7 +124,7 @@ class EltaLabelRenderer
|
||||
|
||||
$generator = new BarcodeGeneratorPNG();
|
||||
|
||||
$data = [
|
||||
return [
|
||||
'sender_name' => config('elta.sender_name'),
|
||||
'sender_address' => config('elta.sender_address'),
|
||||
'sender_postcode' => config('elta.sender_postcode'),
|
||||
@@ -72,6 +147,7 @@ class EltaLabelRenderer
|
||||
// report's 40-char wrap width, which our own data never hits
|
||||
// in practice for a name/address/contact triple).
|
||||
'sender_lines' => [
|
||||
'Κωδικός:'.config('elta.apost_code'),
|
||||
config('elta.sender_name'),
|
||||
config('elta.sender_address'),
|
||||
'TK:'.config('elta.sender_postcode').' '.config('elta.sender_area').' ΤΗΛ:'.config('elta.sender_phone'),
|
||||
@@ -95,13 +171,14 @@ class EltaLabelRenderer
|
||||
? sprintf('001/%03d', $packageCount)
|
||||
: '001',
|
||||
'multiPiece' => $packageCount > 1,
|
||||
'polaplo' => $packageCount > 1 ? '* ΠΟΛΛΑΠΛΗ ΑΠΟΣΤΟΛΗ *' : null,
|
||||
|
||||
'station_apo' => config('elta.origin_station_code'),
|
||||
'station_pros' => $destinationStation->code,
|
||||
'station_pros_title' => $destinationStation->name,
|
||||
'station_pros' => $station['code'],
|
||||
'station_pros_title' => $station['name'],
|
||||
|
||||
'service_code' => '214',
|
||||
'service_name' => 'ΠΟΛΗ ΠΟΛΗ -ΠΘ- ΝΗΣΙ',
|
||||
'service_code' => $meta['rec_srv'] ?? '',
|
||||
'service_name' => $meta['rec_srv_t'] ?? '',
|
||||
|
||||
'xreosi' => 'ΧΡΕΩΣΗ ΑΠΟΣΤΟΛΕΑ ΠΙΣΤΩΣΗ',
|
||||
'siimvasi' => '131775-9',
|
||||
@@ -114,46 +191,95 @@ class EltaLabelRenderer
|
||||
// yields reference "1426000981436" (13 chars after the '>').
|
||||
'ocr_reference' => strlen($ocrLine) >= 14 ? substr($ocrLine, 1, 13) : '',
|
||||
|
||||
'order_reference' => (string) $order->id,
|
||||
// The reference we send ELTA as pel_ref_no (order reference;
|
||||
// older vouchers were sent the order id).
|
||||
'order_reference' => (string) $order->reference,
|
||||
|
||||
// sydetaE.rdlc's "Πρόσθετες Υπηρεσίες" (sur_1..4) and the
|
||||
// ΑΝΤΙΚΑΤΑΒΟΛΗ banner (antik_1) are only populated by ELTA's
|
||||
// own client when the shipper ticks special-service checkboxes
|
||||
// (Sydeta.cs::print_vg()'s checkBox2-5) or a COD amount is set
|
||||
// — neither is tracked on our Shipment model today, so these
|
||||
// render as empty boxes, matching what a plain "credit" send
|
||||
// with no extras or COD looks like on a real label.
|
||||
'sur_1' => null,
|
||||
'sur_2' => null,
|
||||
'sur_3' => null,
|
||||
// sydetaE.rdlc's "Πρόσθετες Υπηρεσίες" (sur_1..4): the ticked
|
||||
// extra services, in ELTA's own wording and slots.
|
||||
'sur_1' => $surcharges[1],
|
||||
'sur_2' => $surcharges[2],
|
||||
'sur_3' => $surcharges[3],
|
||||
'sur_4' => null,
|
||||
'antik_1' => $meta['cod_amount'] ?? null ? '* ΠΡΟΣΟΧΗ ΑΝΤΙΚΑΤΑΒΟΛΗ *' : null,
|
||||
...$cod,
|
||||
// ogos_2: "LxWxH = volume", only when dimensions were given —
|
||||
// we never send any, so the box stays empty like the client's.
|
||||
'volumetric_weight' => $meta['volumetric_weight'] ?? null,
|
||||
|
||||
'eltaLogo' => $this->logoDataUri(),
|
||||
];
|
||||
}
|
||||
|
||||
$paperSize = config('elta.label_paper_size', 'a4');
|
||||
$view = $paperSize === 'a6'
|
||||
? 'core::shipping.carriers.elta.label-a6'
|
||||
: 'core::shipping.carriers.elta.label-a4';
|
||||
/**
|
||||
* The client's own sur_1..3 placement (Sydeta.cs::print_vg()): 002 is
|
||||
* always sur_1; 003 is always sur_2; 004 is sur_3 after 003, else
|
||||
* sur_2. Slots can stay empty (003 alone leaves sur_1 blank).
|
||||
*
|
||||
* @param array<int, string> $services ExtraService values
|
||||
* @return array{1: ?string, 2: ?string, 3: ?string}
|
||||
*/
|
||||
private function surchargeSlots(array $services): array
|
||||
{
|
||||
$has = fn (string $service) => in_array($service, $services, true);
|
||||
$slots = [1 => null, 2 => null, 3 => null];
|
||||
|
||||
$pdf = Pdf::loadView($view, $data);
|
||||
if ($has('special_handling')) {
|
||||
$slots[1] = self::SERVICE_TEXT['special_handling'];
|
||||
}
|
||||
|
||||
// A6 = 104mm x 148mm, per RDLCPrinter.cs's own DeviceInfo
|
||||
// override for printer_size==2 (1mm ≈ 2.8346pt) — note this is
|
||||
// ELTA's own thermal-label size, not the ISO A6 (105x148mm) the
|
||||
// old comment here named. A 4-value paper array plus an
|
||||
// orientation string together confuse dompdf into doubling the
|
||||
// canvas and silently overflowing to a second blank page
|
||||
// (reproduced with a bare empty page); the array alone is
|
||||
// already portrait (height > width), so the orientation
|
||||
// argument is both redundant and the actual bug.
|
||||
$paperSize === 'a6'
|
||||
? $pdf->setPaper([0, 0, 294.80, 419.53])
|
||||
: $pdf->setPaper('a4');
|
||||
if ($has('timed_delivery')) {
|
||||
$slots[2] = self::SERVICE_TEXT['timed_delivery'];
|
||||
|
||||
return $pdf->output();
|
||||
if ($has('saturday_delivery')) {
|
||||
$slots[3] = self::SERVICE_TEXT['saturday_delivery'];
|
||||
}
|
||||
} elseif ($has('saturday_delivery')) {
|
||||
$slots[2] = self::SERVICE_TEXT['saturday_delivery'];
|
||||
}
|
||||
|
||||
return $slots;
|
||||
}
|
||||
|
||||
/**
|
||||
* The cash-on-delivery texts exactly as the client prints them
|
||||
* (Sydeta.cs::print_vg(), cash branch): antik_1 "ΑΝΤΙΚΑΤΑΒΟΛΗ 17.00"
|
||||
* heads the COD column and the A6 bottom banner, then "* ΑΝΑΛΥΣΗ *" and
|
||||
* the cash line ("17.00 MΕΤΡΗΤΑ" — the amount as sent in pel_ant_poso,
|
||||
* the client's own Latin "M" kept). antik_minima / apodiksi / antik_poso
|
||||
* are the A4 payment stub's. We only ever send cash (no cheques), so
|
||||
* antik_4..7 stay empty.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function codFields(?float $amount): array
|
||||
{
|
||||
if (! $amount) {
|
||||
return [
|
||||
'antik_1' => null, 'antik_2' => null, 'antik_3' => null,
|
||||
'antik_4' => null, 'antik_5' => null, 'antik_6' => null, 'antik_7' => null,
|
||||
'antik_lines' => [],
|
||||
'antik_minima' => null,
|
||||
'apodiksi' => null,
|
||||
'antik_poso' => '0.00',
|
||||
];
|
||||
}
|
||||
|
||||
$sent = number_format($amount, 2, '.', '');
|
||||
|
||||
$lines = [
|
||||
'antik_1' => 'ΑΝΤΙΚΑΤΑΒΟΛΗ '.$sent,
|
||||
'antik_2' => '* ΑΝΑΛΥΣΗ *',
|
||||
'antik_3' => $sent.' MΕΤΡΗΤΑ',
|
||||
'antik_4' => null, 'antik_5' => null, 'antik_6' => null, 'antik_7' => null,
|
||||
];
|
||||
|
||||
return [
|
||||
...$lines,
|
||||
'antik_lines' => array_values(array_filter($lines)),
|
||||
'antik_minima' => '* ΠΡΟΣΟΧΗ ΑΝΤΙΚΑΤΑΒΟΛΗ *',
|
||||
'apodiksi' => '* Απόδειξη Είσπαξης *',
|
||||
'antik_poso' => $sent,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user