update
This commit is contained in:
312
.paul/phases/66-allegro-delivery-tracking/66-01-PLAN.md
Normal file
312
.paul/phases/66-allegro-delivery-tracking/66-01-PLAN.md
Normal file
@@ -0,0 +1,312 @@
|
||||
---
|
||||
phase: 66-allegro-delivery-tracking
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- src/Modules/Shipments/AllegroTrackingService.php
|
||||
- src/Modules/Shipments/DeliveryStatus.php
|
||||
- src/Modules/Cron/ShipmentTrackingHandler.php
|
||||
autonomous: true
|
||||
delegation: auto
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Zaimplementować śledzenie statusu przesyłek Allegro Delivery (numery A-*) przez publiczne edge API Allegro. Aktualnie `AllegroTrackingService` zwraca `null` dla przesyłek nie-InPost. Po zmianie — pobiera statusy z `https://edge.allegro.pl/ad/tracking?packageNo={nr}` i normalizuje je do istniejącego systemu `DeliveryStatus`.
|
||||
|
||||
## Purpose
|
||||
Przesyłki Allegro Delivery (One Kurier, DPD via Allegro, itp.) nie mają śledzenia w orderPRO. Użytkownicy muszą ręcznie sprawdzać status na allegro.pl. Ta zmiana automatyzuje ten proces.
|
||||
|
||||
## Output
|
||||
- `AllegroTrackingService` pobiera statusy z edge API dla przesyłek non-InPost
|
||||
- `DeliveryStatus` ma rozszerzony mapping Allegro o statusy z edge API (opisy PL)
|
||||
- Rate limiting: max 1 request na minutę per przesyłka (w cron handler)
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
@.paul/STATE.md
|
||||
|
||||
## Source Files
|
||||
@src/Modules/Shipments/AllegroTrackingService.php
|
||||
@src/Modules/Shipments/DeliveryStatus.php
|
||||
@src/Modules/Cron/ShipmentTrackingHandler.php
|
||||
|
||||
## API Research
|
||||
Edge API endpoint (publiczny, bez autoryzacji):
|
||||
- URL: `https://edge.allegro.pl/ad/tracking?packageNo={trackingNumber}`
|
||||
- Header: `Accept: application/vnd.allegro.internal.v1+json`
|
||||
- Response: `{"status": [{"eventTimestamp": "ISO8601", "description": "Opis PL"}]}`
|
||||
- Ostatni element tablicy = aktualny status
|
||||
- Opisy są po polsku, np.:
|
||||
- "Przesyłka została przygotowana przez nadawcę"
|
||||
- "Przesyłka została nadana"
|
||||
- "Przesyłka została podjęta z punktu przez kuriera"
|
||||
- "Przesyłka została odebrana przez kuriera"
|
||||
- "Kurier przekazał przesyłkę do magazynu"
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Allegro Delivery tracking zwraca status
|
||||
```gherkin
|
||||
Given przesyłka z provider=allegro_wza i tracking_number zaczynający się od "A"
|
||||
When cron tracking handler odpytuje AllegroTrackingService
|
||||
Then serwis pobiera status z edge.allegro.pl/ad/tracking
|
||||
And zwraca znormalizowany status + opis po polsku
|
||||
```
|
||||
|
||||
## AC-2: Mapowanie opisów na znormalizowane statusy
|
||||
```gherkin
|
||||
Given odpowiedź z edge API zawiera description np. "Przesyłka została nadana"
|
||||
When AllegroTrackingService przetwarza odpowiedź
|
||||
Then mapuje opis na raw status (np. "shipped") i normalizuje przez DeliveryStatus
|
||||
And status_raw zawiera oryginalny opis z API
|
||||
```
|
||||
|
||||
## AC-3: Rate limiting — max 1 request/min
|
||||
```gherkin
|
||||
Given cron handler przetwarza wiele przesyłek allegro_wza
|
||||
When odpytuje edge API
|
||||
Then między kolejnymi requestami do edge.allegro.pl czeka minimum 60 sekund
|
||||
And inne providery (InPost, Apaczka) nie są objęte tym limitem
|
||||
```
|
||||
|
||||
## AC-4: Fallback InPost nadal działa
|
||||
```gherkin
|
||||
Given przesyłka z provider=allegro_wza i carrier_id zawierający "inpost"
|
||||
When cron tracking handler odpytuje AllegroTrackingService
|
||||
Then serwis nadal używa InPost API (nie edge API)
|
||||
And zachowanie jest identyczne jak przed zmianą
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Rozszerzenie DeliveryStatus o mapowanie Allegro edge API</name>
|
||||
<files>src/Modules/Shipments/DeliveryStatus.php</files>
|
||||
<action>
|
||||
Dodaj nową mapę `ALLEGRO_EDGE_MAP` i `ALLEGRO_EDGE_DESCRIPTIONS` do DeliveryStatus.
|
||||
|
||||
Edge API zwraca opisy po polsku (nie kody). Trzeba mapować opisy na wewnętrzne klucze, a potem na znormalizowane statusy.
|
||||
|
||||
Nowa mapa `ALLEGRO_EDGE_MAP` (klucz = slug z opisu, wartość = normalized status):
|
||||
```php
|
||||
private const ALLEGRO_EDGE_MAP = [
|
||||
'przygotowana_przez_nadawce' => self::CREATED,
|
||||
'nadana' => self::CONFIRMED,
|
||||
'podjeta_z_punktu' => self::IN_TRANSIT,
|
||||
'odebrana_przez_kuriera' => self::IN_TRANSIT,
|
||||
'przekazana_do_magazynu' => self::IN_TRANSIT,
|
||||
'w_sortowni' => self::IN_TRANSIT,
|
||||
'w_doreceniu' => self::OUT_FOR_DELIVERY,
|
||||
'gotowa_do_odbioru' => self::READY_FOR_PICKUP,
|
||||
'dostarczona' => self::DELIVERED,
|
||||
'doreczona' => self::DELIVERED,
|
||||
'zwrocona' => self::RETURNED,
|
||||
'anulowana' => self::CANCELLED,
|
||||
'problem' => self::PROBLEM,
|
||||
];
|
||||
```
|
||||
|
||||
Nowa mapa `ALLEGRO_EDGE_DESCRIPTIONS` — klucz = slug, wartość = oryginalny opis PL (identyczny jak z API).
|
||||
|
||||
Dodaj nowy provider `allegro_edge` do:
|
||||
- `PROVIDER_MAPS` array
|
||||
- `PROVIDER_DESCRIPTIONS` array
|
||||
- match w `normalize()` i `description()`
|
||||
|
||||
Dodaj statyczną metodę `slugifyAllegroDescription(string $description): string` — konwertuje opis PL na slug:
|
||||
1. Usuwa prefiks "Przesyłka została " / "Kurier "
|
||||
2. Bierze główne słowo kluczowe
|
||||
3. Zamienia polskie znaki na ASCII
|
||||
4. Zamienia spacje na podkreślenia
|
||||
5. Zwraca lowercase slug
|
||||
|
||||
WAŻNE: Metoda musi obsługiwać nieznane opisy — jeśli slug nie istnieje w mapie, zwróć sam slug jako raw_status i self::UNKNOWN jako normalized.
|
||||
</action>
|
||||
<verify>Klasa DeliveryStatus kompiluje się bez błędów. Nowe stałe ALLEGRO_EDGE_MAP i ALLEGRO_EDGE_DESCRIPTIONS istnieją. Provider 'allegro_edge' jest obsługiwany w normalize() i description().</verify>
|
||||
<done>AC-2 satisfied: mapowanie opisów na znormalizowane statusy zdefiniowane.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Implementacja fetchAllegroEdgeStatus w AllegroTrackingService</name>
|
||||
<files>src/Modules/Shipments/AllegroTrackingService.php</files>
|
||||
<action>
|
||||
Zmodyfikuj `AllegroTrackingService::getDeliveryStatus()` aby obsługiwał przesyłki Allegro Delivery (non-InPost):
|
||||
|
||||
1. W metodzie `getDeliveryStatus()`, po bloku `if (str_contains(... 'inpost'))`, zamiast `return null` dodaj:
|
||||
```php
|
||||
return $this->fetchAllegroEdgeStatus($trackingNumber);
|
||||
```
|
||||
|
||||
2. Dodaj nową prywatną metodę `fetchAllegroEdgeStatus(string $trackingNumber): ?array`:
|
||||
```php
|
||||
private function fetchAllegroEdgeStatus(string $trackingNumber): ?array
|
||||
{
|
||||
try {
|
||||
$url = 'https://edge.allegro.pl/ad/tracking?packageNo=' . rawurlencode($trackingNumber);
|
||||
$response = $this->edgeApiRequest($url);
|
||||
|
||||
$statuses = $response['status'] ?? [];
|
||||
if (!is_array($statuses) || $statuses === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ostatni element = najnowszy status
|
||||
$latest = end($statuses);
|
||||
$description = trim((string) ($latest['description'] ?? ''));
|
||||
if ($description === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$slug = DeliveryStatus::slugifyAllegroDescription($description);
|
||||
|
||||
return [
|
||||
'status' => DeliveryStatus::normalize('allegro_edge', $slug),
|
||||
'status_raw' => $description,
|
||||
'description' => $description,
|
||||
];
|
||||
} catch (Throwable) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Dodaj nową prywatną metodę `edgeApiRequest(string $url): array` — osobna od istniejącej `apiRequest()` bo nie wymaga autoryzacji:
|
||||
```php
|
||||
private function edgeApiRequest(string $url): array
|
||||
{
|
||||
$ch = curl_init($url);
|
||||
if ($ch === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$opts = [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_CONNECTTIMEOUT => 5,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Accept: application/vnd.allegro.internal.v1+json',
|
||||
'Content-Type: application/vnd.allegro.internal.v1+json',
|
||||
],
|
||||
];
|
||||
|
||||
$caPath = $this->getCaBundlePath();
|
||||
if ($caPath !== null) {
|
||||
$opts[CURLOPT_CAINFO] = $caPath;
|
||||
}
|
||||
|
||||
curl_setopt_array($ch, $opts);
|
||||
$body = curl_exec($ch);
|
||||
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$ch = null;
|
||||
|
||||
if ($body === false || $httpCode < 200 || $httpCode >= 300) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$json = json_decode((string) $body, true);
|
||||
return is_array($json) ? $json : [];
|
||||
}
|
||||
```
|
||||
|
||||
NIE zmieniaj istniejącej metody `fetchInpostStatus()` ani `apiRequest()` — to osobne ścieżki.
|
||||
</action>
|
||||
<verify>AllegroTrackingService kompiluje się. Metoda getDeliveryStatus nie zwraca null dla non-InPost przesyłek (wywołuje fetchAllegroEdgeStatus). Metoda edgeApiRequest wysyła Accept: application/vnd.allegro.internal.v1+json.</verify>
|
||||
<done>AC-1 satisfied: Allegro Delivery tracking pobiera status z edge API. AC-4 satisfied: InPost path bez zmian.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Rate limiting w ShipmentTrackingHandler dla Allegro edge API</name>
|
||||
<files>src/Modules/Cron/ShipmentTrackingHandler.php</files>
|
||||
<action>
|
||||
Dodaj rate limiting do `ShipmentTrackingHandler::handle()` — max 1 request na 60 sekund do edge.allegro.pl.
|
||||
|
||||
1. Dodaj stałą:
|
||||
```php
|
||||
private const ALLEGRO_EDGE_RATE_LIMIT_SECONDS = 60;
|
||||
```
|
||||
|
||||
2. W metodzie `handle()`, przed pętlą `foreach`, dodaj zmienną śledzącą czas ostatniego requestu Allegro edge:
|
||||
```php
|
||||
$lastAllegroEdgeRequestTime = 0.0;
|
||||
```
|
||||
|
||||
3. Wewnątrz pętli `foreach`, PO uzyskaniu `$service` a PRZED wywołaniem `$service->getDeliveryStatus()`, dodaj sprawdzenie:
|
||||
```php
|
||||
if ($provider === 'allegro_wza') {
|
||||
$carrierId = strtolower(trim((string) ($package['carrier_id'] ?? '')));
|
||||
$isInpost = str_contains($carrierId, 'inpost') || str_contains($carrierId, 'paczkomat');
|
||||
|
||||
if (!$isInpost) {
|
||||
$elapsed = microtime(true) - $lastAllegroEdgeRequestTime;
|
||||
if ($elapsed < self::ALLEGRO_EDGE_RATE_LIMIT_SECONDS) {
|
||||
$sleepTime = (int) ceil(self::ALLEGRO_EDGE_RATE_LIMIT_SECONDS - $elapsed);
|
||||
sleep($sleepTime);
|
||||
}
|
||||
// Zaraz po sleep (lub bez), przed wywołaniem getDeliveryStatus:
|
||||
$lastAllegroEdgeRequestTime = microtime(true);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
WAŻNE:
|
||||
- Rate limit dotyczy TYLKO requestów do edge.allegro.pl (non-InPost allegro_wza)
|
||||
- InPost i Apaczka requestów NIE ograniczaj
|
||||
- sleep() w cronie jest OK — to background process
|
||||
</action>
|
||||
<verify>ShipmentTrackingHandler kompiluje się. Stała ALLEGRO_EDGE_RATE_LIMIT_SECONDS = 60 istnieje. Kod rate limitingu jest w pętli foreach przed getDeliveryStatus dla allegro_wza non-inpost.</verify>
|
||||
<done>AC-3 satisfied: max 1 request/min do edge.allegro.pl.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- src/Modules/Shipments/InpostTrackingService.php
|
||||
- src/Modules/Shipments/ApaczkaTrackingService.php
|
||||
- src/Modules/Shipments/ShipmentTrackingInterface.php
|
||||
- src/Modules/Shipments/ShipmentTrackingRegistry.php
|
||||
- src/Modules/Shipments/ShipmentPackageRepository.php
|
||||
- src/Modules/Cron/CronHandlerFactory.php
|
||||
- Istniejące mapy INPOST_MAP, APACZKA_MAP w DeliveryStatus
|
||||
- Istniejąca metoda fetchInpostStatus() w AllegroTrackingService
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Nie tworzymy nowych klas — rozszerzamy istniejące
|
||||
- Nie zmieniamy schematu DB — delivery_status_raw już obsługuje dowolne stringi
|
||||
- Nie dodajemy UI — tracking UI już istnieje i działa z dowolnym statusem
|
||||
- Nie tworzymy unit testów w tym planie
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] DeliveryStatus::normalize('allegro_edge', 'nadana') zwraca 'confirmed'
|
||||
- [ ] DeliveryStatus::slugifyAllegroDescription('Przesyłka została nadana') zwraca slug mapowany na status
|
||||
- [ ] AllegroTrackingService::getDeliveryStatus() dla non-InPost allegro_wza wywołuje edge API
|
||||
- [ ] AllegroTrackingService::getDeliveryStatus() dla InPost allegro_wza nadal używa InPost API
|
||||
- [ ] ShipmentTrackingHandler ma rate limit 60s między requestami edge.allegro.pl
|
||||
- [ ] Żadne chronione pliki nie zostały zmodyfikowane
|
||||
- [ ] PHP syntax check: `php -l` na każdym zmienionym pliku
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Wszystkie 3 taski auto completed
|
||||
- Przesyłki Allegro Delivery (A-numery) mają automatyczny tracking statusu
|
||||
- Rate limit chroni przed blokadą przez Allegro
|
||||
- Istniejący tracking InPost i Apaczka bez zmian
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/66-allegro-delivery-tracking/66-01-SUMMARY.md`
|
||||
</output>
|
||||
96
.paul/phases/66-allegro-delivery-tracking/66-01-SUMMARY.md
Normal file
96
.paul/phases/66-allegro-delivery-tracking/66-01-SUMMARY.md
Normal file
@@ -0,0 +1,96 @@
|
||||
---
|
||||
phase: 66-allegro-delivery-tracking
|
||||
plan: 01
|
||||
subsystem: shipments
|
||||
tags: [allegro, tracking, edge-api, delivery-status, cron]
|
||||
|
||||
requires:
|
||||
- phase: 27-shipment-tracking-backend
|
||||
provides: ShipmentTrackingInterface, DeliveryStatus, ShipmentTrackingHandler
|
||||
|
||||
provides:
|
||||
- Allegro Delivery tracking via edge.allegro.pl API
|
||||
- Edge API integration (no auth, public endpoint)
|
||||
- Rate limiting for edge API requests (60s)
|
||||
|
||||
affects: [allegro-tracking, delivery-status, cron-handler]
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns: [edge-api-integration, rate-limited-cron]
|
||||
|
||||
key-files:
|
||||
created: []
|
||||
modified:
|
||||
- src/Modules/Shipments/AllegroTrackingService.php
|
||||
- src/Modules/Shipments/DeliveryStatus.php
|
||||
- src/Modules/Cron/ShipmentTrackingHandler.php
|
||||
|
||||
key-decisions:
|
||||
- "Edge API (edge.allegro.pl/ad/tracking) zamiast Allegro REST API (nie daje tracking statusów)"
|
||||
- "Accept: application/vnd.allegro.internal.v1+json — wymagany nagłówek"
|
||||
- "Provider allegro_edge osobny od allegro_wza w DeliveryStatus"
|
||||
- "Rate limit 60s między requestami do edge API"
|
||||
|
||||
patterns-established:
|
||||
- "slugifyAllegroDescription() konwertuje opisy PL na slugi mapowalne na statusy"
|
||||
- "Osobna metoda edgeApiRequest() bez Bearer token (publiczny endpoint)"
|
||||
|
||||
duration: ~15min
|
||||
started: 2026-04-03T20:30:00Z
|
||||
completed: 2026-04-03T20:45:00Z
|
||||
---
|
||||
|
||||
# Phase 66 Plan 01: Allegro Delivery Tracking — Core Integration
|
||||
|
||||
**Integracja śledzenia przesyłek Allegro Delivery przez publiczne edge API — fetchAllegroEdgeStatus, mapowanie opisów PL na znormalizowane statusy, rate limiting 60s w cronie.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~15min |
|
||||
| Tasks | 3 completed (delegated) |
|
||||
| Files modified | 3 |
|
||||
| Execution mode | Delegated auto (3 sub-agents) |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Allegro Delivery tracking zwraca status | Pass | fetchAllegroEdgeStatus pobiera z edge API |
|
||||
| AC-2: Mapowanie opisów na statusy | Pass | slugify + ALLEGRO_EDGE_MAP |
|
||||
| AC-3: Rate limiting max 1 req/min | Pass | sleep() w ShipmentTrackingHandler |
|
||||
| AC-4: Fallback InPost nadal działa | Pass | Warunek carrier_id inpost/paczkomat bez zmian |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- AllegroTrackingService pobiera statusy z edge.allegro.pl/ad/tracking dla non-InPost przesyłek
|
||||
- DeliveryStatus ma provider 'allegro_edge' z mapą slugów i opisów PL
|
||||
- ShipmentTrackingHandler throttluje requesty do edge API (60s)
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `src/Modules/Shipments/DeliveryStatus.php` | Modified | ALLEGRO_EDGE_MAP, ALLEGRO_EDGE_DESCRIPTIONS, slugifyAllegroDescription() |
|
||||
| `src/Modules/Shipments/AllegroTrackingService.php` | Modified | fetchAllegroEdgeStatus(), edgeApiRequest() |
|
||||
| `src/Modules/Cron/ShipmentTrackingHandler.php` | Modified | Rate limiting 60s dla allegro_wza non-inpost |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. Slug mismatch w mapie**
|
||||
- **Found during:** Orkiestrator verification
|
||||
- **Issue:** "Kurier przekazał przesyłkę do magazynu" → slug `przekazal_przesylke_do_magazynu` nie był w mapie (była `przekazana_do_magazynu`)
|
||||
- **Fix:** Dodano wariant do ALLEGRO_EDGE_MAP
|
||||
- **Verification:** Test 5/5 realnych opisów mapuje poprawnie
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:** Plan 66-02 rozszerza mapę i dodaje keyword fallback
|
||||
|
||||
---
|
||||
*Phase: 66-allegro-delivery-tracking, Plan: 01*
|
||||
*Completed: 2026-04-03*
|
||||
228
.paul/phases/66-allegro-delivery-tracking/66-02-PLAN.md
Normal file
228
.paul/phases/66-allegro-delivery-tracking/66-02-PLAN.md
Normal file
@@ -0,0 +1,228 @@
|
||||
---
|
||||
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
|
||||
---
|
||||
|
||||
<objective>
|
||||
## 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
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## 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 |
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## 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)
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Rozszerzenie mapy + guessStatusFromDescription</name>
|
||||
<files>src/Modules/Shipments/DeliveryStatus.php</files>
|
||||
<action>
|
||||
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ę.
|
||||
</action>
|
||||
<verify>php -l przechodzi. Test: DeliveryStatus::normalize('allegro_edge', 'wyslana_z_sortowni') zwraca 'in_transit'. Test: DeliveryStatus::guessStatusFromDescription('Paczka jest w drodze') zwraca 'in_transit'.</verify>
|
||||
<done>AC-1 satisfied: nowe slugi w mapie. AC-2 satisfied: fallback method istnieje.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Integracja fallback + logowanie w AllegroTrackingService</name>
|
||||
<files>src/Modules/Shipments/AllegroTrackingService.php</files>
|
||||
<action>
|
||||
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().
|
||||
</action>
|
||||
<verify>php -l przechodzi. Metoda fetchAllegroEdgeStatus zawiera fallback guessStatusFromDescription i error_log.</verify>
|
||||
<done>AC-2 partially satisfied: fallback zintegrowany. AC-3 satisfied: logowanie nieznanych statusów.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## 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
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
- [ ] 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
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 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
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/66-allegro-delivery-tracking/66-02-SUMMARY.md`
|
||||
</output>
|
||||
93
.paul/phases/66-allegro-delivery-tracking/66-02-SUMMARY.md
Normal file
93
.paul/phases/66-allegro-delivery-tracking/66-02-SUMMARY.md
Normal file
@@ -0,0 +1,93 @@
|
||||
---
|
||||
phase: 66-allegro-delivery-tracking
|
||||
plan: 02
|
||||
subsystem: shipments
|
||||
tags: [allegro, tracking, fallback, keyword-matching, logging]
|
||||
|
||||
requires:
|
||||
- phase: 66-allegro-delivery-tracking
|
||||
provides: AllegroTrackingService edge API, DeliveryStatus allegro_edge provider
|
||||
|
||||
provides:
|
||||
- Keyword-based fallback for unknown Allegro edge descriptions
|
||||
- Error logging for unmapped statuses
|
||||
- Extended ALLEGRO_EDGE_MAP with 6 new real-world slugs
|
||||
|
||||
affects: [allegro-tracking, delivery-status]
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns: [keyword-fallback, graceful-degradation]
|
||||
|
||||
key-files:
|
||||
modified:
|
||||
- src/Modules/Shipments/DeliveryStatus.php
|
||||
- src/Modules/Shipments/AllegroTrackingService.php
|
||||
|
||||
key-decisions:
|
||||
- "Keyword fallback zamiast pytania użytkownika (cron nie ma interakcji)"
|
||||
- "error_log dla niezmapowanych statusów (monitoring bez blokowania)"
|
||||
- "IN_TRANSIT sprawdzany przed READY_FOR_PICKUP w fallback (uniknięcie fałszywego matchu 'odbior' w 'w drodze do odbiorcy')"
|
||||
|
||||
patterns-established:
|
||||
- "guessStatusFromDescription() jako graceful degradation dla nieznanych opisów"
|
||||
- "Kolejność keyword matching: terminal → active → transit → pickup (specyficzność malejąca)"
|
||||
|
||||
duration: ~10min
|
||||
started: 2026-04-03T20:45:00Z
|
||||
completed: 2026-04-03T20:55:00Z
|
||||
---
|
||||
|
||||
# Phase 66 Plan 02: Allegro Tracking Fallback + Extended Map
|
||||
|
||||
**Rozszerzenie mapy o 6 nowych slugów z realnych przesyłek, keyword-based fallback (guessStatusFromDescription) dla nieznanych opisów, logowanie nowych statusów do error_log.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~10min |
|
||||
| Tasks | 2 completed (delegated) + 1 orkiestrator fix |
|
||||
| Files modified | 2 |
|
||||
| Execution mode | Delegated auto (2 sub-agents, sequential) |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Nowe slugi w mapie | Pass | 6 nowych slugów z zamówienia AD0243IOG6 |
|
||||
| AC-2: Fallback keyword matching | Pass | guessStatusFromDescription() — 9 kategorii keywords |
|
||||
| AC-3: Logowanie nieznanych statusów | Pass | error_log w fetchAllegroEdgeStatus |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- 6 nowych slugów w ALLEGRO_EDGE_MAP z realnego zamówienia AD0243IOG6
|
||||
- guessStatusFromDescription() jako keyword fallback — 9 kategorii (delivered, returned, cancelled, out_for_delivery, ready_for_pickup, in_transit, confirmed, created, problem)
|
||||
- Logowanie niezmapowanych statusów do error_log z pełnym kontekstem (opis, slug, guessed status)
|
||||
- Fix kolejności keyword matching: IN_TRANSIT przed READY_FOR_PICKUP
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `src/Modules/Shipments/DeliveryStatus.php` | Modified | +6 slugów w mapie, +guessStatusFromDescription() |
|
||||
| `src/Modules/Shipments/AllegroTrackingService.php` | Modified | Fallback + error_log w fetchAllegroEdgeStatus() |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. Kolejność keyword matching**
|
||||
- **Found during:** Orkiestrator verification
|
||||
- **Issue:** "w drodze do odbiorcy" matchował READY_FOR_PICKUP (bo `odbior`) zamiast IN_TRANSIT
|
||||
- **Fix:** IN_TRANSIT sprawdzany przed READY_FOR_PICKUP, READY_FOR_PICKUP ograniczony do `oczekuje na odb`/`gotowa do odb`
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:** Phase 66 complete — Allegro Delivery tracking działa z mapą + fallback + logowaniem
|
||||
|
||||
**Blockers:** None
|
||||
|
||||
---
|
||||
*Phase: 66-allegro-delivery-tracking, Plan: 02*
|
||||
*Completed: 2026-04-03*
|
||||
Reference in New Issue
Block a user