'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, 'NEW' => self::CREATED, 'PENDING' => self::CREATED, 'CONFIRMED' => self::CONFIRMED, 'PICKED_UP' => self::IN_TRANSIT, 'IN_TRANSIT' => self::IN_TRANSIT, 'OUT_FOR_DELIVERY' => self::OUT_FOR_DELIVERY, 'DELIVERED' => self::DELIVERED, 'RETURNED' => self::RETURNED, 'CANCELLED' => self::CANCELLED, 'ERROR' => self::PROBLEM, 'WAITING_FOR_PICKUP' => self::READY_FOR_PICKUP, 'REDIRECT' => 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', 'NEW' => 'Zamówienie utworzone', 'PENDING' => 'Oczekuje na przetworzenie', 'CONFIRMED' => 'Zamówienie potwierdzone', 'PICKED_UP' => 'Odebrana przez kuriera', 'IN_TRANSIT' => 'W transporcie', 'OUT_FOR_DELIVERY' => 'W doręczeniu', 'DELIVERED' => 'Doręczona', 'RETURNED' => 'Zwrócona do nadawcy', 'CANCELLED' => 'Anulowana', 'ERROR' => 'Błąd zamówienia', 'WAITING_FOR_PICKUP' => 'Oczekuje na odbiór w punkcie', 'REDIRECT' => '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', ]; private const ALLEGRO_EDGE_MAP = [ // Realne slugi z edge API (po slugify opisów) 'przygotowana_przez_nadawce' => self::CREATED, 'nadana' => self::CONFIRMED, 'podjeta_z_maszyny_przez_kuriera' => self::IN_TRANSIT, 'podjeta_z_punktu_przez_kuriera' => self::IN_TRANSIT, 'podjeta_z_punktu' => self::IN_TRANSIT, 'odebrana_przez_kuriera' => self::IN_TRANSIT, 'przekazal_przesylke_do_magazynu' => self::IN_TRANSIT, 'przekazana_do_magazynu' => self::IN_TRANSIT, 'przesylka_wyjechala_w_droge_do_punktu_docelowego' => self::IN_TRANSIT, 'w_sortowni' => self::IN_TRANSIT, 'wyjechala_w_droge_do_punktu_docelowego' => self::IN_TRANSIT, 'wyslana_z_sortowni' => self::IN_TRANSIT, 'w_doreczeniu' => self::OUT_FOR_DELIVERY, 'wydana_do_doreczenia' => self::OUT_FOR_DELIVERY, 'dostarczana' => self::OUT_FOR_DELIVERY, 'gotowa_do_odbioru' => self::READY_FOR_PICKUP, 'oczekuje_na_odbior' => self::READY_FOR_PICKUP, 'przesylka_oczekuje_na_odbior' => self::READY_FOR_PICKUP, 'dostarczona' => self::DELIVERED, 'doreczona' => self::DELIVERED, 'odebrana' => self::DELIVERED, 'zwrocona' => self::RETURNED, 'zwrocona_do_nadawcy' => self::RETURNED, 'anulowana' => self::CANCELLED, 'odmowa_przyjecia' => self::PROBLEM, 'uszkodzona' => self::PROBLEM, 'zagubiona' => self::PROBLEM, ]; private const ALLEGRO_EDGE_DESCRIPTIONS = [ 'przygotowana_przez_nadawce' => 'Przesyłka przygotowana przez nadawcę', 'nadana' => 'Przesyłka nadana', 'podjeta_z_maszyny_przez_kuriera' => 'Podjęta z maszyny przez kuriera', 'podjeta_z_punktu_przez_kuriera' => 'Podjęta z punktu przez kuriera', 'odebrana_przez_kuriera' => 'Odebrana przez kuriera', 'przekazana_do_magazynu' => 'Przekazana do magazynu', 'przesylka_wyjechala_w_droge_do_punktu_docelowego' => 'Wyjechała w drogę do punktu docelowego', 'w_sortowni' => 'W sortowni', 'wyjechala_w_droge_do_punktu_docelowego' => 'Wyjechała w drogę do punktu docelowego', 'wyslana_z_sortowni' => 'Wysłana z sortowni', 'w_doreczeniu' => 'W doręczeniu', 'wydana_do_doreczenia' => 'Wydana do doręczenia', 'dostarczana' => 'Dostarczana', 'gotowa_do_odbioru' => 'Gotowa do odbioru', 'oczekuje_na_odbior' => 'Oczekuje na odbiór', 'przesylka_oczekuje_na_odbior' => 'Oczekuje na odbiór', 'dostarczona' => 'Dostarczona', 'doreczona' => 'Doręczona', 'odebrana' => 'Odebrana', 'zwrocona' => 'Zwrócona', 'zwrocona_do_nadawcy' => 'Zwrócona do nadawcy', 'anulowana' => 'Anulowana', 'odmowa_przyjecia' => 'Odmowa przyjęcia', 'uszkodzona' => 'Uszkodzona', 'zagubiona' => 'Zagubiona', ]; public const ALL_STATUSES = [ self::UNKNOWN, self::CREATED, self::CONFIRMED, self::IN_TRANSIT, self::OUT_FOR_DELIVERY, self::READY_FOR_PICKUP, self::DELIVERED, self::RETURNED, self::CANCELLED, self::PROBLEM, ]; private const PROVIDER_MAPS = [ 'inpost' => self::INPOST_MAP, 'apaczka' => self::APACZKA_MAP, 'allegro_wza' => self::ALLEGRO_MAP, 'allegro_edge' => self::ALLEGRO_EDGE_MAP, ]; private const PROVIDER_DESCRIPTIONS = [ 'inpost' => self::INPOST_DESCRIPTIONS, 'apaczka' => self::APACZKA_DESCRIPTIONS, 'allegro_wza' => self::ALLEGRO_DESCRIPTIONS, 'allegro_edge' => self::ALLEGRO_EDGE_DESCRIPTIONS, ]; /** * @return array */ public static function getDefaultMappings(string $provider): array { $map = self::PROVIDER_MAPS[$provider] ?? []; $descriptions = self::PROVIDER_DESCRIPTIONS[$provider] ?? []; $result = []; foreach ($map as $rawStatus => $normalized) { $result[(string) $rawStatus] = [ 'normalized' => $normalized, 'description' => (string) ($descriptions[$rawStatus] ?? (string) $rawStatus), ]; } return $result; } /** * @param array $overrides keyed by "provider:raw_status" */ public static function normalizeWithOverrides(string $provider, string $rawStatus, array $overrides): string { $key = $provider . ':' . $rawStatus; if (isset($overrides[$key]) && $overrides[$key]['normalized_status'] !== '') { return $overrides[$key]['normalized_status']; } return self::normalize($provider, $rawStatus); } /** * @param array $overrides keyed by "provider:raw_status" */ public static function descriptionWithOverrides(string $provider, string $rawStatus, array $overrides): string { $key = $provider . ':' . $rawStatus; if (isset($overrides[$key]) && $overrides[$key]['description'] !== '') { return $overrides[$key]['description']; } return self::description($provider, $rawStatus); } 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, 'allegro_edge' => self::ALLEGRO_EDGE_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, 'allegro_edge' => self::ALLEGRO_EDGE_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); } public static function slugifyAllegroDescription(string $description): string { $text = trim($description); if ($text === '') { return 'unknown'; } // Usuń typowe prefiksy $text = preg_replace('/^Przesy[łl]ka zosta[łl]a\s+/ui', '', $text); $text = preg_replace('/^Kurier\s+/ui', '', $text); $text = preg_replace('/^Paczka zosta[łl]a\s+/ui', '', $text); // Polskie znaki na ASCII $polish = ['ą','ć','ę','ł','ń','ó','ś','ź','ż','Ą','Ć','Ę','Ł','Ń','Ó','Ś','Ź','Ż']; $ascii = ['a','c','e','l','n','o','s','z','z','A','C','E','L','N','O','S','Z','Z']; $text = str_replace($polish, $ascii, $text); // Lowercase, zamień nie-alfanumeryczne na podkreślenia $text = strtolower($text); $text = preg_replace('/[^a-z0-9]+/', '_', $text); $text = trim($text, '_'); return $text !== '' ? $text : 'unknown'; } /** * Keyword-based fallback for unknown Allegro edge descriptions. * Used when slugified description is not in ALLEGRO_EDGE_MAP. */ public static function guessStatusFromDescription(string $description): string { $lower = mb_strtolower($description, 'UTF-8'); if (str_contains($lower, 'doręczon') || str_contains($lower, 'dostarczono') || str_contains($lower, 'odebrana przez odbiorc')) { return self::DELIVERED; } if (str_contains($lower, 'zwrócon') || str_contains($lower, 'zwrocona')) { return self::RETURNED; } if (str_contains($lower, 'anulowan')) { return self::CANCELLED; } if (str_contains($lower, 'doręczeni') || str_contains($lower, 'doreczenia') || str_contains($lower, 'wydana do')) { return self::OUT_FOR_DELIVERY; } if (str_contains($lower, 'sortowni') || str_contains($lower, 'magazyn') || str_contains($lower, 'w drodze') || str_contains($lower, 'tranzyt') || str_contains($lower, 'kurier') || str_contains($lower, 'podjęta') || str_contains($lower, 'podjeta') || str_contains($lower, 'wyjechał') || str_contains($lower, 'wyjechala')) { return self::IN_TRANSIT; } if (str_contains($lower, 'oczekuje na odb') || str_contains($lower, 'gotowa do odb') || (str_contains($lower, 'odbiór') && !str_contains($lower, 'w drodze'))) { return self::READY_FOR_PICKUP; } if (str_contains($lower, 'nadana') || str_contains($lower, 'nadano')) { return self::CONFIRMED; } if (str_contains($lower, 'przygotowan') || str_contains($lower, 'utworzon')) { return self::CREATED; } if (str_contains($lower, 'uszkodzon') || str_contains($lower, 'problem') || str_contains($lower, 'zagubion')) { return self::PROBLEM; } return self::UNKNOWN; } public static function trackingUrl(string $provider, string $trackingNumber, string $carrierId = ''): ?string { $number = trim($trackingNumber); if ($number === '') { return null; } $encoded = rawurlencode($number); if ($provider === 'inpost') { return 'https://inpost.pl/sledzenie-przesylek?number=' . $encoded; } if ($carrierId !== '') { $url = self::matchCarrierByName($encoded, strtolower(trim($carrierId))); if ($url !== null) { return $url; } } if ($provider === 'allegro_wza') { return 'https://allegro.pl/allegrodelivery/sledzenie-paczki?numer=' . $encoded; } return 'https://www.google.com/search?q=' . $encoded . '+sledzenie+przesylki'; } private static function matchCarrierByName(string $encoded, string $carrier): ?string { if (str_contains($carrier, 'dpd')) { return 'https://tracktrace.dpd.com.pl/parcelDetails?p1=' . $encoded; } if (str_contains($carrier, 'dhl')) { return 'https://www.dhl.com/pl-pl/home/sledzenie-przesylki.html?tracking-id=' . $encoded; } if (str_contains($carrier, 'inpost') || str_contains($carrier, 'paczkomat')) { return 'https://inpost.pl/sledzenie-przesylek?number=' . $encoded; } if (str_contains($carrier, 'orlen') || str_contains($carrier, 'ruch')) { return 'https://www.orlenpaczka.pl/sledz-paczke/?numer=' . $encoded; } if (str_contains($carrier, 'poczta') || str_contains($carrier, 'pocztex')) { return 'https://emonitoring.poczta-polska.pl/?numer=' . $encoded; } if (str_contains($carrier, 'ups')) { return 'https://www.ups.com/track?tracknum=' . $encoded; } if (str_contains($carrier, 'fedex')) { return 'https://www.fedex.com/fedextrack/?trknbr=' . $encoded; } if (str_contains($carrier, 'gls')) { return 'https://gls-group.com/PL/pl/sledzenie-paczek?match=' . $encoded; } if ($carrier === 'allegro') { return 'https://allegro.pl/allegrodelivery/sledzenie-paczki?numer=' . $encoded; } return null; } }