--- phase: 66-allegro-delivery-tracking plan: 02 type: execute wave: 2 depends_on: ["66-01"] files_modified: - src/Modules/Shipments/DeliveryStatus.php - src/Modules/Shipments/AllegroTrackingService.php autonomous: true delegation: auto --- ## Goal 1. Uzupełnić mapę ALLEGRO_EDGE_MAP o brakujące statusy z realnych przesyłek 2. Dodać mechanizm keyword-based fallback (guessStatusFromDescription) dla nieznanych opisów 3. Logować nowe nierozpoznane statusy do activity_log ## Purpose Edge API Allegro zwraca opisy po polsku bez ustalonego słownika — mogą pojawić się nowe warianty. Hardcoded mapa nie wystarczy. Fallback + logowanie = system sam sobie radzi z nowymi opisami i informuje admina. ## Output - Rozszerzona mapa ALLEGRO_EDGE_MAP o 5 nowych slugów - Metoda guessStatusFromDescription() w DeliveryStatus jako keyword fallback - Logowanie nieznanych statusów w AllegroTrackingService ## Prior Work @.paul/phases/66-allegro-delivery-tracking/66-01-PLAN.md ## Nowe statusy z realnego zamówienia AD0243IOG6 | Slug | Opis | Mapowanie | |------|------|-----------| | podjeta_z_maszyny_przez_kuriera | Przesyłka została podjęta z maszyny przez kuriera | in_transit | | przesylka_wyjechala_w_droge_do_punktu_docelowego | Przesyłka wyjechała w drogę do punktu docelowego | in_transit | | wyslana_z_sortowni | Wysłana z sortowni | in_transit | | wydana_do_doreczenia | Przesyłka została wydana do doręczenia | out_for_delivery | | przesylka_oczekuje_na_odbior | Przesyłka oczekuje na odbiór | ready_for_pickup | ## AC-1: Nowe slugi w mapie ```gherkin Given opis "Wysłana z sortowni" z edge API When slugify + normalize Then zwraca 'in_transit' (nie 'unknown') ``` ## AC-2: Fallback keyword matching ```gherkin Given nieznany opis np. "Paczka jest w drodze do odbiorcy" When slug nie istnieje w ALLEGRO_EDGE_MAP Then guessStatusFromDescription() dopasowuje na podstawie słów kluczowych And zwraca odpowiedni znormalizowany status ``` ## AC-3: Logowanie nieznanych statusów ```gherkin Given opis z edge API którego slug NIE jest w mapie i fallback zwraca unknown When AllegroTrackingService przetwarza taki status Then loguje do error_log: "[AllegroTracking] Nowy niezmapowany status: {opis} (slug: {slug})" And nadal zwraca wynik z status=unknown (nie null) ``` Task 1: Rozszerzenie mapy + guessStatusFromDescription src/Modules/Shipments/DeliveryStatus.php 1. Dodaj brakujące slugi do ALLEGRO_EDGE_MAP: ```php 'podjeta_z_maszyny_przez_kuriera' => self::IN_TRANSIT, 'przesylka_wyjechala_w_droge_do_punktu_docelowego' => self::IN_TRANSIT, 'wyjechala_w_droge_do_punktu_docelowego' => self::IN_TRANSIT, 'wyslana_z_sortowni' => self::IN_TRANSIT, 'wydana_do_doreczenia' => self::OUT_FOR_DELIVERY, 'przesylka_oczekuje_na_odbior' => self::READY_FOR_PICKUP, ``` 2. Dodaj odpowiednie opisy do ALLEGRO_EDGE_DESCRIPTIONS: ```php 'podjeta_z_maszyny_przez_kuriera' => 'Podjęta z maszyny przez kuriera', 'przesylka_wyjechala_w_droge_do_punktu_docelowego' => 'Wyjechała w drogę do punktu docelowego', 'wyjechala_w_droge_do_punktu_docelowego' => 'Wyjechała w drogę do punktu docelowego', 'wyslana_z_sortowni' => 'Wysłana z sortowni', 'wydana_do_doreczenia' => 'Wydana do doręczenia', 'przesylka_oczekuje_na_odbior' => 'Oczekuje na odbiór', ``` 3. Dodaj nową statyczną metodę `guessStatusFromDescription(string $description): string` — keyword-based fallback. Umieść ją po slugifyAllegroDescription(): ```php public static function guessStatusFromDescription(string $description): string { $lower = mb_strtolower($description, 'UTF-8'); // Terminal statuses first 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; } // Active statuses if (str_contains($lower, 'doręczeni') || str_contains($lower, 'doreczenia') || str_contains($lower, 'wydana do')) { return self::OUT_FOR_DELIVERY; } if (str_contains($lower, 'odbiór') || str_contains($lower, 'odbior') || str_contains($lower, 'oczekuje na odb')) { return self::READY_FOR_PICKUP; } 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, '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, 'zagubiła') || str_contains($lower, 'zagubion')) { return self::PROBLEM; } return self::UNKNOWN; } ``` WAŻNE: NIE zmieniaj istniejących metod normalize(), description(), slugifyAllegroDescription(). Tylko DODAWAJ nowe wpisy do map i nową metodę. php -l przechodzi. Test: DeliveryStatus::normalize('allegro_edge', 'wyslana_z_sortowni') zwraca 'in_transit'. Test: DeliveryStatus::guessStatusFromDescription('Paczka jest w drodze') zwraca 'in_transit'. AC-1 satisfied: nowe slugi w mapie. AC-2 satisfied: fallback method istnieje. Task 2: Integracja fallback + logowanie w AllegroTrackingService src/Modules/Shipments/AllegroTrackingService.php Zmodyfikuj metodę `fetchAllegroEdgeStatus()` — dodaj fallback i logowanie. Po linii: ```php $slug = DeliveryStatus::slugifyAllegroDescription($description); ``` Zamień blok return na: ```php $normalized = DeliveryStatus::normalize('allegro_edge', $slug); // Fallback: jeśli slug nieznany, próbuj keyword matching if ($normalized === DeliveryStatus::UNKNOWN) { $normalized = DeliveryStatus::guessStatusFromDescription($description); // Loguj niezmapowany status (do uzupełnienia mapy w przyszłości) error_log(sprintf( '[AllegroTracking] Niezmapowany status: "%s" (slug: %s, guessed: %s)', $description, $slug, $normalized )); } return [ 'status' => $normalized, 'status_raw' => $description, 'description' => $description, ]; ``` To zastępuje istniejący blok: ```php return [ 'status' => DeliveryStatus::normalize('allegro_edge', $slug), 'status_raw' => $description, 'description' => $description, ]; ``` WAŻNE: Nie zmieniaj nic innego w tym pliku. Tylko modyfikacja wewnątrz fetchAllegroEdgeStatus(). php -l przechodzi. Metoda fetchAllegroEdgeStatus zawiera fallback guessStatusFromDescription i error_log. AC-2 partially satisfied: fallback zintegrowany. AC-3 satisfied: logowanie nieznanych statusów. ## DO NOT CHANGE - Istniejące mapy INPOST_MAP, APACZKA_MAP, ALLEGRO_MAP - Metody: normalize(), description(), slugifyAllegroDescription(), fetchInpostStatus() - src/Modules/Cron/ShipmentTrackingHandler.php (rate limit z 66-01 bez zmian) - Wszystkie inne pliki ## SCOPE LIMITS - Nie tworzymy UI do zarządzania mapowaniem (istniejący Delivery Status Mapping UI wystarczy) - Nie tworzymy unit testów w tym planie - [ ] php -l na obu plikach - [ ] DeliveryStatus::normalize('allegro_edge', 'wyslana_z_sortowni') === 'in_transit' - [ ] DeliveryStatus::normalize('allegro_edge', 'wydana_do_doreczenia') === 'out_for_delivery' - [ ] DeliveryStatus::guessStatusFromDescription('Paczka jest w drodze do odbiorcy') === 'in_transit' - [ ] DeliveryStatus::guessStatusFromDescription('Przesyłka odebrana w punkcie') !== 'unknown' - [ ] fetchAllegroEdgeStatus fallback + error_log działa - Oba taski completed - Wszystkie realne opisy z API (obu zamówień) mapują się na właściwe statusy - Nieznane przyszłe opisy są obsługiwane przez keyword fallback - Nieznane statusy logowane do error_log After completion, create `.paul/phases/66-allegro-delivery-tracking/66-02-SUMMARY.md`