Feat: Shipping functionalities redesign, elta courier integration, commenting out acs since no integration can happen, flow updates
This commit is contained in:
@@ -2,34 +2,66 @@
|
||||
|
||||
namespace Modules\Core\Shipping\Carriers\Elta;
|
||||
|
||||
use Carbon\CarbonInterface;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Lunar\Models\Order;
|
||||
use Modules\Core\Shipping\Carriers\Elta\Exceptions\EltaApiException;
|
||||
use Modules\Core\Shipping\Contracts\CarrierFulfillmentInterface;
|
||||
use Modules\Core\Shipping\Contracts\IssuesVoucherOnPrint;
|
||||
use Modules\Core\Shipping\Contracts\SupportsBatchLabels;
|
||||
use Modules\Core\Shipping\Contracts\SupportsExtraServices;
|
||||
use Modules\Core\Shipping\Contracts\SupportsTracking;
|
||||
use Modules\Core\Shipping\Contracts\SupportsVoucherListing;
|
||||
use Modules\Core\Shipping\Contracts\SupportsVoucherLookup;
|
||||
use Modules\Core\Shipping\DTOs\CarrierVoucher;
|
||||
use Modules\Core\Shipping\DTOs\ShipmentRequest;
|
||||
use Modules\Core\Shipping\DTOs\TrackingCheckpoint;
|
||||
use Modules\Core\Shipping\Enums\ExtraService;
|
||||
use Modules\Core\Shipping\Enums\TrackingStatus;
|
||||
use Modules\Core\Shipping\Models\Shipment;
|
||||
|
||||
/**
|
||||
* Not SupportsManifestBatching — no manifest/pickup-list operation was
|
||||
* found in either ELTA operation family, unlike ACS's
|
||||
* ACS_Issue_Pickup_List. Same shape as Box Now, which has no manifest
|
||||
* step either.
|
||||
* ELTA vouchers are created **pending** (pel_insert_flag "0", live-verified):
|
||||
* ELTA stores the voucher with an internal id but no voucher number, and it
|
||||
* can still be cancelled. Printing issues it (PELVG01NEW1 — what ELTA's own
|
||||
* client does from its group-editing screen): that assigns the voucher
|
||||
* number, OCR line, destination station and service, and from then on ELTA
|
||||
* refuses to delete it ("Δεν Επιτρέπεται! Εχει Γίνει Εκτύπωση"). So a
|
||||
* shipment has no tracking_reference until its label is printed.
|
||||
*
|
||||
* Not SupportsManifestBatching — ELTA has no manifest/pickup-list step.
|
||||
*/
|
||||
class EltaFulfillmentService implements CarrierFulfillmentInterface, SupportsTracking
|
||||
class EltaFulfillmentService implements CarrierFulfillmentInterface, IssuesVoucherOnPrint, SupportsBatchLabels, SupportsExtraServices, SupportsTracking, SupportsVoucherListing, SupportsVoucherLookup
|
||||
{
|
||||
/** PELMANIF2 / PELPARALVGNEW1 page through 100 rows at a time. */
|
||||
private const MAX_PAGES = 50;
|
||||
|
||||
public function __construct(
|
||||
private readonly EltaClient $client,
|
||||
private readonly EltaLabelRenderer $labelRenderer,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* ELTA surcharge codes (pel_sur_1..3), from its own client's checkboxes.
|
||||
* Insurance isn't a surcharge — it's the amount in pel_asf_poso.
|
||||
*/
|
||||
public function extraServices(): array
|
||||
{
|
||||
return [
|
||||
ExtraService::SaturdayDelivery->value => '004',
|
||||
ExtraService::TimedDelivery->value => '003',
|
||||
ExtraService::SpecialHandling->value => '002',
|
||||
ExtraService::Insurance->value => 'pel_asf_poso',
|
||||
];
|
||||
}
|
||||
|
||||
public function createShipment(Order $order, ShipmentRequest $request): Shipment
|
||||
{
|
||||
$address = $order->shippingAddress;
|
||||
$weight = $request->weight ?? 0.5;
|
||||
$surcharges = $this->surchargeCodes($request);
|
||||
|
||||
$params = [
|
||||
'pel_apost_code' => config('elta.apost_code'),
|
||||
@@ -47,9 +79,11 @@ class EltaFulfillmentService implements CarrierFulfillmentInterface, SupportsTra
|
||||
'pel_z' => '',
|
||||
'pel_temaxia' => (string) $request->packageCount,
|
||||
'pel_paral_sxolia' => '',
|
||||
'pel_sur_1' => '',
|
||||
'pel_sur_2' => '',
|
||||
'pel_sur_3' => '',
|
||||
'pel_sur_1' => $surcharges[0] ?? '',
|
||||
'pel_sur_2' => $surcharges[1] ?? '',
|
||||
'pel_sur_3' => $surcharges[2] ?? '',
|
||||
// pel_ant_poso is the cash-on-delivery amount; pel_ant_poso1..4
|
||||
// are cheques (with dates) in ELTA's client — never used here.
|
||||
'pel_ant_poso' => '',
|
||||
'pel_ant_poso1' => '',
|
||||
'pel_ant_poso2' => '',
|
||||
@@ -59,10 +93,16 @@ class EltaFulfillmentService implements CarrierFulfillmentInterface, SupportsTra
|
||||
'pel_ant_date2' => '',
|
||||
'pel_ant_date3' => '',
|
||||
'pel_ant_date4' => '',
|
||||
'pel_asf_poso' => '',
|
||||
'pel_asf_poso' => $request->has(ExtraService::Insurance) && $request->insuranceAmount
|
||||
? number_format($request->insuranceAmount, 2, '.', '')
|
||||
: '',
|
||||
'pel_user' => config('elta.user_code'),
|
||||
'pel_ref_no' => (string) $order->id,
|
||||
'pel_insert_flag' => '',
|
||||
// Our order reference — comes back in ELTA's lists and tracking,
|
||||
// which is how we find the pending voucher's id below and match
|
||||
// vouchers to orders on the Carrier Vouchers screen.
|
||||
'pel_ref_no' => (string) $order->reference,
|
||||
// "0" = save without issuing (see this class's docblock).
|
||||
'pel_insert_flag' => '0',
|
||||
'pel_paral_code' => '',
|
||||
'pel_retur_code' => '',
|
||||
];
|
||||
@@ -74,122 +114,66 @@ class EltaFulfillmentService implements CarrierFulfillmentInterface, SupportsTra
|
||||
$params['pel_ant_poso'] = number_format($codAmount, 2, '.', '');
|
||||
}
|
||||
|
||||
$response = $this->client->createVoucher($params)->throwIfError();
|
||||
$this->client->createVoucher($params)->throwIfError();
|
||||
|
||||
$voucherNo = (string) $response->data['vg_code'];
|
||||
|
||||
$shipment = Shipment::create([
|
||||
// PELVGNEW doesn't return the new voucher's id, so find it in the
|
||||
// pending list by our reference. Saved even if that lookup fails —
|
||||
// the voucher exists at ELTA either way, and printing retries it.
|
||||
return Shipment::create([
|
||||
'order_id' => $order->id,
|
||||
'carrier' => 'elta',
|
||||
'tracking_reference' => $voucherNo,
|
||||
'source' => Shipment::SOURCE_CREATED,
|
||||
'tracking_reference' => null,
|
||||
'meta' => [
|
||||
'carrier_id' => $this->findPendingVoucherId((string) $order->reference),
|
||||
'weight' => $weight,
|
||||
'ocr_line' => $response->data['ocr_line'] ?? null,
|
||||
'date_time' => $response->data['date_time'] ?? null,
|
||||
'package_count' => $request->packageCount,
|
||||
'cod_amount' => $codAmount,
|
||||
'services' => $request->serviceValues(),
|
||||
'insurance_amount' => $request->has(ExtraService::Insurance) ? $request->insuranceAmount : null,
|
||||
],
|
||||
]);
|
||||
|
||||
// PELVGNEW's own response already carries every child voucher
|
||||
// number for a multi-piece shipment in vg_child_no — no second
|
||||
// request needed, same as the old CREATEAWB/vg_child shape.
|
||||
foreach (array_filter((array) ($response->data['vg_child_no'] ?? [])) as $childVoucherNo) {
|
||||
Shipment::create([
|
||||
'order_id' => $order->id,
|
||||
'carrier' => 'elta',
|
||||
'tracking_reference' => $childVoucherNo,
|
||||
'parent_reference' => $voucherNo,
|
||||
'meta' => $shipment->meta?->toArray() ?? [],
|
||||
]);
|
||||
}
|
||||
|
||||
return $shipment;
|
||||
}
|
||||
|
||||
/**
|
||||
* PELB64VG (the old, deprecated operation this used to call) fails
|
||||
* with the same st_flag=3 as CREATEAWB — it's the same legacy family.
|
||||
* There's no *NEW replacement: ELTA's own official client renders
|
||||
* labels entirely locally from a bundled report template, never
|
||||
* fetching one from the server at all (see EltaLabelRenderer's
|
||||
* docblock). This reproduces that local rendering instead.
|
||||
*/
|
||||
public function printLabel(Shipment $shipment): string
|
||||
{
|
||||
$pdf = $this->labelRenderer->render($shipment);
|
||||
$this->issue($shipment);
|
||||
|
||||
$pdf = $this->labelRenderer->render($shipment->refresh());
|
||||
|
||||
$shipment->update(['label_printed_at' => now()]);
|
||||
|
||||
return $pdf;
|
||||
}
|
||||
|
||||
public function printLabels(Collection $shipments): string
|
||||
{
|
||||
$shipments->each(fn (Shipment $shipment) => $this->issue($shipment));
|
||||
|
||||
$pdf = $this->labelRenderer->renderMany($shipments->map->refresh());
|
||||
|
||||
Shipment::whereIn('id', $shipments->pluck('id'))->update(['label_printed_at' => now()]);
|
||||
|
||||
return $pdf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlike the old, deprecated operation set (which genuinely has no
|
||||
* delete operation), the *NEW family does support cancellation via
|
||||
* PELVGDEL — but it identifies a voucher by its internal pel_vg_id,
|
||||
* not the AWB/tracking number PELVGNEW returns (vg_code). Getting
|
||||
* pel_vg_id requires first listing the account's vouchers via
|
||||
* PELPARALVGNEW1 and matching the row whose pel_paral_vg equals our
|
||||
* tracking_reference.
|
||||
* Pending (not yet printed) vouchers are deleted at ELTA. Issued ones
|
||||
* can't be — ELTA only allows that before printing.
|
||||
*/
|
||||
public function cancelShipment(Shipment $shipment): void
|
||||
{
|
||||
$voucherId = $this->findVoucherId($shipment->tracking_reference);
|
||||
|
||||
if ($voucherId === null) {
|
||||
if (filled($shipment->tracking_reference)) {
|
||||
throw new EltaApiException(
|
||||
'Could not find voucher '.$shipment->tracking_reference.' in ELTA\'s voucher list — it may already be too old or cancelled to look up.',
|
||||
"ELTA voucher {$shipment->tracking_reference} is already printed, and ELTA only allows cancelling vouchers that haven't been printed yet. Contact ELTA to cancel it.",
|
||||
);
|
||||
}
|
||||
|
||||
$this->client->deleteVoucher(['pel_vg_id' => $voucherId])->throwIfError();
|
||||
$this->client->deleteVoucher(['pel_vg_id' => $this->pendingVoucherId($shipment)])->throwIfError();
|
||||
|
||||
$shipment->update(['cancelled_at' => now()]);
|
||||
}
|
||||
|
||||
private function findVoucherId(string $trackingReference): ?string
|
||||
{
|
||||
$inId = '0';
|
||||
|
||||
// PELPARALVGNEW1 paginates via in_id, same cursor pattern the
|
||||
// official client uses — walk pages until the voucher is found
|
||||
// or a page comes back empty. flag_1/flag_2 map to the real
|
||||
// client's "Show All"/"All Users" checkboxes — live-confirmed
|
||||
// both must be '1', otherwise even a voucher created moments ago
|
||||
// is filtered out of the (default, unfiltered-looking) list.
|
||||
for ($page = 0; $page < 50; $page++) {
|
||||
$response = $this->client->listVouchers([
|
||||
'pel_code' => config('elta.apost_code'),
|
||||
'pel_user_code' => config('elta.user_code'),
|
||||
'flag_1' => '1',
|
||||
'flag_2' => '1',
|
||||
'in_id' => $inId,
|
||||
])->throwIfError();
|
||||
|
||||
$rows = array_filter(
|
||||
(array) ($response->data['vg_rec'] ?? []),
|
||||
fn (array $row) => filled($row['pel_vg_id'] ?? null),
|
||||
);
|
||||
|
||||
if ($rows === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$match = collect($rows)->first(
|
||||
fn (array $row) => ($row['pel_paral_vg'] ?? null) === $trackingReference,
|
||||
);
|
||||
|
||||
if ($match !== null) {
|
||||
return $match['pel_vg_id'];
|
||||
}
|
||||
|
||||
$inId = (string) end($rows)['pel_vg_id'];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function trackShipment(Shipment $shipment): Collection
|
||||
{
|
||||
$response = $this->client->getTracking([
|
||||
@@ -220,12 +204,269 @@ class EltaFulfillmentService implements CarrierFulfillmentInterface, SupportsTra
|
||||
carrierStatus: $row['web_status_name'] ?? null,
|
||||
message: $row['web_sxolia'] ?: ($row['web_status_name'] ?? null),
|
||||
location: $row['web_station'] ?? null,
|
||||
occurredAt: Carbon::createFromFormat('YmdHi', substr($dateTime, 0, 12)),
|
||||
// ELTA reports Greek local time.
|
||||
occurredAt: Carbon::createFromFormat('YmdHi', substr($dateTime, 0, 12), 'Europe/Athens')->setTimezone(config('app.timezone')),
|
||||
meta: $row,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* PELMANIF2 (the client's "Πολλαπλή Αναζήτηση"): every issued voucher
|
||||
* in the date range with its latest status. A second pass with
|
||||
* status_flag 3 ("Προς Επιστροφή") marks the ones heading back to us.
|
||||
*/
|
||||
public function listVouchers(CarbonInterface $from, CarbonInterface $to): iterable
|
||||
{
|
||||
$returning = collect($this->searchVouchers($from, $to, '3'))->pluck('pel_col_1')->flip();
|
||||
|
||||
foreach ($this->searchVouchers($from, $to, '0') as $row) {
|
||||
$number = trim($row['pel_col_1']);
|
||||
|
||||
yield new CarrierVoucher(
|
||||
carrier: 'elta',
|
||||
voucherNumber: $number,
|
||||
reference: filled($row['pel_col_2'] ?? null) ? trim($row['pel_col_2']) : null,
|
||||
recipientName: trim($row['pel_col_3'] ?? '') ?: null,
|
||||
postcode: trim($row['pel_col_4'] ?? '') ?: null,
|
||||
statusText: trim($row['pel_col_6'] ?? '') ?: null,
|
||||
status: filled(trim($row['pel_col_6'] ?? '')) ? $this->guessStatusFromTitle(trim($row['pel_col_6'])) : null,
|
||||
codAmount: (float) ($row['pel_col_8'] ?? 0) ?: null,
|
||||
isReturn: $returning->has($row['pel_col_1']),
|
||||
date: $this->dateFromListRow($row['pel_col_7'] ?? ''),
|
||||
raw: $row,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PELTTNEW01 (tracking) also returns the voucher's details (pel_rec):
|
||||
* recipient, postcode, our reference, cash on delivery.
|
||||
*/
|
||||
public function lookupVoucher(string $voucherNumber): ?CarrierVoucher
|
||||
{
|
||||
$response = $this->client->getTracking([
|
||||
'web_vg' => trim($voucherNumber),
|
||||
'pel_code' => config('elta.apost_code'),
|
||||
]);
|
||||
|
||||
$record = $response->data['pel_rec'] ?? [];
|
||||
|
||||
if ($response->hasError || blank($record['a_vg_3'] ?? null)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$statuses = array_values(array_filter(
|
||||
(array) ($response->data['web_status'] ?? []),
|
||||
fn (array $row) => filled($row['web_date_time'] ?? null),
|
||||
));
|
||||
|
||||
return new CarrierVoucher(
|
||||
carrier: 'elta',
|
||||
voucherNumber: trim($record['a_vg_3']),
|
||||
reference: trim($record['a_ref'] ?? '') ?: null,
|
||||
recipientName: trim($record['a_rec_title'] ?? '') ?: null,
|
||||
postcode: trim($record['a_rec_postal'] ?? '') ?: null,
|
||||
phone: trim($record['a_rec_tel_1'] ?? '') ?: null,
|
||||
statusText: filled($statuses) ? trim(end($statuses)['web_status_name'] ?? '') : null,
|
||||
status: filled($statuses) ? $this->guessStatusFromTitle(trim(end($statuses)['web_status_name'] ?? '')) : null,
|
||||
codAmount: (float) ($record['a_antik'] ?? 0) ?: null,
|
||||
date: $this->dateFromListRow($record['a_sender_date'] ?? ''),
|
||||
raw: $record,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Issues a pending voucher (no-op once it has a number): stores the
|
||||
* voucher number and what ELTA returns for the label, plus one shipment
|
||||
* per child voucher of a multi-piece send.
|
||||
*/
|
||||
private function issue(Shipment $shipment): void
|
||||
{
|
||||
if (filled($shipment->tracking_reference)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$response = $this->client->issueVoucher([
|
||||
'pel_id' => $this->pendingVoucherId($shipment),
|
||||
'sender_station' => $this->senderStation(),
|
||||
])->throwIfError();
|
||||
|
||||
$data = $response->data;
|
||||
$voucherNo = trim((string) ($data['vg_code'] ?? ''));
|
||||
|
||||
if ($voucherNo === '') {
|
||||
throw new EltaApiException('ELTA issued the voucher but returned no voucher number.', $data);
|
||||
}
|
||||
|
||||
$shipment->tracking_reference = $voucherNo;
|
||||
$shipment->meta = array_merge($shipment->meta?->toArray() ?? [], [
|
||||
'ocr_line' => $data['ocr_line'] ?? null,
|
||||
'date_time' => $data['date_time'] ?? null,
|
||||
'rec_station' => $data['rec_station'] ?? null,
|
||||
'rec_station_t' => $data['rec_station_t'] ?? null,
|
||||
'rec_srv' => $data['rec_srv'] ?? null,
|
||||
'rec_srv_t' => $data['rec_srv_t'] ?? null,
|
||||
'return_vg' => trim((string) ($data['return_vg'] ?? '')) ?: null,
|
||||
'epitagh_vg' => trim((string) ($data['epitagh_vg'] ?? '')) ?: null,
|
||||
]);
|
||||
$shipment->save();
|
||||
|
||||
foreach (array_filter(array_map('trim', (array) ($data['vg_child_no'] ?? []))) as $childVoucherNo) {
|
||||
Shipment::create([
|
||||
'order_id' => $shipment->order_id,
|
||||
'carrier' => 'elta',
|
||||
'source' => Shipment::SOURCE_CREATED,
|
||||
'tracking_reference' => $childVoucherNo,
|
||||
'parent_reference' => $voucherNo,
|
||||
'label_printed_at' => now(),
|
||||
'meta' => $shipment->meta->toArray(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private function surchargeCodes(ShipmentRequest $request): array
|
||||
{
|
||||
$codes = $this->extraServices();
|
||||
|
||||
return collect($request->services)
|
||||
->reject(fn (ExtraService $service) => $service === ExtraService::Insurance)
|
||||
->map(fn (ExtraService $service) => $codes[$service->value] ?? null)
|
||||
->filter()
|
||||
->values()
|
||||
->take(3)
|
||||
->all();
|
||||
}
|
||||
|
||||
private function pendingVoucherId(Shipment $shipment): string
|
||||
{
|
||||
$id = $shipment->meta['carrier_id'] ?? null;
|
||||
|
||||
if (blank($id) && $shipment->order) {
|
||||
$id = $this->findPendingVoucherId((string) $shipment->order->reference);
|
||||
|
||||
if ($id) {
|
||||
$shipment->meta = array_merge($shipment->meta?->toArray() ?? [], ['carrier_id' => $id]);
|
||||
$shipment->save();
|
||||
}
|
||||
}
|
||||
|
||||
if (blank($id)) {
|
||||
throw new EltaApiException("Couldn't find this shipment's pending voucher at ELTA.");
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The newest not-yet-issued voucher in ELTA's list carrying our
|
||||
* reference. flag_1/flag_2 are the client's "Show all" / "All users"
|
||||
* checkboxes — both "1", or even a just-created voucher is filtered out.
|
||||
*/
|
||||
private function findPendingVoucherId(string $reference): ?string
|
||||
{
|
||||
$inId = '0';
|
||||
$match = null;
|
||||
|
||||
for ($page = 0; $page < self::MAX_PAGES; $page++) {
|
||||
$rows = array_values(array_filter(
|
||||
(array) ($this->client->listVouchers([
|
||||
'pel_code' => config('elta.apost_code'),
|
||||
'pel_user_code' => config('elta.user_code'),
|
||||
'flag_1' => '1',
|
||||
'flag_2' => '1',
|
||||
'in_id' => $inId,
|
||||
])->throwIfError()->data['vg_rec'] ?? []),
|
||||
fn (array $row) => filled($row['pel_vg_id'] ?? null),
|
||||
));
|
||||
|
||||
foreach ($rows as $row) {
|
||||
if (trim($row['pel_ref_no'] ?? '') === $reference && blank(trim($row['pel_paral_vg'] ?? ''))
|
||||
&& ($match === null || $row['pel_vg_id'] > $match)) {
|
||||
$match = $row['pel_vg_id'];
|
||||
}
|
||||
}
|
||||
|
||||
if (count($rows) < 100) {
|
||||
break;
|
||||
}
|
||||
|
||||
$inId = (string) end($rows)['pel_vg_id'];
|
||||
}
|
||||
|
||||
return $match;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, array<string, string>>
|
||||
*/
|
||||
private function searchVouchers(CarbonInterface $from, CarbonInterface $to, string $statusFlag): array
|
||||
{
|
||||
$inId = '0';
|
||||
$all = [];
|
||||
|
||||
for ($page = 0; $page < self::MAX_PAGES; $page++) {
|
||||
$rows = array_values(array_filter(
|
||||
(array) ($this->client->searchVouchers([
|
||||
'pel_code' => config('elta.apost_code'),
|
||||
'date_1' => $from->format('d/m/Y'),
|
||||
'date_2' => $to->format('d/m/Y'),
|
||||
'paral_code' => '',
|
||||
'status_flag' => $statusFlag,
|
||||
'in_id' => $inId,
|
||||
])->throwIfError()->data['ag_pel_rec'] ?? []),
|
||||
fn (array $row) => filled(trim($row['pel_col_1'] ?? '')),
|
||||
));
|
||||
|
||||
array_push($all, ...$rows);
|
||||
|
||||
if (count($rows) < 100) {
|
||||
break;
|
||||
}
|
||||
|
||||
$inId = (string) end($rows)['pel_col_id'];
|
||||
}
|
||||
|
||||
return $all;
|
||||
}
|
||||
|
||||
/**
|
||||
* ELTA dates in its lists look like "29/09/26 09:41 PEL CLIENT" or
|
||||
* "29/09/2026 09:51".
|
||||
*/
|
||||
private function dateFromListRow(string $value): ?CarbonInterface
|
||||
{
|
||||
if (! preg_match('#(\d{2})/(\d{2})/(\d{2,4})#', $value, $m)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$year = strlen($m[3]) === 2 ? '20'.$m[3] : $m[3];
|
||||
|
||||
return Carbon::createFromDate((int) $year, (int) $m[2], (int) $m[1])->startOfDay();
|
||||
}
|
||||
|
||||
/**
|
||||
* The station ELTA needs when issuing a voucher: configured
|
||||
* (ELTA_ORIGIN_STATION_CODE), or read from the login response once —
|
||||
* ELTA returns the account's user_station there even though our
|
||||
* account's login password is rejected.
|
||||
*/
|
||||
private function senderStation(): string
|
||||
{
|
||||
if (filled(config('elta.origin_station_code'))) {
|
||||
return (string) config('elta.origin_station_code');
|
||||
}
|
||||
|
||||
return (string) Cache::rememberForever('elta.user_station', fn () => $this->client->login([
|
||||
'pel_code' => config('elta.apost_code'),
|
||||
'user_code' => config('elta.user_code'),
|
||||
'user_pass' => config('elta.user_pass'),
|
||||
])->data['user_station'] ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* web_status_name is free text with no structured status code — same
|
||||
* limitation Acs\AcsFulfillmentService::guessStatusFromAction() has.
|
||||
@@ -234,15 +475,23 @@ class EltaFulfillmentService implements CarrierFulfillmentInterface, SupportsTra
|
||||
* it isn't yet in transit. Other statuses are best-guess substring
|
||||
* matches to refine as more real tracking text is observed.
|
||||
*/
|
||||
/**
|
||||
* ELTA's status texts come in unaccented capitals ("ΠΑΡΑΔΟΘΗΚΕ"), so
|
||||
* they're lowercased and stripped of accents before matching.
|
||||
*/
|
||||
private function guessStatusFromTitle(string $title): TrackingStatus
|
||||
{
|
||||
$title = mb_strtolower($title);
|
||||
$title = strtr(mb_strtolower($title), [
|
||||
'ά' => 'α', 'έ' => 'ε', 'ή' => 'η', 'ί' => 'ι', 'ό' => 'ο', 'ύ' => 'υ', 'ώ' => 'ω',
|
||||
'ϊ' => 'ι', 'ϋ' => 'υ', 'ΐ' => 'ι', 'ΰ' => 'υ',
|
||||
]);
|
||||
|
||||
return match (true) {
|
||||
str_contains($title, 'παραδόθηκε') => TrackingStatus::Delivered,
|
||||
str_contains($title, 'διανομή') => TrackingStatus::OutForDelivery,
|
||||
str_contains($title, 'παραλαβή') => TrackingStatus::CollectedFromSender,
|
||||
str_contains($title, 'μεταφορά') || str_contains($title, 'διαμετακόμιση') => TrackingStatus::InTransit,
|
||||
str_contains($title, 'επιστροφ') => TrackingStatus::Returned,
|
||||
str_contains($title, 'παραδοθηκε') => TrackingStatus::Delivered,
|
||||
str_contains($title, 'διανομη') => TrackingStatus::OutForDelivery,
|
||||
str_contains($title, 'παραλαβη') => TrackingStatus::CollectedFromSender,
|
||||
str_contains($title, 'μεταφορα') || str_contains($title, 'διαμετακομιση') || str_contains($title, 'διακινηση') => TrackingStatus::InTransit,
|
||||
default => TrackingStatus::Pending,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user