feat(27-shipment-tracking-backend): infrastruktura sledzenia przesylek — statusy, tracking services, cron handler
Dwupoziomowy system statusow dostawy (normalized + raw z API), implementacje trackingu dla InPost ShipX, Apaczka i Allegro WZA, cron handler odpytujacy aktywne przesylki co 15 minut. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
203
src/Modules/Shipments/DeliveryStatus.php
Normal file
203
src/Modules/Shipments/DeliveryStatus.php
Normal file
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Modules\Shipments;
|
||||
|
||||
final class DeliveryStatus
|
||||
{
|
||||
public const UNKNOWN = 'unknown';
|
||||
public const CREATED = 'created';
|
||||
public const CONFIRMED = 'confirmed';
|
||||
public const IN_TRANSIT = 'in_transit';
|
||||
public const OUT_FOR_DELIVERY = 'out_for_delivery';
|
||||
public const READY_FOR_PICKUP = 'ready_for_pickup';
|
||||
public const DELIVERED = 'delivered';
|
||||
public const RETURNED = 'returned';
|
||||
public const CANCELLED = 'cancelled';
|
||||
public const PROBLEM = 'problem';
|
||||
|
||||
public const TERMINAL_STATUSES = [
|
||||
self::DELIVERED,
|
||||
self::RETURNED,
|
||||
self::CANCELLED,
|
||||
];
|
||||
|
||||
public const LABEL_PL = [
|
||||
self::UNKNOWN => 'Nieznany',
|
||||
self::CREATED => 'Utworzona',
|
||||
self::CONFIRMED => 'Potwierdzona',
|
||||
self::IN_TRANSIT => 'W tranzycie',
|
||||
self::OUT_FOR_DELIVERY => 'W doręczeniu',
|
||||
self::READY_FOR_PICKUP => 'Gotowa do odbioru',
|
||||
self::DELIVERED => 'Doręczona',
|
||||
self::RETURNED => 'Zwrócona',
|
||||
self::CANCELLED => 'Anulowana',
|
||||
self::PROBLEM => 'Problem',
|
||||
];
|
||||
|
||||
private const INPOST_MAP = [
|
||||
'created' => self::CREATED,
|
||||
'offers_prepared' => self::CREATED,
|
||||
'offer_selected' => self::CREATED,
|
||||
'confirmed' => self::CONFIRMED,
|
||||
'dispatched' => self::CONFIRMED,
|
||||
'collected' => self::IN_TRANSIT,
|
||||
'taken_by_courier' => self::IN_TRANSIT,
|
||||
'adopted_at_source_branch' => self::IN_TRANSIT,
|
||||
'adopted_at_sorting_center' => self::IN_TRANSIT,
|
||||
'sent_from_sorting_center' => self::IN_TRANSIT,
|
||||
'adopted_at_target_sorting_center' => self::IN_TRANSIT,
|
||||
'sent_from_target_sorting_center' => self::IN_TRANSIT,
|
||||
'adopted_at_target_branch' => self::IN_TRANSIT,
|
||||
'out_for_delivery' => self::OUT_FOR_DELIVERY,
|
||||
'ready_to_pickup' => self::READY_FOR_PICKUP,
|
||||
'ready_to_pickup_from_branch' => self::READY_FOR_PICKUP,
|
||||
'ready_to_pickup_from_pok' => self::READY_FOR_PICKUP,
|
||||
'stack_in_box_machine' => self::READY_FOR_PICKUP,
|
||||
'stack_in_customer_service_point' => self::READY_FOR_PICKUP,
|
||||
'delivered' => self::DELIVERED,
|
||||
'claimed' => self::DELIVERED,
|
||||
'returned_to_sender' => self::RETURNED,
|
||||
'undelivered' => self::RETURNED,
|
||||
'undelivered_wrong_address' => self::RETURNED,
|
||||
'undelivered_incomplete_address' => self::RETURNED,
|
||||
'undelivered_unknown_recipient' => self::RETURNED,
|
||||
'undelivered_cod_cash_receiver' => self::RETURNED,
|
||||
'cancelled' => self::CANCELLED,
|
||||
'expired' => self::CANCELLED,
|
||||
'avizo' => self::PROBLEM,
|
||||
'pickup_time_expired' => self::PROBLEM,
|
||||
'stack_parcel_pickup_time_expired' => self::PROBLEM,
|
||||
'missing' => self::PROBLEM,
|
||||
'delay_in_delivery' => self::PROBLEM,
|
||||
'oversized' => self::PROBLEM,
|
||||
'pickup_reminder_sent' => self::READY_FOR_PICKUP,
|
||||
'pickup_reminder_sent_address' => self::READY_FOR_PICKUP,
|
||||
'readdressed' => self::IN_TRANSIT,
|
||||
'redirect_to_box' => self::IN_TRANSIT,
|
||||
];
|
||||
|
||||
private const INPOST_DESCRIPTIONS = [
|
||||
'created' => 'Przesyłka utworzona',
|
||||
'offers_prepared' => 'Oferty cenowe przygotowane',
|
||||
'offer_selected' => 'Oferta wybrana',
|
||||
'confirmed' => 'Przesyłka potwierdzona',
|
||||
'dispatched' => 'Przesyłka nadana',
|
||||
'collected' => 'Odebrana od nadawcy',
|
||||
'taken_by_courier' => 'Odebrana przez kuriera',
|
||||
'adopted_at_source_branch' => 'Przyjęta w oddziale źródłowym',
|
||||
'adopted_at_sorting_center' => 'Przyjęta w centrum sortowania',
|
||||
'sent_from_sorting_center' => 'Wysłana z centrum sortowania',
|
||||
'adopted_at_target_sorting_center' => 'Przyjęta w docelowym centrum sortowania',
|
||||
'sent_from_target_sorting_center' => 'Wysłana z docelowego centrum sortowania',
|
||||
'adopted_at_target_branch' => 'Przyjęta w oddziale docelowym',
|
||||
'out_for_delivery' => 'W drodze do odbiorcy',
|
||||
'ready_to_pickup' => 'Gotowa do odbioru w paczkomacie',
|
||||
'ready_to_pickup_from_branch' => 'Gotowa do odbioru z oddziału',
|
||||
'ready_to_pickup_from_pok' => 'Gotowa do odbioru z POK',
|
||||
'stack_in_box_machine' => 'Umieszczona w paczkomacie',
|
||||
'stack_in_customer_service_point' => 'Umieszczona w punkcie obsługi',
|
||||
'delivered' => 'Doręczona',
|
||||
'claimed' => 'Odebrana po awizo',
|
||||
'returned_to_sender' => 'Zwrócona do nadawcy',
|
||||
'undelivered' => 'Niedoręczona',
|
||||
'undelivered_wrong_address' => 'Niedoręczona — błędny adres',
|
||||
'undelivered_incomplete_address' => 'Niedoręczona — niepełny adres',
|
||||
'undelivered_unknown_recipient' => 'Niedoręczona — nieznany odbiorca',
|
||||
'undelivered_cod_cash_receiver' => 'Niedoręczona — problem z pobraniem',
|
||||
'cancelled' => 'Anulowana',
|
||||
'expired' => 'Wygasła',
|
||||
'avizo' => 'Awizowana',
|
||||
'pickup_time_expired' => 'Czas odbioru upłynął',
|
||||
'stack_parcel_pickup_time_expired' => 'Czas odbioru ze stack upłynął',
|
||||
'missing' => 'Przesyłka zagubiona',
|
||||
'delay_in_delivery' => 'Opóźnienie w dostawie',
|
||||
'oversized' => 'Przesyłka ponadgabarytowa',
|
||||
'pickup_reminder_sent' => 'Wysłano przypomnienie o odbiorze',
|
||||
'pickup_reminder_sent_address' => 'Przypomnienie wysłane na adres',
|
||||
'readdressed' => 'Przekierowana na inny adres',
|
||||
'redirect_to_box' => 'Przekierowana do paczkomatu',
|
||||
];
|
||||
|
||||
private const APACZKA_MAP = [
|
||||
'0' => self::CREATED,
|
||||
'1' => self::CONFIRMED,
|
||||
'2' => self::IN_TRANSIT,
|
||||
'3' => self::IN_TRANSIT,
|
||||
'4' => self::OUT_FOR_DELIVERY,
|
||||
'5' => self::DELIVERED,
|
||||
'6' => self::RETURNED,
|
||||
'7' => self::CANCELLED,
|
||||
'8' => self::PROBLEM,
|
||||
'9' => self::READY_FOR_PICKUP,
|
||||
'10' => self::IN_TRANSIT,
|
||||
];
|
||||
|
||||
private const APACZKA_DESCRIPTIONS = [
|
||||
'0' => 'Oczekuje na przetworzenie',
|
||||
'1' => 'Zamówienie potwierdzone',
|
||||
'2' => 'Odebrana przez kuriera',
|
||||
'3' => 'W transporcie',
|
||||
'4' => 'W doręczeniu',
|
||||
'5' => 'Doręczona',
|
||||
'6' => 'Zwrócona do nadawcy',
|
||||
'7' => 'Anulowana',
|
||||
'8' => 'Błąd zamówienia',
|
||||
'9' => 'Oczekuje na odbiór w punkcie',
|
||||
'10' => 'Przekierowana',
|
||||
];
|
||||
|
||||
private const ALLEGRO_MAP = [
|
||||
'NEW' => self::CREATED,
|
||||
'READY_TO_SHIP' => self::CONFIRMED,
|
||||
'IN_TRANSIT' => self::IN_TRANSIT,
|
||||
'DELIVERED' => self::DELIVERED,
|
||||
'CANCELLED' => self::CANCELLED,
|
||||
'ERROR' => self::PROBLEM,
|
||||
'RETURNED' => self::RETURNED,
|
||||
];
|
||||
|
||||
private const ALLEGRO_DESCRIPTIONS = [
|
||||
'NEW' => 'Przesyłka utworzona',
|
||||
'READY_TO_SHIP' => 'Etykieta wygenerowana, oczekuje na nadanie',
|
||||
'IN_TRANSIT' => 'Odebrana przez przewoźnika',
|
||||
'DELIVERED' => 'Doręczona',
|
||||
'CANCELLED' => 'Anulowana',
|
||||
'ERROR' => 'Błąd przetwarzania',
|
||||
'RETURNED' => 'Zwrócona do nadawcy',
|
||||
];
|
||||
|
||||
public static function normalize(string $provider, string $rawStatus): string
|
||||
{
|
||||
$map = match ($provider) {
|
||||
'inpost' => self::INPOST_MAP,
|
||||
'apaczka' => self::APACZKA_MAP,
|
||||
'allegro_wza' => self::ALLEGRO_MAP,
|
||||
default => [],
|
||||
};
|
||||
|
||||
return $map[$rawStatus] ?? self::UNKNOWN;
|
||||
}
|
||||
|
||||
public static function description(string $provider, string $rawStatus): string
|
||||
{
|
||||
$map = match ($provider) {
|
||||
'inpost' => self::INPOST_DESCRIPTIONS,
|
||||
'apaczka' => self::APACZKA_DESCRIPTIONS,
|
||||
'allegro_wza' => self::ALLEGRO_DESCRIPTIONS,
|
||||
default => [],
|
||||
};
|
||||
|
||||
return $map[$rawStatus] ?? $rawStatus;
|
||||
}
|
||||
|
||||
public static function label(string $status): string
|
||||
{
|
||||
return self::LABEL_PL[$status] ?? 'Nieznany';
|
||||
}
|
||||
|
||||
public static function isTerminal(string $status): bool
|
||||
{
|
||||
return in_array($status, self::TERMINAL_STATUSES, true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user