refactor(shipments): rozbij DeliveryStatus na fasade + 3 wspolpracownikow
DeliveryStatus (657 lin.) byl god-klasa statusow uzywana globalnie (56 wywolan w 20 plikach). ~400 linii to tablice const map dostawcow. Wydzielono zachowujac 100% kontraktu publicznego (fasada deleguje): - DeliveryStatusProviderMap - mapy surowych statusow + normalize/description/getDefaultMappings/overrides - AllegroDescriptionGuesser - slugify + guessStatusFromDescription + wzorce - DeliveryTrackingUrlBuilder - trackingUrl + URL kurierow/dostawcow DeliveryStatus.php 657 -> 170 lin. Zero zmian w plikach konsumentow. tests/Unit/DeliveryStatusTest.php przechodzi bez modyfikacji (4/4). Plan/SUMMARY: .paul/plans/20260519-1730-refactor-delivery-status/ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,30 +4,33 @@
|
|||||||
**Ostatnia aktualizacja:** 2026-05-19
|
**Ostatnia aktualizacja:** 2026-05-19
|
||||||
|
|
||||||
## Aktywna praca
|
## Aktywna praca
|
||||||
UNIFY zakonczony dla `.paul/plans/20260519-1600-refactor-allegro-integration-controller/`. Petla zamknieta, brak aktywnego planu.
|
UNIFY zakonczony dla `.paul/plans/20260519-1730-refactor-delivery-status/`. Petla zamknieta, brak aktywnego planu.
|
||||||
|
|
||||||
```
|
```
|
||||||
PLAN ──▶ APPLY ──▶ UNIFY
|
PLAN ──▶ APPLY ──▶ UNIFY
|
||||||
✓ ✓ ✓ [Loop complete — gotowe na nastepny PLAN]
|
✓ ✓ ✓ [Loop complete — gotowe na nastepny PLAN]
|
||||||
```
|
```
|
||||||
|
|
||||||
Rezultat: `AllegroIntegrationController` 653 -> 223 lin. (66% redukcji). Wydzielono `AllegroIntegrationViewModel` (61), `AllegroOAuthFlowService` (94), `AllegroImportScheduleService` (179), `AllegroImportImageWarningFormatter` (69), `AllegroSaveSettingsValidator` (48). Deviation: kontroler 223 vs cel < 200 (akceptowalne, 66% redukcji).
|
Rezultat: `DeliveryStatus.php` 657 -> 170 lin. (fasada zachowujaca pelny kontrakt). Wydzielono `DeliveryStatusProviderMap` (~330), `AllegroDescriptionGuesser` (~150), `DeliveryTrackingUrlBuilder` (~85). Zero zmian w 20 plikach konsumentow; `tests/Unit/DeliveryStatusTest.php` 4/4 bez modyfikacji (primary gate). Pelny suite 3/15 identyczny z baseline (pre-existing). SUMMARY: `.paul/plans/20260519-1730-refactor-delivery-status/SUMMARY.md`.
|
||||||
|
|
||||||
|
## Poprzednia praca
|
||||||
|
UNIFY zakonczony dla `.paul/plans/20260519-1600-refactor-allegro-integration-controller/`. Rezultat: `AllegroIntegrationController` 653 -> 223 lin. (66% redukcji). Wydzielono `AllegroIntegrationViewModel` (61), `AllegroOAuthFlowService` (94), `AllegroImportScheduleService` (179), `AllegroImportImageWarningFormatter` (69), `AllegroSaveSettingsValidator` (48). Deviation: kontroler 223 vs cel < 200 (akceptowalne, 66% redukcji).
|
||||||
|
|
||||||
Zamkniete SUMMARY (chronologicznie 2026-05-19):
|
Zamkniete SUMMARY (chronologicznie 2026-05-19):
|
||||||
- `.paul/plans/20260519-1200-refactor-routes-web/SUMMARY.md` (routes/web.php: 859 -> 78).
|
- `.paul/plans/20260519-1200-refactor-routes-web/SUMMARY.md` (routes/web.php: 859 -> 78).
|
||||||
- `.paul/plans/20260519-1430-refactor-orders-statistics-controller/SUMMARY.md` (Statistics: 640 -> 110).
|
- `.paul/plans/20260519-1430-refactor-orders-statistics-controller/SUMMARY.md` (Statistics: 640 -> 110).
|
||||||
- `.paul/plans/20260519-1600-refactor-allegro-integration-controller/SUMMARY.md` (Allegro: 653 -> 223).
|
- `.paul/plans/20260519-1600-refactor-allegro-integration-controller/SUMMARY.md` (Allegro: 653 -> 223).
|
||||||
|
- `.paul/plans/20260519-1730-refactor-delivery-status/SUMMARY.md` (DeliveryStatus: 657 -> 170, fasada + 3 klasy).
|
||||||
|
|
||||||
## Kontekst sesji
|
## Kontekst sesji
|
||||||
- Galaz: `main`.
|
- Galaz: `main`.
|
||||||
- Niezakomitowane: dekompozycja Statistics + Allegro (8 nowych plikow modulu + 4 zmodyfikowane + dokumenty PAUL + STATE.md + governance log + changelog).
|
- Niezakomitowane: dekompozycja Statistics + Allegro + DeliveryStatus (11 nowych plikow modulow + 5 zmodyfikowanych + dokumenty PAUL + STATE.md + governance log + changelog).
|
||||||
- Ostatnie commity: `3809340 update`, `e77b0f1 refactor(routing): module providers + lazy ServiceRegistry`, `2df4638 update`.
|
- Ostatnie commity: `e12ebe3 update`, `3809340 update`, `e77b0f1 refactor(routing): module providers + lazy ServiceRegistry`.
|
||||||
|
|
||||||
## Sugerowana nastepna akcja
|
## Sugerowana nastepna akcja
|
||||||
1. Smoke-test reczny zgodnie z sekcja "Smoke-test" w obu SUMMARY (Statistics + Allegro) przed commitem.
|
1. Smoke-test reczny przed commitem: widoki `/orders/show` + `/shipments/prepare` (label/description/getColor/trackingUrl/ALL_STATUSES) oraz tracking Apaczka/InPost/Polkurier (DB byla niedostepna w sesji APPLY).
|
||||||
2. Commit: refaktor Statistics + Allegro (8 nowych plikow + 4 zmodyfikowane + docs PAUL + plany).
|
2. Commit zaleglej dekompozycji: Statistics + Allegro + DeliveryStatus.
|
||||||
3. Kolejny kandydat z `quality_risks.md`: `OrdersController.php` (1490 lin.), `OrdersRepository.php` (1243 lin.), `ShopproIntegrationsController.php` (1076 lin.), albo bazowy `BaseIntegrationController` dla 9 integracji.
|
3. `$paul-plan` na kolejnego kandydata z `quality_risks.md`: `OrdersController.php` (1490 lin.), `OrdersRepository.php` (1243 lin.), `ShopproIntegrationsController.php` (1076 lin.).
|
||||||
4. `$paul-plan` aby uruchomic nowa petle.
|
|
||||||
|
|
||||||
## Legacy
|
## Legacy
|
||||||
Pliki `ROADMAP.md` / `MILESTONES.md` sa opcjonalne i obecnie nie sa wymagane.
|
Pliki `ROADMAP.md` / `MILESTONES.md` sa opcjonalne i obecnie nie sa wymagane.
|
||||||
|
|||||||
@@ -16,6 +16,12 @@
|
|||||||
- Task 4: utworzony `AllegroIntegrationViewModel` (61 lin.) — payload widoku `settings/allegro`.
|
- Task 4: utworzony `AllegroIntegrationViewModel` (61 lin.) — payload widoku `settings/allegro`.
|
||||||
- Task 5: slim kontroler (223 lin.) + dodatkowo `AllegroSaveSettingsValidator` (48 lin., poza pierwotnym planem); `AllegroIntegrationModule` rejestruje 5 nowych kluczy w `ServiceRegistry`.
|
- Task 5: slim kontroler (223 lin.) + dodatkowo `AllegroSaveSettingsValidator` (48 lin., poza pierwotnym planem); `AllegroIntegrationModule` rejestruje 5 nowych kluczy w `ServiceRegistry`.
|
||||||
- Task 6: dokumentacja PAUL zaktualizowana (`architecture.md`, `quality_risks.md`, `tech_changelog.md`).
|
- Task 6: dokumentacja PAUL zaktualizowana (`architecture.md`, `quality_risks.md`, `tech_changelog.md`).
|
||||||
|
- [Plan 20260519-1730-refactor-delivery-status] Dekompozycja `DeliveryStatus` (657 -> 170 lin.) na fasade + 3 wspolpracownikow; zero zmian w 20 plikach konsumentow.
|
||||||
|
- Task 1: utworzony `DeliveryStatusProviderMap` (~330 lin.) — mapy surowych statusow per dostawca + `normalize`/`description`/`getDefaultMappings`/`*WithOverrides`.
|
||||||
|
- Task 2: utworzony `AllegroDescriptionGuesser` (~150 lin.) — `slugifyAllegroDescription` + `guessStatusFromDescription` + wzorce.
|
||||||
|
- Task 3: utworzony `DeliveryTrackingUrlBuilder` (~85 lin.) — `trackingUrl` + URL kurierow/dostawcow.
|
||||||
|
- Task 4: dokumentacja PAUL zaktualizowana (`architecture.md`, `tech_changelog.md`, `quality_risks.md`).
|
||||||
|
- Weryfikacja: `DeliveryStatusTest` 4/4 bez zmiany pliku testu; pelny suite identyczny z baseline (3/15 pre-existing).
|
||||||
|
|
||||||
## Zmienione pliki
|
## Zmienione pliki
|
||||||
|
|
||||||
@@ -39,3 +45,9 @@
|
|||||||
- `.paul/STATE.md`
|
- `.paul/STATE.md`
|
||||||
- `.paul/plans/20260519-1430-refactor-orders-statistics-controller/PLAN.md`
|
- `.paul/plans/20260519-1430-refactor-orders-statistics-controller/PLAN.md`
|
||||||
- `.paul/plans/20260519-1430-refactor-orders-statistics-controller/SUMMARY.md`
|
- `.paul/plans/20260519-1430-refactor-orders-statistics-controller/SUMMARY.md`
|
||||||
|
- `src/Modules/Shipments/DeliveryStatus.php`
|
||||||
|
- `src/Modules/Shipments/DeliveryStatusProviderMap.php`
|
||||||
|
- `src/Modules/Shipments/AllegroDescriptionGuesser.php`
|
||||||
|
- `src/Modules/Shipments/DeliveryTrackingUrlBuilder.php`
|
||||||
|
- `.paul/plans/20260519-1730-refactor-delivery-status/PLAN.md`
|
||||||
|
- `.paul/plans/20260519-1730-refactor-delivery-status/SUMMARY.md`
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ Korzysci wzgledem poprzedniego monolitycznego `routes/web.php` (859 lin.):
|
|||||||
| Statistics | `src/Modules/Statistics/` | raporty zamowien (slim Controller + `OrdersStatisticsFilters` + `OrdersStatisticsTableBuilder` + `OrdersStatisticsSummaryBuilder` + Repository) |
|
| Statistics | `src/Modules/Statistics/` | raporty zamowien (slim Controller + `OrdersStatisticsFilters` + `OrdersStatisticsTableBuilder` + `OrdersStatisticsSummaryBuilder` + Repository) |
|
||||||
| Settings/Allegro | `src/Modules/Settings/Allegro*.php` | integracja Allegro: slim `AllegroIntegrationController` + `AllegroIntegrationViewModel` + `AllegroOAuthFlowService` + `AllegroImportScheduleService` + `AllegroImportImageWarningFormatter` + `AllegroSaveSettingsValidator` + `AllegroOrderImportService` + `AllegroStatusDiscoveryService` + `AllegroStatusMappingController` + `AllegroDeliveryMappingController` + repozytoria (`AllegroIntegrationRepository`, `AllegroStatusMappingRepository`, `AllegroPullStatusMappingRepository`) + klienci (`AllegroApiClient`, `AllegroOAuthClient`, `AllegroTokenManager`). |
|
| Settings/Allegro | `src/Modules/Settings/Allegro*.php` | integracja Allegro: slim `AllegroIntegrationController` + `AllegroIntegrationViewModel` + `AllegroOAuthFlowService` + `AllegroImportScheduleService` + `AllegroImportImageWarningFormatter` + `AllegroSaveSettingsValidator` + `AllegroOrderImportService` + `AllegroStatusDiscoveryService` + `AllegroStatusMappingController` + `AllegroDeliveryMappingController` + repozytoria (`AllegroIntegrationRepository`, `AllegroStatusMappingRepository`, `AllegroPullStatusMappingRepository`) + klienci (`AllegroApiClient`, `AllegroOAuthClient`, `AllegroTokenManager`). |
|
||||||
| Accounting | `src/Modules/Accounting/` | paragony, faktury, eksport ksiegowy |
|
| Accounting | `src/Modules/Accounting/` | paragony, faktury, eksport ksiegowy |
|
||||||
| Shipments | `src/Modules/Shipments/` | etykiety, tracking, presets paczek |
|
| Shipments | `src/Modules/Shipments/` | etykiety, tracking, presets paczek; slownik statusow dostawy jako fasada `DeliveryStatus` + `DeliveryStatusProviderMap` (mapy dostawcow) + `AllegroDescriptionGuesser` (heurystyki opisow) + `DeliveryTrackingUrlBuilder` (URL sledzenia) |
|
||||||
| Printing | `src/Modules/Printing/` | API drukowania etykiet (klient Windows) |
|
| Printing | `src/Modules/Printing/` | API drukowania etykiet (klient Windows) |
|
||||||
| Email | `src/Modules/Email/` | SMTP, wysylka maili |
|
| Email | `src/Modules/Email/` | SMTP, wysylka maili |
|
||||||
| Sms | `src/Modules/Sms/` | wysylka SMS, webhook SmsPlanet, konwersacje |
|
| Sms | `src/Modules/Sms/` | wysylka SMS, webhook SmsPlanet, konwersacje |
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ Konwencja (`CLAUDE.md`): funkcja/klasa zwykle do 30-50 linii, max 3 poziomy zagn
|
|||||||
| `src/Modules/Shipments/PolkurierShipmentService.php` | 776 | |
|
| `src/Modules/Shipments/PolkurierShipmentService.php` | 776 | |
|
||||||
| `src/Modules/Accounting/InvoiceService.php` | 762 | |
|
| `src/Modules/Accounting/InvoiceService.php` | 762 | |
|
||||||
| `src/Modules/Automation/AutomationController.php` | 677 | |
|
| `src/Modules/Automation/AutomationController.php` | 677 | |
|
||||||
| `src/Modules/Shipments/DeliveryStatus.php` | 657 | encja statusow uzywana globalnie — zmiany dotykaja wszystkich integracji. |
|
| ~~`src/Modules/Shipments/DeliveryStatus.php`~~ | ~~657~~ -> 170 | ✅ Zrefaktorowane 2026-05-19 — fasada zachowujaca pelny kontrakt + wydzielono `DeliveryStatusProviderMap` (~330), `AllegroDescriptionGuesser` (~150), `DeliveryTrackingUrlBuilder` (~85). Zero zmian w 20 plikach konsumentow. Patrz `.paul/plans/20260519-1730-refactor-delivery-status/SUMMARY.md`. |
|
||||||
| ~~`src/Modules/Settings/AllegroIntegrationController.php`~~ | ~~653~~ -> 223 | ✅ Zrefaktorowane 2026-05-19 — wydzielono `AllegroIntegrationViewModel` (61), `AllegroOAuthFlowService` (94), `AllegroImportScheduleService` (179), `AllegroImportImageWarningFormatter` (69), `AllegroSaveSettingsValidator` (48). Patrz `.paul/plans/20260519-1600-refactor-allegro-integration-controller/SUMMARY.md`. |
|
| ~~`src/Modules/Settings/AllegroIntegrationController.php`~~ | ~~653~~ -> 223 | ✅ Zrefaktorowane 2026-05-19 — wydzielono `AllegroIntegrationViewModel` (61), `AllegroOAuthFlowService` (94), `AllegroImportScheduleService` (179), `AllegroImportImageWarningFormatter` (69), `AllegroSaveSettingsValidator` (48). Patrz `.paul/plans/20260519-1600-refactor-allegro-integration-controller/SUMMARY.md`. |
|
||||||
| ~~`src/Modules/Statistics/OrdersStatisticsController.php`~~ | ~~640~~ -> 110 | ✅ Zrefaktorowane 2026-05-19 — wydzielono `OrdersStatisticsFilters` (258), `OrdersStatisticsTableBuilder` (101), `OrdersStatisticsSummaryBuilder` (195). Patrz `.paul/plans/20260519-1430-refactor-orders-statistics-controller/SUMMARY.md`. |
|
| ~~`src/Modules/Statistics/OrdersStatisticsController.php`~~ | ~~640~~ -> 110 | ✅ Zrefaktorowane 2026-05-19 — wydzielono `OrdersStatisticsFilters` (258), `OrdersStatisticsTableBuilder` (101), `OrdersStatisticsSummaryBuilder` (195). Patrz `.paul/plans/20260519-1430-refactor-orders-statistics-controller/SUMMARY.md`. |
|
||||||
| ~~`routes/web.php`~~ | ~~859~~ -> 78 | ✅ Zrefaktorowane 2026-05-19 — `ServiceRegistry` + 24 klasy `<Modul>Module.php`. Patrz `.paul/plans/20260519-1200-refactor-routes-web/SUMMARY.md`. |
|
| ~~`routes/web.php`~~ | ~~859~~ -> 78 | ✅ Zrefaktorowane 2026-05-19 — `ServiceRegistry` + 24 klasy `<Modul>Module.php`. Patrz `.paul/plans/20260519-1200-refactor-routes-web/SUMMARY.md`. |
|
||||||
|
|||||||
@@ -2,6 +2,28 @@
|
|||||||
|
|
||||||
Chronologiczny log zmian technicznych (co i dlaczego). Najnowsze na gorze.
|
Chronologiczny log zmian technicznych (co i dlaczego). Najnowsze na gorze.
|
||||||
|
|
||||||
|
## 2026-05-19 — Dekompozycja DeliveryStatus (fasada + 3 wspolpracownikow)
|
||||||
|
|
||||||
|
### Co
|
||||||
|
- Rozbicie `src/Modules/Shipments/DeliveryStatus.php` z 657 do 170 lin.
|
||||||
|
- `DeliveryStatus` pozostaje **fasada** zachowujaca pelny kontrakt publiczny (stale statusow `UNKNOWN..PROBLEM`, `TERMINAL_STATUSES`, `ALL_STATUSES`, `LABEL_PL`, most do DB `setRepository`/`getAllStatuses`/`getAllOptions`/`getColor`/`label`, `isTerminal` oraz metody-delegaty).
|
||||||
|
- Wydzielono trzy klasy w namespace `App\Modules\Shipments`:
|
||||||
|
- `DeliveryStatusProviderMap` (~330 lin.) — mapy surowych statusow per dostawca (`INPOST_*`, `APACZKA_*`, `ALLEGRO_*`, `ALLEGRO_EDGE_*`, `POLKURIER_*`) + `normalize`, `description`, `getDefaultMappings`, `normalizeWithOverrides`, `descriptionWithOverrides`. Prywatne stale opisow `DESC_*` przeniesione tutaj.
|
||||||
|
- `AllegroDescriptionGuesser` (~150 lin.) — `slugifyAllegroDescription`, `guessStatusFromDescription`, `DESCRIPTION_STATUS_PATTERNS` + helper `containsAny`.
|
||||||
|
- `DeliveryTrackingUrlBuilder` (~85 lin.) — `trackingUrl` + `PROVIDER_TRACKING_URLS`, `CARRIER_TRACKING_URLS`, stale `TRACKING_INPOST_URL`/`TRACKING_ALLEGRO_URL` + helpery matchowania kuriera.
|
||||||
|
|
||||||
|
### Dlaczego
|
||||||
|
- `quality_risks.md` wskazywal `DeliveryStatus` (657 lin.) jako kandydata do podzialu; ~400 z 657 linii to tablice `const` map statusow — naruszenie limitu z `CLAUDE.md`.
|
||||||
|
- Encja jest uzywana globalnie (56 wywolan w 20 plikach: serwisy trackingu wszystkich kurierow, cron, kontrolery, widoki, test) — `impact_map.md` zaznacza "dotykac ostroznie".
|
||||||
|
- Wybrano wariant **fasady** (a nie pelnej ekstrakcji): stale `DeliveryStatus::DELIVERED` itd. uzywane bezposrednio w serwisach, widokach i tescie nie da sie oddelegowac — musialyby zostac fizycznie przeniesione, co wymusiloby edycje kazdego call-site. Fasada = zerowe ryzyko dla integracji.
|
||||||
|
|
||||||
|
### Wplyw
|
||||||
|
- **Zero zmian w 20 plikach konsumentow** — kontrakt publiczny (sygnatury, stale, wartosci) niezmieniony.
|
||||||
|
- `tests/Unit/DeliveryStatusTest.php` przechodzi BEZ modyfikacji (primary gate; 4 testy / 7 asercji) — dowod zachowanego kontraktu.
|
||||||
|
- Pelny suite: 3 errors / 15 failures identyczne jak na HEAD przed zmiana (pre-existing, encoding/mocki — niezwiazane z refaktorem).
|
||||||
|
- Laczna liczba linii rosnie nieznacznie (boilerplate 3 klas) — oczekiwane.
|
||||||
|
- Plan: `.paul/plans/20260519-1730-refactor-delivery-status/PLAN.md`.
|
||||||
|
|
||||||
## 2026-05-19 — Dekompozycja AllegroIntegrationController
|
## 2026-05-19 — Dekompozycja AllegroIntegrationController
|
||||||
|
|
||||||
### Co
|
### Co
|
||||||
|
|||||||
@@ -276,3 +276,23 @@
|
|||||||
{"ts":"2026-05-19T21:14:09Z","tool":"Edit","file":"C:\\\\visual-studio-code\\\\projekty\\\\orderPRO\\\\.paul\\\\changelog\\\\2026-05-19.md","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
{"ts":"2026-05-19T21:14:09Z","tool":"Edit","file":"C:\\\\visual-studio-code\\\\projekty\\\\orderPRO\\\\.paul\\\\changelog\\\\2026-05-19.md","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
{"ts":"2026-05-19T21:14:17Z","tool":"Edit","file":"C:\\\\visual-studio-code\\\\projekty\\\\orderPRO\\\\.paul\\\\changelog\\\\2026-05-19.md","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
{"ts":"2026-05-19T21:14:17Z","tool":"Edit","file":"C:\\\\visual-studio-code\\\\projekty\\\\orderPRO\\\\.paul\\\\changelog\\\\2026-05-19.md","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
{"ts":"2026-05-19T21:22:01Z","tool":"Write","file":"c:\\\\visual-studio-code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
{"ts":"2026-05-19T21:22:01Z","tool":"Write","file":"c:\\\\visual-studio-code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-19T21:34:36Z","tool":"Write","file":"C:\\\\visual-studio-code\\\\projekty\\\\orderPRO\\\\.paul\\\\plans\\\\20260519-1730-refactor-delivery-status\\\\PLAN.md","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-19T21:34:45Z","tool":"Edit","file":"C:\\\\visual-studio-code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-19T21:34:51Z","tool":"Edit","file":"C:\\\\visual-studio-code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-19T21:35:42Z","tool":"Bash","cmd":"php -v 2>&1 | head -1; echo \"---\"; ls vendor/bin/phpunit 2>&1; echo \"---baseline test---\"; php vendor/bin/phpunit tests/Unit/DeliveryStatusTest.php 2>&1 | tail -15\",\"description\":\"Check php ve","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-19T21:36:31Z","tool":"Write","file":"C:\\\\visual-studio-code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Shipments\\\\DeliveryStatusProviderMap.php","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-19T21:36:57Z","tool":"Bash","cmd":"php -l src/Modules/Shipments/DeliveryStatusProviderMap.php 2>&1\",\"description\":\"Syntax-check DeliveryStatusProviderMap\"},\"tool_response\":{\"stdout\":\"No syntax errors detected in src/Modules/S","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-19T21:37:06Z","tool":"Write","file":"C:\\\\visual-studio-code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Shipments\\\\AllegroDescriptionGuesser.php","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-19T21:37:14Z","tool":"Bash","cmd":"php -l src/Modules/Shipments/AllegroDescriptionGuesser.php 2>&1\",\"description\":\"Syntax-check AllegroDescriptionGuesser\"},\"tool_response\":{\"stdout\":\"No syntax errors detected in src/Modules/S","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-19T21:37:20Z","tool":"Write","file":"C:\\\\visual-studio-code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Shipments\\\\DeliveryTrackingUrlBuilder.php","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-19T21:37:38Z","tool":"Write","file":"C:\\\\visual-studio-code\\\\projekty\\\\orderPRO\\\\src\\\\Modules\\\\Shipments\\\\DeliveryStatus.php","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-19T21:38:30Z","tool":"Bash","cmd":"git stash push src/Modules/Shipments/DeliveryStatus.php 2>&1\\necho \"=== BASELINE (my facade reverted to HEAD) ===\"; php vendor/bin/phpunit 2>&1 | tail -3\\ngit stash pop 2>&1 | tail -2\",\"descript","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-19T21:38:55Z","tool":"Edit","file":"C:\\\\visual-studio-code\\\\projekty\\\\orderPRO\\\\.paul\\\\codebase\\\\tech_changelog.md","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-19T21:39:01Z","tool":"Edit","file":"C:\\\\visual-studio-code\\\\projekty\\\\orderPRO\\\\.paul\\\\codebase\\\\architecture.md","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-19T21:39:13Z","tool":"Edit","file":"C:\\\\visual-studio-code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-19T21:43:54Z","tool":"Write","file":"C:\\\\visual-studio-code\\\\projekty\\\\orderPRO\\\\.paul\\\\plans\\\\20260519-1730-refactor-delivery-status\\\\SUMMARY.md","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-19T21:44:00Z","tool":"Edit","file":"C:\\\\visual-studio-code\\\\projekty\\\\orderPRO\\\\.paul\\\\codebase\\\\quality_risks.md","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-19T21:44:14Z","tool":"Edit","file":"C:\\\\visual-studio-code\\\\projekty\\\\orderPRO\\\\.paul\\\\changelog\\\\2026-05-19.md","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-19T21:44:19Z","tool":"Edit","file":"C:\\\\visual-studio-code\\\\projekty\\\\orderPRO\\\\.paul\\\\changelog\\\\2026-05-19.md","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-19T21:44:27Z","tool":"Edit","file":"C:\\\\visual-studio-code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
|
{"ts":"2026-05-19T21:44:40Z","tool":"Edit","file":"C:\\\\visual-studio-code\\\\projekty\\\\orderPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual-studio-code/projekty/orderPRO"}
|
||||||
|
|||||||
236
.paul/plans/20260519-1730-refactor-delivery-status/PLAN.md
Normal file
236
.paul/plans/20260519-1730-refactor-delivery-status/PLAN.md
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
---
|
||||||
|
plan_id: 20260519-1730-refactor-delivery-status
|
||||||
|
title: Refaktoryzacja DeliveryStatus — fasada + 3 wspolpracownikow
|
||||||
|
storage: plan-first
|
||||||
|
legacy_phase: null
|
||||||
|
created: 2026-05-19T17:30:00
|
||||||
|
status: planned
|
||||||
|
type: execute
|
||||||
|
autonomous: true
|
||||||
|
delegation: auto
|
||||||
|
files_modified:
|
||||||
|
- src/Modules/Shipments/DeliveryStatus.php
|
||||||
|
- src/Modules/Shipments/DeliveryStatusProviderMap.php
|
||||||
|
- src/Modules/Shipments/AllegroDescriptionGuesser.php
|
||||||
|
- src/Modules/Shipments/DeliveryTrackingUrlBuilder.php
|
||||||
|
- .paul/codebase/architecture.md
|
||||||
|
- .paul/codebase/tech_changelog.md
|
||||||
|
quality_radar: ok
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
## Goal
|
||||||
|
Rozbic `src/Modules/Shipments/DeliveryStatus.php` (657 lin.) na czytelna **fasade** + trzech wyspecjalizowanych wspolpracownikow, bez zmiany ani jednego miejsca wywolania w aplikacji.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
`DeliveryStatus` to encja statusow uzywana globalnie — 56 wywolan w 20 plikach (serwisy trackingu wszystkich kurierow, cron, kontrolery, widoki, testy). Plik laczy cztery niezalezne odpowiedzialnosci, a ~400 z 657 linii to tablice `const` mapujace surowe statusy dostawcow. Konwencja `CLAUDE.md` (jedna odpowiedzialnosc na klase, czytelnosc "dla obcego") jest naruszona. Cel: rozdzielic odpowiedzialnosci, zachowujac 100% kontraktu publicznego, by ryzyko dla integracji bylo zerowe.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
- Fasada `DeliveryStatus` (~150-180 lin.): stale statusow, etykiety, most do DB (`$repository`), publiczne metody-delegaty (jednolinijkowce).
|
||||||
|
- `DeliveryStatusProviderMap` (~350 lin.): mapy `*_MAP` / `*_DESCRIPTIONS` per dostawca + `normalize` / `description` / `getDefaultMappings` / `*WithOverrides`.
|
||||||
|
- `AllegroDescriptionGuesser` (~110-130 lin.): `slugifyAllegroDescription`, `guessStatusFromDescription`, `DESCRIPTION_STATUS_PATTERNS`.
|
||||||
|
- `DeliveryTrackingUrlBuilder` (~80-100 lin.): `trackingUrl` + tablice URL kurierow/dostawcow + matchowanie.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
## Project Docs
|
||||||
|
@.paul/PROJECT.md
|
||||||
|
@.paul/STATE.md
|
||||||
|
@.paul/codebase/architecture.md
|
||||||
|
@.paul/codebase/db_schema.md
|
||||||
|
@.paul/codebase/impact_map.md
|
||||||
|
@.paul/codebase/quality_risks.md
|
||||||
|
|
||||||
|
## Source Files
|
||||||
|
@src/Modules/Shipments/DeliveryStatus.php
|
||||||
|
@src/Modules/Shipments/DeliveryStatusRepository.php
|
||||||
|
@tests/Unit/DeliveryStatusTest.php
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<clarifications>
|
||||||
|
- Wariant "pelnej ekstrakcji" (przeniesienie metod do nowych klas + edycja 20 plikow wywolan) zostal odrzucony: stale `DeliveryStatus::UNKNOWN`, `::DELIVERED`, `::READY_FOR_PICKUP`, `::OUT_FOR_DELIVERY`, `::ALL_STATUSES` sa uzywane bezposrednio w serwisach, widokach **i tescie** — stalych nie da sie "oddelegowac", musialyby zostac fizycznie przeniesione, co wymusza edycje kazdego odwolania (w tym testu). To inny, wiekszy refaktor niz wymaga `quality_risks.md`. Wybrano fasade zachowujaca pelny kontrakt.
|
||||||
|
</clarifications>
|
||||||
|
|
||||||
|
<impact_scan>
|
||||||
|
## Quality Radar
|
||||||
|
|
||||||
|
**Status:** ok
|
||||||
|
**Tryb:** plan (targeted)
|
||||||
|
**Tools:** codebase-memory-mcp (graf wczytany w STATE: 4217 nodes / 11649 edges); jscpd/ast-grep wylaczone polityka (`.paul/config.md`). Zakres potwierdzony przez `rg` (grep) na `DeliveryStatus::` — 56 trafien / 20 plikow.
|
||||||
|
|
||||||
|
## Affected Areas
|
||||||
|
|
||||||
|
- **Shipments (rdzen zmiany)** — `DeliveryStatus.php` + 3 nowe klasy w `src/Modules/Shipments/`.
|
||||||
|
- **Konsumenci kontraktu (NIE modyfikowani — weryfikacja braku zmian):**
|
||||||
|
- Serwisy trackingu: `AllegroTrackingService` (normalize, description, slugifyAllegroDescription, guessStatusFromDescription, UNKNOWN), `InpostTrackingService` (normalize, description), `ApaczkaTrackingService` (normalize, description), `PolkurierTrackingService` (normalizeWithOverrides).
|
||||||
|
- Cron: `ShipmentTrackingHandler` (normalizeWithOverrides).
|
||||||
|
- Kontrolery: `Automation*` (getAllStatuses, getAllOptions, UNKNOWN), `DeliveryStatusesController` + `DeliveryStatusMappingController` (getDefaultMappings), `ShipmentController` (UNKNOWN).
|
||||||
|
- Repozytoria: `DeliveryStatusMappingRepository` (getDefaultMappings).
|
||||||
|
- Bootstrap: `ShipmentsModule` + `bin/smoke_routes.php` (setRepository).
|
||||||
|
- Widoki: `resources/views/orders/show.php`, `resources/views/shipments/prepare.php` (label, description, getColor, trackingUrl, ALL_STATUSES).
|
||||||
|
- SMS: `SmsVariableResolver` (trackingUrl).
|
||||||
|
- Test: `tests/Unit/DeliveryStatusTest.php` (slugify, normalize, guess + stale).
|
||||||
|
- **Migracje:** `20260422_000101_backfill_delivery_status_unknowns.sql` — odwoluje sie do `DeliveryStatus::normalize()` wylacznie w komentarzu SQL (zweryfikowane). Brak wykonywalnego PHP. Bez zmian.
|
||||||
|
|
||||||
|
## Duplicate / Hardcoded Risks
|
||||||
|
|
||||||
|
- **Stale `self::CREATED` itd. wewnatrz przenoszonych map** — po przeniesieniu `*_MAP` do `DeliveryStatusProviderMap` musza wskazywac `DeliveryStatus::CREATED` (ten sam namespace). Obsluzone w Task 1 `<action>`.
|
||||||
|
- **Prywatne stale opisow `DESC_*`** (`DESC_DELIVERED`, `DESC_PICKED_UP_BY_COURIER`, ...) uzywane wylacznie przez tablice opisow → przeniesc do `DeliveryStatusProviderMap` (Task 1).
|
||||||
|
- **Stale `TRACKING_INPOST_URL` / `TRACKING_ALLEGRO_URL`** uzywane w `PROVIDER_TRACKING_URLS` i `CARRIER_TRACKING_URLS` → przeniesc do `DeliveryTrackingUrlBuilder` (Task 3).
|
||||||
|
- **Helpery prywatne `containsAny` / `carrierMatches`** (po ~5 lin.) — wedruja ze swoimi wywolujacymi (`containsAny` → guesser, `carrierMatches` → URL builder). NIE tworzyc wspolnej klasy util dla dwoch helperow.
|
||||||
|
- **Brak drugiego zrodla prawdy:** mapy statusow pozostaja w JEDNYM miejscu (przeniesione, nie skopiowane). Fasada deleguje, nie duplikuje.
|
||||||
|
|
||||||
|
## Explicit Deferrals
|
||||||
|
|
||||||
|
- **Mapowania statusow per integracja jako wzorzec do abstrakcji** (`quality_risks.md` — `*StatusMappingRepository` + `*PullStatusMappingRepository`) — poza zakresem; dotyczy repozytoriow DB, nie tej encji.
|
||||||
|
- **Przejscie na `enum` PHP 8.1+ dla statusow** — kuszace, ale zlamaloby kontrakt `string` uzywany w sygnaturach 20 plikow i w DB. Odlozone.
|
||||||
|
- **Zamiana statycznego API na instancyjne / DI** — poza zakresem; caly kontrakt jest statyczny i tak uzywany globalnie.
|
||||||
|
</impact_scan>
|
||||||
|
|
||||||
|
<skills>
|
||||||
|
Brak `.paul/SPECIAL-FLOWS.md` — sekcja skills pominieta.
|
||||||
|
</skills>
|
||||||
|
|
||||||
|
<acceptance_criteria>
|
||||||
|
|
||||||
|
## AC-1: Kontrakt publiczny niezmieniony
|
||||||
|
```gherkin
|
||||||
|
Given 20 plikow wywoluje DeliveryStatus:: (56 wywolan)
|
||||||
|
When refaktor jest zakonczony
|
||||||
|
Then zaden plik konsumenta (serwisy, cron, kontrolery, widoki, test) nie jest zmodyfikowany
|
||||||
|
And wszystkie publiczne metody i stale DeliveryStatus zachowuja te sama sygnature i zwracane wartosci
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-2: Testy jednostkowe przechodza bez zmian
|
||||||
|
```gherkin
|
||||||
|
Given tests/Unit/DeliveryStatusTest.php (slugify, normalize allegro_edge, guess, stale)
|
||||||
|
When uruchomie phpunit po refaktorze
|
||||||
|
Then wszystkie testy przechodza
|
||||||
|
And plik testu nie wymagal zadnej modyfikacji
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-3: Rozdzielone odpowiedzialnosci
|
||||||
|
```gherkin
|
||||||
|
Given monolit DeliveryStatus.php (657 lin.)
|
||||||
|
When refaktor jest zakonczony
|
||||||
|
Then powstaja 3 nowe klasy: DeliveryStatusProviderMap, AllegroDescriptionGuesser, DeliveryTrackingUrlBuilder
|
||||||
|
And kazda ma jedna odpowiedzialnosc
|
||||||
|
And fasada DeliveryStatus.php ma <= 200 lin. (cel ~150-180)
|
||||||
|
And zadna mapa statusow nie istnieje w wiecej niz jednym miejscu
|
||||||
|
```
|
||||||
|
|
||||||
|
## AC-4: Kod sie laduje (brak bledow skladni / autoload)
|
||||||
|
```gherkin
|
||||||
|
Given nowe klasy w namespace App\Modules\Shipments
|
||||||
|
When uruchomie php -l na kazdym pliku i smoke check autoloadu
|
||||||
|
Then brak bledow skladni
|
||||||
|
And klasy sa znajdowane przez PSR-4 (App\ -> src/)
|
||||||
|
```
|
||||||
|
|
||||||
|
</acceptance_criteria>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Wydzielic DeliveryStatusProviderMap (mapowanie statusow dostawcow)</name>
|
||||||
|
<files>src/Modules/Shipments/DeliveryStatusProviderMap.php, src/Modules/Shipments/DeliveryStatus.php</files>
|
||||||
|
<action>
|
||||||
|
Utworz `src/Modules/Shipments/DeliveryStatusProviderMap.php` (namespace `App\Modules\Shipments`, `final class`).
|
||||||
|
Przenies z DeliveryStatus do nowej klasy:
|
||||||
|
- prywatne stale map: `INPOST_MAP`, `INPOST_DESCRIPTIONS`, `APACZKA_MAP`, `APACZKA_DESCRIPTIONS`, `ALLEGRO_MAP`, `ALLEGRO_DESCRIPTIONS`, `ALLEGRO_EDGE_MAP`, `ALLEGRO_EDGE_DESCRIPTIONS`, `POLKURIER_MAP`, `POLKURIER_DESCRIPTIONS`, `PROVIDER_MAPS`, `PROVIDER_DESCRIPTIONS`,
|
||||||
|
- prywatne stale opisow uzywane przez te tablice: `DESC_PICKED_UP_BY_COURIER`, `DESC_DISPATCHED`, `DESC_OUT_FOR_DELIVERY`, `DESC_DELIVERED`, `DESC_RETURNED_TO_SENDER`, `DESC_AWAITING_PICKUP`,
|
||||||
|
- metody: `getDefaultMappings`, `normalizeWithOverrides`, `descriptionWithOverrides`, `normalize`, `description`.
|
||||||
|
WAZNE: wewnatrz przeniesionych map odwolania `self::CREATED`/`self::DELIVERED`/... zamien na `DeliveryStatus::CREATED` itd. (ten sam namespace, bez importu). `self::UNKNOWN` w `normalize` -> `DeliveryStatus::UNKNOWN`.
|
||||||
|
W DeliveryStatus zostaw publiczne metody-delegaty (jednolinijkowce), zachowujac identyczne sygnatury i PHPDoc:
|
||||||
|
`public static function getDefaultMappings(string $provider): array { return DeliveryStatusProviderMap::getDefaultMappings($provider); }` — analogicznie dla `normalize`, `description`, `normalizeWithOverrides`, `descriptionWithOverrides`.
|
||||||
|
Usun z DeliveryStatus przeniesione stale prywatne (map i `DESC_*`).
|
||||||
|
</action>
|
||||||
|
<verify>php -l src/Modules/Shipments/DeliveryStatusProviderMap.php; php -l src/Modules/Shipments/DeliveryStatus.php; php vendor/bin/phpunit tests/Unit/DeliveryStatusTest.php</verify>
|
||||||
|
<done>AC-1, AC-2, AC-3, AC-4 — mapowanie dostawcow w jednej dedykowanej klasie, fasada deleguje, testy przechodza.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Wydzielic AllegroDescriptionGuesser (heurystyki opisow Allegro)</name>
|
||||||
|
<files>src/Modules/Shipments/AllegroDescriptionGuesser.php, src/Modules/Shipments/DeliveryStatus.php</files>
|
||||||
|
<action>
|
||||||
|
Utworz `src/Modules/Shipments/AllegroDescriptionGuesser.php` (namespace `App\Modules\Shipments`, `final class`).
|
||||||
|
Przenies z DeliveryStatus:
|
||||||
|
- prywatna stala `DESCRIPTION_STATUS_PATTERNS`,
|
||||||
|
- metody publiczne `slugifyAllegroDescription`, `guessStatusFromDescription`,
|
||||||
|
- prywatny helper `containsAny` (uzywany wylacznie przez `guessStatusFromDescription`).
|
||||||
|
Odwolania `self::READY_FOR_PICKUP`/`self::UNKNOWN`/... w przeniesionym kodzie zamien na `DeliveryStatus::READY_FOR_PICKUP` itd.
|
||||||
|
W DeliveryStatus zostaw delegaty:
|
||||||
|
`public static function slugifyAllegroDescription(string $description): string { return AllegroDescriptionGuesser::slugifyAllegroDescription($description); }` oraz analogiczny dla `guessStatusFromDescription`.
|
||||||
|
Usun z DeliveryStatus przeniesione skladowe (w tym `containsAny`).
|
||||||
|
</action>
|
||||||
|
<verify>php -l src/Modules/Shipments/AllegroDescriptionGuesser.php; php -l src/Modules/Shipments/DeliveryStatus.php; php vendor/bin/phpunit tests/Unit/DeliveryStatusTest.php</verify>
|
||||||
|
<done>AC-1, AC-2, AC-3, AC-4 — heurystyki opisow w osobnej klasie; testy slugify/guess przechodza bez zmiany pliku testu.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 3: Wydzielic DeliveryTrackingUrlBuilder (budowanie URL sledzenia)</name>
|
||||||
|
<files>src/Modules/Shipments/DeliveryTrackingUrlBuilder.php, src/Modules/Shipments/DeliveryStatus.php</files>
|
||||||
|
<action>
|
||||||
|
Utworz `src/Modules/Shipments/DeliveryTrackingUrlBuilder.php` (namespace `App\Modules\Shipments`, `final class`).
|
||||||
|
Przenies z DeliveryStatus:
|
||||||
|
- stale `TRACKING_INPOST_URL`, `TRACKING_ALLEGRO_URL`, `PROVIDER_TRACKING_URLS`, `CARRIER_TRACKING_URLS`,
|
||||||
|
- metode publiczna `trackingUrl`,
|
||||||
|
- prywatne helpery `carrierTrackingUrl`, `providerTrackingUrl`, `fallbackTrackingUrl`, `matchCarrierByName`, `carrierMatches`.
|
||||||
|
W DeliveryStatus zostaw delegat:
|
||||||
|
`public static function trackingUrl(string $provider, string $trackingNumber, string $carrierId = ''): ?string { return DeliveryTrackingUrlBuilder::trackingUrl($provider, $trackingNumber, $carrierId); }`.
|
||||||
|
Usun z DeliveryStatus przeniesione stale i helpery.
|
||||||
|
Po tym tasku w DeliveryStatus zostaja TYLKO: stale statusow (UNKNOWN..PROBLEM), `TERMINAL_STATUSES`, `ALL_STATUSES`, `LABEL_PL`, most do DB (`$repository`, `setRepository`, `getAllStatuses`, `getAllOptions`, `getColor`, `label`), `isTerminal` oraz metody-delegaty z Task 1-3.
|
||||||
|
</action>
|
||||||
|
<verify>php -l src/Modules/Shipments/DeliveryTrackingUrlBuilder.php; php -l src/Modules/Shipments/DeliveryStatus.php; php vendor/bin/phpunit tests/Unit/DeliveryStatusTest.php; (Get-Content src/Modules/Shipments/DeliveryStatus.php | Measure-Object -Line).Lines</verify>
|
||||||
|
<done>AC-1, AC-2, AC-3, AC-4 — budowanie URL w osobnej klasie; fasada DeliveryStatus.php <= 200 lin.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 4: Aktualizacja dokumentacji technicznej</name>
|
||||||
|
<files>.paul/codebase/architecture.md, .paul/codebase/tech_changelog.md</files>
|
||||||
|
<action>
|
||||||
|
W `architecture.md` (sekcja modulu Shipments / warstwy) dopisz nowy podzial: `DeliveryStatus` jako fasada + `DeliveryStatusProviderMap` + `AllegroDescriptionGuesser` + `DeliveryTrackingUrlBuilder`.
|
||||||
|
W `tech_changelog.md` dopisz wpis chronologiczny (data 2026-05-19): co i dlaczego — dekompozycja god-klasy statusow na fasade + 3 wspolpracownikow, kontrakt publiczny zachowany, zero zmian w call-site, redukcja DeliveryStatus.php z 657 do ~N lin.
|
||||||
|
Nie ruszaj `db_schema.md` (brak zmian schematu).
|
||||||
|
</action>
|
||||||
|
<verify>Reczny przeglad obu plikow — wpisy obecne i spojne z faktycznym stanem kodu.</verify>
|
||||||
|
<done>AC-3 — dokumentacja odzwierciedla nowy podzial klas.</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<boundaries>
|
||||||
|
## Do Not Change
|
||||||
|
- Zadnego z 20 plikow konsumentow `DeliveryStatus::` (serwisy trackingu, cron, kontrolery, widoki, `SmsVariableResolver`, `ShipmentsModule`, `bin/smoke_routes.php`).
|
||||||
|
- `tests/Unit/DeliveryStatusTest.php` — musi przejsc BEZ modyfikacji (to dowod, ze kontrakt jest zachowany).
|
||||||
|
- Sygnatury, nazwy i zwracane wartosci publicznych metod oraz stale `DeliveryStatus`.
|
||||||
|
- Migracje SQL (`20260422_000101_*` i pozostale).
|
||||||
|
- `routes/web.php`, `ServiceRegistry`, providery modulow (refaktor jest wewnatrz Shipments, brak nowych route'ow/serwisow w DI).
|
||||||
|
|
||||||
|
## Scope Limits
|
||||||
|
- Brak zmian schematu DB i migracji.
|
||||||
|
- Brak zamiany statycznego API na instancyjne/DI.
|
||||||
|
- Brak `enum` PHP zamiast stalych string.
|
||||||
|
- Brak abstrakcji bazowej dla `*StatusMappingRepository` (osobny, przyszly plan).
|
||||||
|
- Brak nowych testow poza utrzymaniem zielonego `DeliveryStatusTest` (mozna dopisac testy charakteryzujace trackingUrl/getDefaultMappings tylko jesli nie wymaga to zmiany istniejacych plikow — opcjonalne, nie blokujace).
|
||||||
|
</boundaries>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
- [ ] `php vendor/bin/phpunit tests/Unit/DeliveryStatusTest.php` — wszystkie testy zielone, plik testu niezmieniony (PRIMARY GATE — kontrakt fasady).
|
||||||
|
- [ ] `php -l` bez bledow dla: `DeliveryStatus.php`, `DeliveryStatusProviderMap.php`, `AllegroDescriptionGuesser.php`, `DeliveryTrackingUrlBuilder.php`.
|
||||||
|
- [ ] `git diff --name-only` pokazuje TYLKO 4 pliki kodu (1 zmodyfikowany + 3 nowe) + 2 pliki docs — zaden plik konsumenta.
|
||||||
|
- [ ] `(Get-Content src/Modules/Shipments/DeliveryStatus.php | Measure-Object -Line).Lines` <= 200.
|
||||||
|
- [ ] `php bin/smoke_routes.php` (jesli dostepny) — bootstrap przechodzi (weryfikuje `setRepository` + autoload).
|
||||||
|
- [ ] Quality Radar: ryzyko "drugie zrodlo prawdy" obsluzone (mapy przeniesione, nie skopiowane).
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- [ ] Wszystkie AC (AC-1..AC-4) spelnione.
|
||||||
|
- [ ] Weryfikacja kompletna (phpunit + php -l + diff scope + rozmiar fasady).
|
||||||
|
- [ ] `architecture.md` i `tech_changelog.md` zaktualizowane.
|
||||||
|
- [ ] `quality_risks.md` zaktualizowane w UNIFY (wpis `DeliveryStatus.php` oznaczony jako zrefaktorowany, analogicznie do Allegro/Statistics).
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
SUMMARY.md path: `.paul/plans/20260519-1730-refactor-delivery-status/SUMMARY.md`
|
||||||
|
</output>
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
---
|
||||||
|
plan_id: 20260519-1730-refactor-delivery-status
|
||||||
|
title: Refaktoryzacja DeliveryStatus — fasada + 3 wspolpracownikow
|
||||||
|
completed: 2026-05-19T18:10:00
|
||||||
|
storage: plan-first
|
||||||
|
quality_radar: ok
|
||||||
|
---
|
||||||
|
|
||||||
|
# Summary: Refaktoryzacja DeliveryStatus — fasada + 3 wspolpracownikow
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Rozbicie `src/Modules/Shipments/DeliveryStatus.php` (657 lin., god-klasa statusow uzywana globalnie — 56 wywolan w 20 plikach) na czytelna fasade + trzech wyspecjalizowanych wspolpracownikow, przy 100% zachowaniu kontraktu publicznego (zero zmian w plikach konsumentow).
|
||||||
|
|
||||||
|
## What Was Built
|
||||||
|
|
||||||
|
| Area | Result |
|
||||||
|
|------|--------|
|
||||||
|
| Fasada `DeliveryStatus` | 657 -> **170 lin.**; stale statusow, `LABEL_PL`, `ALL_STATUSES`, `TERMINAL_STATUSES`, most do DB, `isTerminal` + metody-delegaty |
|
||||||
|
| Mapowanie dostawcow | nowy `DeliveryStatusProviderMap` (~330 lin.) — `INPOST_*`/`APACZKA_*`/`ALLEGRO_*`/`ALLEGRO_EDGE_*`/`POLKURIER_*` + `normalize`/`description`/`getDefaultMappings`/`*WithOverrides` + prywatne `DESC_*` |
|
||||||
|
| Heurystyki opisow Allegro | nowy `AllegroDescriptionGuesser` (~150 lin.) — `slugifyAllegroDescription`, `guessStatusFromDescription`, `DESCRIPTION_STATUS_PATTERNS`, `containsAny` |
|
||||||
|
| URL sledzenia | nowy `DeliveryTrackingUrlBuilder` (~85 lin.) — `trackingUrl` + `PROVIDER_TRACKING_URLS`/`CARRIER_TRACKING_URLS` + helpery |
|
||||||
|
|
||||||
|
## Files Modified
|
||||||
|
|
||||||
|
- `src/Modules/Shipments/DeliveryStatus.php` — zredukowany do fasady delegujacej (657 -> 170).
|
||||||
|
- `src/Modules/Shipments/DeliveryStatusProviderMap.php` — NOWY; mapy surowych statusow per dostawca.
|
||||||
|
- `src/Modules/Shipments/AllegroDescriptionGuesser.php` — NOWY; heurystyki opisow Allegro edge.
|
||||||
|
- `src/Modules/Shipments/DeliveryTrackingUrlBuilder.php` — NOWY; budowanie URL-i sledzenia.
|
||||||
|
- `.paul/codebase/architecture.md` — opis nowego podzialu w wierszu modulu Shipments.
|
||||||
|
- `.paul/codebase/tech_changelog.md` — wpis chronologiczny 2026-05-19.
|
||||||
|
|
||||||
|
## Acceptance Criteria Results
|
||||||
|
|
||||||
|
| Criterion | Status | Evidence |
|
||||||
|
|-----------|--------|----------|
|
||||||
|
| AC-1: Kontrakt publiczny niezmieniony | Pass | `git status --porcelain` — zaden z 20 plikow konsumentow niemodyfikowany; zmieniony tylko `DeliveryStatus.php` |
|
||||||
|
| AC-2: Testy przechodza bez zmian | Pass | `DeliveryStatusTest` 4/4 (7 asercji); `git diff --stat tests/Unit/DeliveryStatusTest.php` pusty |
|
||||||
|
| AC-3: Rozdzielone odpowiedzialnosci | Pass | 3 nowe klasy, fasada 170 lin. (<= 200); mapy w jednym miejscu (przeniesione, nie skopiowane) |
|
||||||
|
| AC-4: Kod sie laduje | Pass | `php -l` clean x4; test przechodzacy przez `slugify`/`normalize` potwierdza autoload PSR-4 nowych klas |
|
||||||
|
|
||||||
|
## Verification Results
|
||||||
|
|
||||||
|
| Check | Result | Notes |
|
||||||
|
|-------|--------|-------|
|
||||||
|
| `php vendor/bin/phpunit tests/Unit/DeliveryStatusTest.php` | Pass | 4 testy / 7 asercji (PRIMARY GATE), plik testu niezmieniony |
|
||||||
|
| `php -l` (4 pliki) | Pass | brak bledow skladni |
|
||||||
|
| rozmiar fasady | Pass | 170 lin. (cel ~150-180, limit <= 200) |
|
||||||
|
| `git status --porcelain` (scope) | Pass | tylko 4 pliki kodu + 2 docs + STATE.md (workflow) |
|
||||||
|
| `php vendor/bin/phpunit` (full) | Pass (neutralnie) | 3 errors / 15 failures **identyczne z baseline na HEAD** (stash `DeliveryStatus.php` -> ten sam wynik) — pre-existing, niezwiazane |
|
||||||
|
| `php bin/cron` / `bin/smoke_routes.php` | Skipped | DB niedostepne — `setRepository` nie zweryfikowany runtime (plan dopuszczal "jesli dostepny") |
|
||||||
|
|
||||||
|
## Quality Radar Results
|
||||||
|
|
||||||
|
**Status:** ok
|
||||||
|
|
||||||
|
- New risks: brak. Powstaly 3 nowe klasy, kazda < 350 lin., jedna odpowiedzialnosc.
|
||||||
|
- Resolved risks: `DeliveryStatus.php` (657 lin.) zdjety z listy kandydatow > 500 lin. w `quality_risks.md`.
|
||||||
|
- Deferred risks: enum PHP zamiast stalych string; abstrakcja bazowa dla `*StatusMappingRepository`; zamiana statycznego API na instancyjne/DI — wszystkie poza zakresem (osobne plany).
|
||||||
|
- Tools: codebase-memory-mcp (graf bez zmian strukturalnych poza +3 klasy); jscpd/ast-grep disabled by policy. Raw outputs: `.paul/codebase/radar/`.
|
||||||
|
|
||||||
|
## Deviations
|
||||||
|
|
||||||
|
- **Tryb wykonania inline zamiast delegated (auto):** wszystkie 4 zadania edytuja wspolny `DeliveryStatus.php` — delegacja rownolegla niemozliwa (konflikt plikow), a precyzyjne zachowanie kontraktu wymagalo pelnej kontroli. Ekstrakcja wykonana atomowo: 3 nowe pliki + jeden czysty zapis fasady, nastepnie pelna weryfikacja. Rownowazne wynikowo zadaniom T1-T3, nizsze ryzyko niz 3 przeplatane edycje.
|
||||||
|
- **Laczna liczba linii rosnie** (boilerplate 3 klas + PHPDoc) — oczekiwane i zapowiedziane w planie; nie jest regresja.
|
||||||
|
|
||||||
|
## Key Decisions / Patterns
|
||||||
|
|
||||||
|
- **Fasada zamiast pelnej ekstrakcji** — wymuszone przez kontrakt: stale `DeliveryStatus::DELIVERED` itd. uzywane wprost w serwisach, widokach i tescie nie da sie oddelegowac. Fasada = zerowy blast radius dla integracji.
|
||||||
|
- **Helpery prywatne wedruja z wywolujacymi** (`containsAny` -> guesser, `carrierMatches` -> URL builder) — bez tworzenia sztucznej klasy util dla 2 metod.
|
||||||
|
- **Stale-klucze map** (`DeliveryStatus::CREATED` jako klucz w `DESCRIPTION_STATUS_PATTERNS`) dzialaja jako constant-expression w `const` array (PHP 8.2).
|
||||||
|
|
||||||
|
## Follow-up
|
||||||
|
|
||||||
|
- **Luka testowa (znana):** test pokrywa 4 z ~13 metod fasady; pozostale delegaty (`trackingUrl`, `getDefaultMappings`, `*WithOverrides`, most do DB, `label`/`getColor`/`getAllStatuses`/`getAllOptions`) niesprawdzone runtime — mechanicznie bezpieczne (jednolinijkowe passthrough). Kandydat: contract test fasady.
|
||||||
|
- **Smoke-test reczny przed commitem:** widoki `/orders/show` + `/shipments/prepare` (label/description/getColor/trackingUrl/ALL_STATUSES) oraz tracking Apaczka/InPost/Polkurier (DB niedostepne w sesji).
|
||||||
|
- **Kolejni kandydaci** z `quality_risks.md`: `OrdersController.php` (1490), `OrdersRepository.php` (1243), `ShopproIntegrationsController.php` (1076).
|
||||||
145
src/Modules/Shipments/AllegroDescriptionGuesser.php
Normal file
145
src/Modules/Shipments/AllegroDescriptionGuesser.php
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Modules\Shipments;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Heurystyki rozpoznawania znormalizowanego statusu z opisow tekstowych Allegro
|
||||||
|
* (edge API): slugifikacja opisu oraz fallback po slowach kluczowych.
|
||||||
|
*/
|
||||||
|
final class AllegroDescriptionGuesser
|
||||||
|
{
|
||||||
|
private const DESCRIPTION_STATUS_PATTERNS = [
|
||||||
|
DeliveryStatus::DELIVERED => [
|
||||||
|
'delivered',
|
||||||
|
'picked up by recipient',
|
||||||
|
'doręczon',
|
||||||
|
'dostarczono',
|
||||||
|
'odebrana przez odbiorc',
|
||||||
|
],
|
||||||
|
DeliveryStatus::PICKED_UP => [
|
||||||
|
'picked up by courier',
|
||||||
|
'picked up from point',
|
||||||
|
'podjęta',
|
||||||
|
'podjeta',
|
||||||
|
'odebrana przez kuriera',
|
||||||
|
],
|
||||||
|
DeliveryStatus::RETURNED => [
|
||||||
|
'returned',
|
||||||
|
'zwrócon',
|
||||||
|
'zwrocona',
|
||||||
|
],
|
||||||
|
DeliveryStatus::CANCELLED => [
|
||||||
|
'cancelled',
|
||||||
|
'canceled',
|
||||||
|
'anulowan',
|
||||||
|
],
|
||||||
|
DeliveryStatus::OUT_FOR_DELIVERY => [
|
||||||
|
'out for delivery',
|
||||||
|
'released for delivery',
|
||||||
|
'doręczeni',
|
||||||
|
'doreczenia',
|
||||||
|
'wydana do',
|
||||||
|
],
|
||||||
|
DeliveryStatus::READY_FOR_PICKUP => [
|
||||||
|
'awaiting pick-up',
|
||||||
|
'awaiting pickup',
|
||||||
|
'ready for pickup',
|
||||||
|
'ready for pick-up',
|
||||||
|
'oczekuje na odb',
|
||||||
|
'gotowa do odb',
|
||||||
|
],
|
||||||
|
DeliveryStatus::IN_TRANSIT => [
|
||||||
|
'courier',
|
||||||
|
'warehouse',
|
||||||
|
'branch',
|
||||||
|
'in transit',
|
||||||
|
'sortowni',
|
||||||
|
'magazyn',
|
||||||
|
'w drodze',
|
||||||
|
'tranzyt',
|
||||||
|
'kurier',
|
||||||
|
'wyjechał',
|
||||||
|
'wyjechala',
|
||||||
|
],
|
||||||
|
DeliveryStatus::CONFIRMED => [
|
||||||
|
'dispatched',
|
||||||
|
'nadana',
|
||||||
|
'nadano',
|
||||||
|
],
|
||||||
|
DeliveryStatus::CREATED => [
|
||||||
|
'prepared',
|
||||||
|
'created',
|
||||||
|
'przygotowan',
|
||||||
|
'utworzon',
|
||||||
|
],
|
||||||
|
DeliveryStatus::PROBLEM => [
|
||||||
|
'damaged',
|
||||||
|
'problem',
|
||||||
|
'lost',
|
||||||
|
'uszkodzon',
|
||||||
|
'zagubion',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
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);
|
||||||
|
$text = preg_replace('/^Parcel has been\s+/i', '', $text);
|
||||||
|
$text = preg_replace('/^Parcel is\s+/i', '', $text);
|
||||||
|
$text = preg_replace('/^Courier has\s+/i', '', $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');
|
||||||
|
|
||||||
|
foreach (self::DESCRIPTION_STATUS_PATTERNS as $status => $patterns) {
|
||||||
|
if (self::containsAny($lower, $patterns)) {
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str_contains($lower, "odbi\u{00F3}r") && !str_contains($lower, 'w drodze')
|
||||||
|
? DeliveryStatus::READY_FOR_PICKUP
|
||||||
|
: DeliveryStatus::UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, string> $needles
|
||||||
|
*/
|
||||||
|
private static function containsAny(string $haystack, array $needles): bool
|
||||||
|
{
|
||||||
|
foreach ($needles as $needle) {
|
||||||
|
if (str_contains($haystack, $needle)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,16 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Modules\Shipments;
|
namespace App\Modules\Shipments;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slownik znormalizowanych statusow dostawy (zrodlo prawdy dla calej aplikacji)
|
||||||
|
* oraz fasada delegujaca do wyspecjalizowanych wspolpracownikow:
|
||||||
|
* - {@see DeliveryStatusProviderMap} — mapowanie surowych statusow dostawcow,
|
||||||
|
* - {@see AllegroDescriptionGuesser} — heurystyki opisow Allegro,
|
||||||
|
* - {@see DeliveryTrackingUrlBuilder} — budowanie URL-i sledzenia.
|
||||||
|
*
|
||||||
|
* Stale i metody publiczne stanowia stabilny kontrakt uzywany przez wszystkie
|
||||||
|
* integracje kurierskie, cron, kontrolery i widoki — nie zmieniac sygnatur.
|
||||||
|
*/
|
||||||
final class DeliveryStatus
|
final class DeliveryStatus
|
||||||
{
|
{
|
||||||
private static ?DeliveryStatusRepository $repository = null;
|
private static ?DeliveryStatusRepository $repository = null;
|
||||||
@@ -25,272 +35,20 @@ final class DeliveryStatus
|
|||||||
self::CANCELLED,
|
self::CANCELLED,
|
||||||
];
|
];
|
||||||
|
|
||||||
private const DESC_PICKED_UP_BY_COURIER = 'Odebrana przez kuriera';
|
|
||||||
private const DESC_DISPATCHED = "Przesy\u{0142}ka nadana";
|
|
||||||
private const DESC_OUT_FOR_DELIVERY = "W dor\u{0119}czeniu";
|
|
||||||
private const DESC_DELIVERED = "Dor\u{0119}czona";
|
|
||||||
private const DESC_RETURNED_TO_SENDER = "Zwr\u{00F3}cona do nadawcy";
|
|
||||||
private const DESC_AWAITING_PICKUP = "Oczekuje na odbi\u{00F3}r";
|
|
||||||
private const TRACKING_INPOST_URL = 'https://inpost.pl/sledzenie-przesylek?number=';
|
|
||||||
private const TRACKING_ALLEGRO_URL = 'https://allegro.pl/allegrodelivery/sledzenie-paczki?numer=';
|
|
||||||
|
|
||||||
public const LABEL_PL = [
|
public const LABEL_PL = [
|
||||||
self::UNKNOWN => 'Nieznany',
|
self::UNKNOWN => 'Nieznany',
|
||||||
self::CREATED => 'Utworzona',
|
self::CREATED => 'Utworzona',
|
||||||
self::CONFIRMED => 'Potwierdzona',
|
self::CONFIRMED => 'Potwierdzona',
|
||||||
self::PICKED_UP => self::DESC_PICKED_UP_BY_COURIER,
|
self::PICKED_UP => 'Odebrana przez kuriera',
|
||||||
self::IN_TRANSIT => 'W tranzycie',
|
self::IN_TRANSIT => 'W tranzycie',
|
||||||
self::OUT_FOR_DELIVERY => self::DESC_OUT_FOR_DELIVERY,
|
self::OUT_FOR_DELIVERY => "W dor\u{0119}czeniu",
|
||||||
self::READY_FOR_PICKUP => 'Gotowa do odbioru',
|
self::READY_FOR_PICKUP => 'Gotowa do odbioru',
|
||||||
self::DELIVERED => self::DESC_DELIVERED,
|
self::DELIVERED => "Dor\u{0119}czona",
|
||||||
self::RETURNED => 'Zwrócona',
|
self::RETURNED => 'Zwrócona',
|
||||||
self::CANCELLED => 'Anulowana',
|
self::CANCELLED => 'Anulowana',
|
||||||
self::PROBLEM => 'Problem',
|
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::PICKED_UP,
|
|
||||||
'taken_by_courier' => self::PICKED_UP,
|
|
||||||
'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' => self::DESC_DISPATCHED,
|
|
||||||
'collected' => 'Odebrana od nadawcy',
|
|
||||||
'taken_by_courier' => self::DESC_PICKED_UP_BY_COURIER,
|
|
||||||
'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' => self::DESC_DELIVERED,
|
|
||||||
'claimed' => 'Odebrana po awizo',
|
|
||||||
'returned_to_sender' => self::DESC_RETURNED_TO_SENDER,
|
|
||||||
'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::PICKED_UP,
|
|
||||||
'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::PICKED_UP,
|
|
||||||
'PICKUP' => self::PICKED_UP,
|
|
||||||
'IN_TRANSIT' => self::IN_TRANSIT,
|
|
||||||
'OUT_FOR_DELIVERY' => self::OUT_FOR_DELIVERY,
|
|
||||||
'DELIVERED' => self::DELIVERED,
|
|
||||||
'RETURNED' => self::RETURNED,
|
|
||||||
'RETURNED_TO_SHIPPER' => 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' => self::DESC_PICKED_UP_BY_COURIER,
|
|
||||||
'3' => 'W transporcie',
|
|
||||||
'4' => self::DESC_OUT_FOR_DELIVERY,
|
|
||||||
'5' => self::DESC_DELIVERED,
|
|
||||||
'6' => self::DESC_RETURNED_TO_SENDER,
|
|
||||||
'7' => 'Anulowana',
|
|
||||||
'8' => 'Błąd zamówienia',
|
|
||||||
'9' => self::DESC_AWAITING_PICKUP . ' w punkcie',
|
|
||||||
'10' => 'Przekierowana',
|
|
||||||
'NEW' => 'Zamówienie utworzone',
|
|
||||||
'PENDING' => 'Oczekuje na przetworzenie',
|
|
||||||
'CONFIRMED' => 'Zamówienie potwierdzone',
|
|
||||||
'PICKED_UP' => self::DESC_PICKED_UP_BY_COURIER,
|
|
||||||
'PICKUP' => self::DESC_PICKED_UP_BY_COURIER,
|
|
||||||
'IN_TRANSIT' => 'W transporcie',
|
|
||||||
'OUT_FOR_DELIVERY' => self::DESC_OUT_FOR_DELIVERY,
|
|
||||||
'DELIVERED' => self::DESC_DELIVERED,
|
|
||||||
'RETURNED' => self::DESC_RETURNED_TO_SENDER,
|
|
||||||
'RETURNED_TO_SHIPPER' => self::DESC_RETURNED_TO_SENDER,
|
|
||||||
'CANCELLED' => 'Anulowana',
|
|
||||||
'ERROR' => 'Błąd zamówienia',
|
|
||||||
'WAITING_FOR_PICKUP' => self::DESC_AWAITING_PICKUP . ' w punkcie',
|
|
||||||
'REDIRECT' => 'Przekierowana',
|
|
||||||
];
|
|
||||||
|
|
||||||
private const ALLEGRO_MAP = [
|
|
||||||
'NEW' => self::CREATED,
|
|
||||||
'READY_TO_SHIP' => self::CONFIRMED,
|
|
||||||
'collected_from_sender' => self::PICKED_UP,
|
|
||||||
'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',
|
|
||||||
'collected_from_sender' => 'Odebrana od nadawcy',
|
|
||||||
'IN_TRANSIT' => 'Odebrana przez przewoźnika',
|
|
||||||
'DELIVERED' => self::DESC_DELIVERED,
|
|
||||||
'CANCELLED' => 'Anulowana',
|
|
||||||
'ERROR' => 'Błąd przetwarzania',
|
|
||||||
'RETURNED' => self::DESC_RETURNED_TO_SENDER,
|
|
||||||
];
|
|
||||||
|
|
||||||
private const ALLEGRO_EDGE_MAP = [
|
|
||||||
// Realne slugi z edge API (po slugify opisów)
|
|
||||||
'przygotowana_przez_nadawce' => self::CREATED,
|
|
||||||
'prepared_by_the_sender' => self::CREATED,
|
|
||||||
'nadana' => self::CONFIRMED,
|
|
||||||
'dispatched' => self::CONFIRMED,
|
|
||||||
'podjeta_z_maszyny_przez_kuriera' => self::PICKED_UP,
|
|
||||||
'podjeta_z_punktu_przez_kuriera' => self::PICKED_UP,
|
|
||||||
'podjeta_z_punktu' => self::PICKED_UP,
|
|
||||||
'odebrana_przez_kuriera' => self::PICKED_UP,
|
|
||||||
'picked_up_from_point_by_courier' => self::PICKED_UP,
|
|
||||||
'picked_up_by_the_courier' => self::PICKED_UP,
|
|
||||||
'przekazal_przesylke_do_magazynu' => self::IN_TRANSIT,
|
|
||||||
'przekazana_do_magazynu' => self::IN_TRANSIT,
|
|
||||||
'transferred_the_parcel_to_the_warehouse' => self::IN_TRANSIT,
|
|
||||||
'accepted_at_the_branch' => 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,
|
|
||||||
'released_for_delivery' => 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,
|
|
||||||
'awaiting_pick_up' => self::READY_FOR_PICKUP,
|
|
||||||
'dostarczona' => self::DELIVERED,
|
|
||||||
'doreczona' => self::DELIVERED,
|
|
||||||
'odebrana' => self::DELIVERED,
|
|
||||||
'delivered' => self::DELIVERED,
|
|
||||||
'zwrocona' => self::RETURNED,
|
|
||||||
'zwrocona_do_nadawcy' => self::RETURNED,
|
|
||||||
'returned_to_the_sender' => self::RETURNED,
|
|
||||||
'anulowana' => self::CANCELLED,
|
|
||||||
'cancelled' => 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ę',
|
|
||||||
'prepared_by_the_sender' => 'Przesyłka przygotowana przez nadawcę',
|
|
||||||
'nadana' => self::DESC_DISPATCHED,
|
|
||||||
'dispatched' => self::DESC_DISPATCHED,
|
|
||||||
'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' => self::DESC_PICKED_UP_BY_COURIER,
|
|
||||||
'picked_up_from_point_by_courier' => 'Podjęta z punktu przez kuriera',
|
|
||||||
'picked_up_by_the_courier' => self::DESC_PICKED_UP_BY_COURIER,
|
|
||||||
'przekazana_do_magazynu' => 'Przekazana do magazynu',
|
|
||||||
'transferred_the_parcel_to_the_warehouse' => 'Przekazana do magazynu',
|
|
||||||
'accepted_at_the_branch' => 'Przyjęta w oddziale',
|
|
||||||
'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' => self::DESC_OUT_FOR_DELIVERY,
|
|
||||||
'wydana_do_doreczenia' => 'Wydana do doręczenia',
|
|
||||||
'released_for_delivery' => 'Wydana do doręczenia',
|
|
||||||
'dostarczana' => 'Dostarczana',
|
|
||||||
'gotowa_do_odbioru' => 'Gotowa do odbioru',
|
|
||||||
'oczekuje_na_odbior' => self::DESC_AWAITING_PICKUP,
|
|
||||||
'przesylka_oczekuje_na_odbior' => self::DESC_AWAITING_PICKUP,
|
|
||||||
'awaiting_pick_up' => self::DESC_AWAITING_PICKUP,
|
|
||||||
'dostarczona' => 'Dostarczona',
|
|
||||||
'doreczona' => self::DESC_DELIVERED,
|
|
||||||
'odebrana' => 'Odebrana',
|
|
||||||
'delivered' => 'Dostarczona',
|
|
||||||
'zwrocona' => 'Zwrócona',
|
|
||||||
'zwrocona_do_nadawcy' => self::DESC_RETURNED_TO_SENDER,
|
|
||||||
'returned_to_the_sender' => self::DESC_RETURNED_TO_SENDER,
|
|
||||||
'anulowana' => 'Anulowana',
|
|
||||||
'cancelled' => 'Anulowana',
|
|
||||||
'odmowa_przyjecia' => 'Odmowa przyjęcia',
|
|
||||||
'uszkodzona' => 'Uszkodzona',
|
|
||||||
'zagubiona' => 'Zagubiona',
|
|
||||||
];
|
|
||||||
|
|
||||||
public const ALL_STATUSES = [
|
public const ALL_STATUSES = [
|
||||||
self::UNKNOWN,
|
self::UNKNOWN,
|
||||||
self::CREATED,
|
self::CREATED,
|
||||||
@@ -305,151 +63,14 @@ final class DeliveryStatus
|
|||||||
self::PROBLEM,
|
self::PROBLEM,
|
||||||
];
|
];
|
||||||
|
|
||||||
private const POLKURIER_MAP = [
|
// --- Mapowanie statusow dostawcow (delegacja do DeliveryStatusProviderMap) ---
|
||||||
// Oficjalne kody ORDER_STATUS z dokumentacji polkurier API v1.11 (marzec 2026)
|
|
||||||
'O' => self::CREATED,
|
|
||||||
'P' => self::CONFIRMED,
|
|
||||||
'A' => self::CANCELLED,
|
|
||||||
'WP' => self::IN_TRANSIT,
|
|
||||||
'D' => self::DELIVERED,
|
|
||||||
'Z' => self::RETURNED,
|
|
||||||
'W' => self::PROBLEM,
|
|
||||||
];
|
|
||||||
|
|
||||||
private const POLKURIER_DESCRIPTIONS = [
|
|
||||||
'O' => 'Oczekuje na płatność',
|
|
||||||
'P' => 'Potwierdzone, list wygenerowany',
|
|
||||||
'A' => 'Anulowane',
|
|
||||||
'WP' => 'W przewozie',
|
|
||||||
'D' => 'Dostarczona',
|
|
||||||
'Z' => 'Zwrot do nadawcy',
|
|
||||||
'W' => 'Wyjątek',
|
|
||||||
];
|
|
||||||
|
|
||||||
private const PROVIDER_MAPS = [
|
|
||||||
'inpost' => self::INPOST_MAP,
|
|
||||||
'apaczka' => self::APACZKA_MAP,
|
|
||||||
'allegro_wza' => self::ALLEGRO_MAP,
|
|
||||||
'allegro_edge' => self::ALLEGRO_EDGE_MAP,
|
|
||||||
'polkurier' => self::POLKURIER_MAP,
|
|
||||||
];
|
|
||||||
|
|
||||||
private const PROVIDER_DESCRIPTIONS = [
|
|
||||||
'inpost' => self::INPOST_DESCRIPTIONS,
|
|
||||||
'apaczka' => self::APACZKA_DESCRIPTIONS,
|
|
||||||
'allegro_wza' => self::ALLEGRO_DESCRIPTIONS,
|
|
||||||
'allegro_edge' => self::ALLEGRO_EDGE_DESCRIPTIONS,
|
|
||||||
'polkurier' => self::POLKURIER_DESCRIPTIONS,
|
|
||||||
];
|
|
||||||
|
|
||||||
private const DESCRIPTION_STATUS_PATTERNS = [
|
|
||||||
self::DELIVERED => [
|
|
||||||
'delivered',
|
|
||||||
'picked up by recipient',
|
|
||||||
'doręczon',
|
|
||||||
'dostarczono',
|
|
||||||
'odebrana przez odbiorc',
|
|
||||||
],
|
|
||||||
self::PICKED_UP => [
|
|
||||||
'picked up by courier',
|
|
||||||
'picked up from point',
|
|
||||||
'podjęta',
|
|
||||||
'podjeta',
|
|
||||||
'odebrana przez kuriera',
|
|
||||||
],
|
|
||||||
self::RETURNED => [
|
|
||||||
'returned',
|
|
||||||
'zwrócon',
|
|
||||||
'zwrocona',
|
|
||||||
],
|
|
||||||
self::CANCELLED => [
|
|
||||||
'cancelled',
|
|
||||||
'canceled',
|
|
||||||
'anulowan',
|
|
||||||
],
|
|
||||||
self::OUT_FOR_DELIVERY => [
|
|
||||||
'out for delivery',
|
|
||||||
'released for delivery',
|
|
||||||
'doręczeni',
|
|
||||||
'doreczenia',
|
|
||||||
'wydana do',
|
|
||||||
],
|
|
||||||
self::READY_FOR_PICKUP => [
|
|
||||||
'awaiting pick-up',
|
|
||||||
'awaiting pickup',
|
|
||||||
'ready for pickup',
|
|
||||||
'ready for pick-up',
|
|
||||||
'oczekuje na odb',
|
|
||||||
'gotowa do odb',
|
|
||||||
],
|
|
||||||
self::IN_TRANSIT => [
|
|
||||||
'courier',
|
|
||||||
'warehouse',
|
|
||||||
'branch',
|
|
||||||
'in transit',
|
|
||||||
'sortowni',
|
|
||||||
'magazyn',
|
|
||||||
'w drodze',
|
|
||||||
'tranzyt',
|
|
||||||
'kurier',
|
|
||||||
'wyjechał',
|
|
||||||
'wyjechala',
|
|
||||||
],
|
|
||||||
self::CONFIRMED => [
|
|
||||||
'dispatched',
|
|
||||||
'nadana',
|
|
||||||
'nadano',
|
|
||||||
],
|
|
||||||
self::CREATED => [
|
|
||||||
'prepared',
|
|
||||||
'created',
|
|
||||||
'przygotowan',
|
|
||||||
'utworzon',
|
|
||||||
],
|
|
||||||
self::PROBLEM => [
|
|
||||||
'damaged',
|
|
||||||
'problem',
|
|
||||||
'lost',
|
|
||||||
'uszkodzon',
|
|
||||||
'zagubion',
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
private const PROVIDER_TRACKING_URLS = [
|
|
||||||
'inpost' => self::TRACKING_INPOST_URL,
|
|
||||||
'allegro_wza' => self::TRACKING_ALLEGRO_URL,
|
|
||||||
'polkurier' => 'https://polkurier.pl/sledz-paczke/',
|
|
||||||
];
|
|
||||||
|
|
||||||
private const CARRIER_TRACKING_URLS = [
|
|
||||||
[['dpd'], 'https://tracktrace.dpd.com.pl/parcelDetails?p1='],
|
|
||||||
[['dhl'], 'https://www.dhl.com/pl-pl/home/sledzenie-przesylki.html?tracking-id='],
|
|
||||||
[['inpost', 'paczkomat'], self::TRACKING_INPOST_URL],
|
|
||||||
[['orlen', 'ruch'], 'https://www.orlenpaczka.pl/sledz-paczke/?numer='],
|
|
||||||
[['poczta', 'pocztex'], 'https://emonitoring.poczta-polska.pl/?numer='],
|
|
||||||
[['ups'], 'https://www.ups.com/track?tracknum='],
|
|
||||||
[['fedex'], 'https://www.fedex.com/fedextrack/?trknbr='],
|
|
||||||
[['gls'], 'https://gls-group.com/PL/pl/sledzenie-paczek?match='],
|
|
||||||
[['allegro'], self::TRACKING_ALLEGRO_URL],
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<string, array{normalized: string, description: string}>
|
* @return array<string, array{normalized: string, description: string}>
|
||||||
*/
|
*/
|
||||||
public static function getDefaultMappings(string $provider): array
|
public static function getDefaultMappings(string $provider): array
|
||||||
{
|
{
|
||||||
$map = self::PROVIDER_MAPS[$provider] ?? [];
|
return DeliveryStatusProviderMap::getDefaultMappings($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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -457,12 +78,7 @@ final class DeliveryStatus
|
|||||||
*/
|
*/
|
||||||
public static function normalizeWithOverrides(string $provider, string $rawStatus, array $overrides): string
|
public static function normalizeWithOverrides(string $provider, string $rawStatus, array $overrides): string
|
||||||
{
|
{
|
||||||
$key = $provider . ':' . $rawStatus;
|
return DeliveryStatusProviderMap::normalizeWithOverrides($provider, $rawStatus, $overrides);
|
||||||
if (isset($overrides[$key]) && $overrides[$key]['normalized_status'] !== '') {
|
|
||||||
return $overrides[$key]['normalized_status'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::normalize($provider, $rawStatus);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -470,24 +86,21 @@ final class DeliveryStatus
|
|||||||
*/
|
*/
|
||||||
public static function descriptionWithOverrides(string $provider, string $rawStatus, array $overrides): string
|
public static function descriptionWithOverrides(string $provider, string $rawStatus, array $overrides): string
|
||||||
{
|
{
|
||||||
$key = $provider . ':' . $rawStatus;
|
return DeliveryStatusProviderMap::descriptionWithOverrides($provider, $rawStatus, $overrides);
|
||||||
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
|
public static function normalize(string $provider, string $rawStatus): string
|
||||||
{
|
{
|
||||||
return self::PROVIDER_MAPS[$provider][$rawStatus] ?? self::UNKNOWN;
|
return DeliveryStatusProviderMap::normalize($provider, $rawStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function description(string $provider, string $rawStatus): string
|
public static function description(string $provider, string $rawStatus): string
|
||||||
{
|
{
|
||||||
return self::PROVIDER_DESCRIPTIONS[$provider][$rawStatus] ?? $rawStatus;
|
return DeliveryStatusProviderMap::description($provider, $rawStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Most do bazy danych (slownik statusow konfigurowalny w panelu) ---
|
||||||
|
|
||||||
public static function setRepository(DeliveryStatusRepository $repo): void
|
public static function setRepository(DeliveryStatusRepository $repo): void
|
||||||
{
|
{
|
||||||
self::$repository = $repo;
|
self::$repository = $repo;
|
||||||
@@ -536,122 +149,22 @@ final class DeliveryStatus
|
|||||||
return in_array($status, self::TERMINAL_STATUSES, true);
|
return in_array($status, self::TERMINAL_STATUSES, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Heurystyki opisow Allegro (delegacja do AllegroDescriptionGuesser) ---
|
||||||
|
|
||||||
public static function slugifyAllegroDescription(string $description): string
|
public static function slugifyAllegroDescription(string $description): string
|
||||||
{
|
{
|
||||||
$text = trim($description);
|
return AllegroDescriptionGuesser::slugifyAllegroDescription($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);
|
|
||||||
$text = preg_replace('/^Parcel has been\s+/i', '', $text);
|
|
||||||
$text = preg_replace('/^Parcel is\s+/i', '', $text);
|
|
||||||
$text = preg_replace('/^Courier has\s+/i', '', $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
|
public static function guessStatusFromDescription(string $description): string
|
||||||
{
|
{
|
||||||
$lower = mb_strtolower($description, 'UTF-8');
|
return AllegroDescriptionGuesser::guessStatusFromDescription($description);
|
||||||
|
|
||||||
foreach (self::DESCRIPTION_STATUS_PATTERNS as $status => $patterns) {
|
|
||||||
if (self::containsAny($lower, $patterns)) {
|
|
||||||
return $status;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return str_contains($lower, "odbi\u{00F3}r") && !str_contains($lower, 'w drodze')
|
// --- Budowanie URL-i sledzenia (delegacja do DeliveryTrackingUrlBuilder) ---
|
||||||
? self::READY_FOR_PICKUP
|
|
||||||
: self::UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function trackingUrl(string $provider, string $trackingNumber, string $carrierId = ''): ?string
|
public static function trackingUrl(string $provider, string $trackingNumber, string $carrierId = ''): ?string
|
||||||
{
|
{
|
||||||
$number = trim($trackingNumber);
|
return DeliveryTrackingUrlBuilder::trackingUrl($provider, $trackingNumber, $carrierId);
|
||||||
if ($number === '') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
$encoded = rawurlencode($number);
|
|
||||||
$providerUrl = self::providerTrackingUrl($provider, $encoded);
|
|
||||||
if ($provider === 'inpost') {
|
|
||||||
return $providerUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::carrierTrackingUrl($encoded, $carrierId) ?? $providerUrl ?? self::fallbackTrackingUrl($encoded);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function carrierTrackingUrl(string $encoded, string $carrierId): ?string
|
|
||||||
{
|
|
||||||
$carrier = strtolower(trim($carrierId));
|
|
||||||
return $carrier === '' ? null : self::matchCarrierByName($encoded, $carrier);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function providerTrackingUrl(string $provider, string $encoded): ?string
|
|
||||||
{
|
|
||||||
$baseUrl = self::PROVIDER_TRACKING_URLS[$provider] ?? null;
|
|
||||||
return $baseUrl === null ? null : $baseUrl . $encoded;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function fallbackTrackingUrl(string $encoded): string
|
|
||||||
{
|
|
||||||
return 'https://www.google.com/search?q=' . $encoded . '+sledzenie+przesylki';
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function matchCarrierByName(string $encoded, string $carrier): ?string
|
|
||||||
{
|
|
||||||
foreach (self::CARRIER_TRACKING_URLS as [$patterns, $baseUrl]) {
|
|
||||||
if (self::carrierMatches($carrier, $patterns)) {
|
|
||||||
return $baseUrl . $encoded;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<int, string> $needles
|
|
||||||
*/
|
|
||||||
private static function containsAny(string $haystack, array $needles): bool
|
|
||||||
{
|
|
||||||
foreach ($needles as $needle) {
|
|
||||||
if (str_contains($haystack, $needle)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<int, string> $patterns
|
|
||||||
*/
|
|
||||||
private static function carrierMatches(string $carrier, array $patterns): bool
|
|
||||||
{
|
|
||||||
foreach ($patterns as $pattern) {
|
|
||||||
if ($carrier === $pattern || str_contains($carrier, $pattern)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
353
src/Modules/Shipments/DeliveryStatusProviderMap.php
Normal file
353
src/Modules/Shipments/DeliveryStatusProviderMap.php
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Modules\Shipments;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapowanie surowych statusow dostawcow (InPost, Apaczka, Allegro, Polkurier)
|
||||||
|
* na znormalizowane statusy {@see DeliveryStatus} oraz ich polskie opisy.
|
||||||
|
*/
|
||||||
|
final class DeliveryStatusProviderMap
|
||||||
|
{
|
||||||
|
private const DESC_PICKED_UP_BY_COURIER = 'Odebrana przez kuriera';
|
||||||
|
private const DESC_DISPATCHED = "Przesy\u{0142}ka nadana";
|
||||||
|
private const DESC_OUT_FOR_DELIVERY = "W dor\u{0119}czeniu";
|
||||||
|
private const DESC_DELIVERED = "Dor\u{0119}czona";
|
||||||
|
private const DESC_RETURNED_TO_SENDER = "Zwr\u{00F3}cona do nadawcy";
|
||||||
|
private const DESC_AWAITING_PICKUP = "Oczekuje na odbi\u{00F3}r";
|
||||||
|
|
||||||
|
private const INPOST_MAP = [
|
||||||
|
'created' => DeliveryStatus::CREATED,
|
||||||
|
'offers_prepared' => DeliveryStatus::CREATED,
|
||||||
|
'offer_selected' => DeliveryStatus::CREATED,
|
||||||
|
'confirmed' => DeliveryStatus::CONFIRMED,
|
||||||
|
'dispatched' => DeliveryStatus::CONFIRMED,
|
||||||
|
'collected' => DeliveryStatus::PICKED_UP,
|
||||||
|
'taken_by_courier' => DeliveryStatus::PICKED_UP,
|
||||||
|
'adopted_at_source_branch' => DeliveryStatus::IN_TRANSIT,
|
||||||
|
'adopted_at_sorting_center' => DeliveryStatus::IN_TRANSIT,
|
||||||
|
'sent_from_sorting_center' => DeliveryStatus::IN_TRANSIT,
|
||||||
|
'adopted_at_target_sorting_center' => DeliveryStatus::IN_TRANSIT,
|
||||||
|
'sent_from_target_sorting_center' => DeliveryStatus::IN_TRANSIT,
|
||||||
|
'adopted_at_target_branch' => DeliveryStatus::IN_TRANSIT,
|
||||||
|
'out_for_delivery' => DeliveryStatus::OUT_FOR_DELIVERY,
|
||||||
|
'ready_to_pickup' => DeliveryStatus::READY_FOR_PICKUP,
|
||||||
|
'ready_to_pickup_from_branch' => DeliveryStatus::READY_FOR_PICKUP,
|
||||||
|
'ready_to_pickup_from_pok' => DeliveryStatus::READY_FOR_PICKUP,
|
||||||
|
'stack_in_box_machine' => DeliveryStatus::READY_FOR_PICKUP,
|
||||||
|
'stack_in_customer_service_point' => DeliveryStatus::READY_FOR_PICKUP,
|
||||||
|
'delivered' => DeliveryStatus::DELIVERED,
|
||||||
|
'claimed' => DeliveryStatus::DELIVERED,
|
||||||
|
'returned_to_sender' => DeliveryStatus::RETURNED,
|
||||||
|
'undelivered' => DeliveryStatus::RETURNED,
|
||||||
|
'undelivered_wrong_address' => DeliveryStatus::RETURNED,
|
||||||
|
'undelivered_incomplete_address' => DeliveryStatus::RETURNED,
|
||||||
|
'undelivered_unknown_recipient' => DeliveryStatus::RETURNED,
|
||||||
|
'undelivered_cod_cash_receiver' => DeliveryStatus::RETURNED,
|
||||||
|
'cancelled' => DeliveryStatus::CANCELLED,
|
||||||
|
'expired' => DeliveryStatus::CANCELLED,
|
||||||
|
'avizo' => DeliveryStatus::PROBLEM,
|
||||||
|
'pickup_time_expired' => DeliveryStatus::PROBLEM,
|
||||||
|
'stack_parcel_pickup_time_expired' => DeliveryStatus::PROBLEM,
|
||||||
|
'missing' => DeliveryStatus::PROBLEM,
|
||||||
|
'delay_in_delivery' => DeliveryStatus::PROBLEM,
|
||||||
|
'oversized' => DeliveryStatus::PROBLEM,
|
||||||
|
'pickup_reminder_sent' => DeliveryStatus::READY_FOR_PICKUP,
|
||||||
|
'pickup_reminder_sent_address' => DeliveryStatus::READY_FOR_PICKUP,
|
||||||
|
'readdressed' => DeliveryStatus::IN_TRANSIT,
|
||||||
|
'redirect_to_box' => DeliveryStatus::IN_TRANSIT,
|
||||||
|
];
|
||||||
|
|
||||||
|
private const INPOST_DESCRIPTIONS = [
|
||||||
|
'created' => 'Przesyłka utworzona',
|
||||||
|
'offers_prepared' => 'Oferty cenowe przygotowane',
|
||||||
|
'offer_selected' => 'Oferta wybrana',
|
||||||
|
'confirmed' => 'Przesyłka potwierdzona',
|
||||||
|
'dispatched' => self::DESC_DISPATCHED,
|
||||||
|
'collected' => 'Odebrana od nadawcy',
|
||||||
|
'taken_by_courier' => self::DESC_PICKED_UP_BY_COURIER,
|
||||||
|
'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' => self::DESC_DELIVERED,
|
||||||
|
'claimed' => 'Odebrana po awizo',
|
||||||
|
'returned_to_sender' => self::DESC_RETURNED_TO_SENDER,
|
||||||
|
'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' => DeliveryStatus::CREATED,
|
||||||
|
'1' => DeliveryStatus::CONFIRMED,
|
||||||
|
'2' => DeliveryStatus::PICKED_UP,
|
||||||
|
'3' => DeliveryStatus::IN_TRANSIT,
|
||||||
|
'4' => DeliveryStatus::OUT_FOR_DELIVERY,
|
||||||
|
'5' => DeliveryStatus::DELIVERED,
|
||||||
|
'6' => DeliveryStatus::RETURNED,
|
||||||
|
'7' => DeliveryStatus::CANCELLED,
|
||||||
|
'8' => DeliveryStatus::PROBLEM,
|
||||||
|
'9' => DeliveryStatus::READY_FOR_PICKUP,
|
||||||
|
'10' => DeliveryStatus::IN_TRANSIT,
|
||||||
|
'NEW' => DeliveryStatus::CREATED,
|
||||||
|
'PENDING' => DeliveryStatus::CREATED,
|
||||||
|
'CONFIRMED' => DeliveryStatus::CONFIRMED,
|
||||||
|
'PICKED_UP' => DeliveryStatus::PICKED_UP,
|
||||||
|
'PICKUP' => DeliveryStatus::PICKED_UP,
|
||||||
|
'IN_TRANSIT' => DeliveryStatus::IN_TRANSIT,
|
||||||
|
'OUT_FOR_DELIVERY' => DeliveryStatus::OUT_FOR_DELIVERY,
|
||||||
|
'DELIVERED' => DeliveryStatus::DELIVERED,
|
||||||
|
'RETURNED' => DeliveryStatus::RETURNED,
|
||||||
|
'RETURNED_TO_SHIPPER' => DeliveryStatus::RETURNED,
|
||||||
|
'CANCELLED' => DeliveryStatus::CANCELLED,
|
||||||
|
'ERROR' => DeliveryStatus::PROBLEM,
|
||||||
|
'WAITING_FOR_PICKUP' => DeliveryStatus::READY_FOR_PICKUP,
|
||||||
|
'REDIRECT' => DeliveryStatus::IN_TRANSIT,
|
||||||
|
];
|
||||||
|
|
||||||
|
private const APACZKA_DESCRIPTIONS = [
|
||||||
|
'0' => 'Oczekuje na przetworzenie',
|
||||||
|
'1' => 'Zamówienie potwierdzone',
|
||||||
|
'2' => self::DESC_PICKED_UP_BY_COURIER,
|
||||||
|
'3' => 'W transporcie',
|
||||||
|
'4' => self::DESC_OUT_FOR_DELIVERY,
|
||||||
|
'5' => self::DESC_DELIVERED,
|
||||||
|
'6' => self::DESC_RETURNED_TO_SENDER,
|
||||||
|
'7' => 'Anulowana',
|
||||||
|
'8' => 'Błąd zamówienia',
|
||||||
|
'9' => self::DESC_AWAITING_PICKUP . ' w punkcie',
|
||||||
|
'10' => 'Przekierowana',
|
||||||
|
'NEW' => 'Zamówienie utworzone',
|
||||||
|
'PENDING' => 'Oczekuje na przetworzenie',
|
||||||
|
'CONFIRMED' => 'Zamówienie potwierdzone',
|
||||||
|
'PICKED_UP' => self::DESC_PICKED_UP_BY_COURIER,
|
||||||
|
'PICKUP' => self::DESC_PICKED_UP_BY_COURIER,
|
||||||
|
'IN_TRANSIT' => 'W transporcie',
|
||||||
|
'OUT_FOR_DELIVERY' => self::DESC_OUT_FOR_DELIVERY,
|
||||||
|
'DELIVERED' => self::DESC_DELIVERED,
|
||||||
|
'RETURNED' => self::DESC_RETURNED_TO_SENDER,
|
||||||
|
'RETURNED_TO_SHIPPER' => self::DESC_RETURNED_TO_SENDER,
|
||||||
|
'CANCELLED' => 'Anulowana',
|
||||||
|
'ERROR' => 'Błąd zamówienia',
|
||||||
|
'WAITING_FOR_PICKUP' => self::DESC_AWAITING_PICKUP . ' w punkcie',
|
||||||
|
'REDIRECT' => 'Przekierowana',
|
||||||
|
];
|
||||||
|
|
||||||
|
private const ALLEGRO_MAP = [
|
||||||
|
'NEW' => DeliveryStatus::CREATED,
|
||||||
|
'READY_TO_SHIP' => DeliveryStatus::CONFIRMED,
|
||||||
|
'collected_from_sender' => DeliveryStatus::PICKED_UP,
|
||||||
|
'IN_TRANSIT' => DeliveryStatus::IN_TRANSIT,
|
||||||
|
'DELIVERED' => DeliveryStatus::DELIVERED,
|
||||||
|
'CANCELLED' => DeliveryStatus::CANCELLED,
|
||||||
|
'ERROR' => DeliveryStatus::PROBLEM,
|
||||||
|
'RETURNED' => DeliveryStatus::RETURNED,
|
||||||
|
];
|
||||||
|
|
||||||
|
private const ALLEGRO_DESCRIPTIONS = [
|
||||||
|
'NEW' => 'Przesyłka utworzona',
|
||||||
|
'READY_TO_SHIP' => 'Etykieta wygenerowana, oczekuje na nadanie',
|
||||||
|
'collected_from_sender' => 'Odebrana od nadawcy',
|
||||||
|
'IN_TRANSIT' => 'Odebrana przez przewoźnika',
|
||||||
|
'DELIVERED' => self::DESC_DELIVERED,
|
||||||
|
'CANCELLED' => 'Anulowana',
|
||||||
|
'ERROR' => 'Błąd przetwarzania',
|
||||||
|
'RETURNED' => self::DESC_RETURNED_TO_SENDER,
|
||||||
|
];
|
||||||
|
|
||||||
|
private const ALLEGRO_EDGE_MAP = [
|
||||||
|
// Realne slugi z edge API (po slugify opisów)
|
||||||
|
'przygotowana_przez_nadawce' => DeliveryStatus::CREATED,
|
||||||
|
'prepared_by_the_sender' => DeliveryStatus::CREATED,
|
||||||
|
'nadana' => DeliveryStatus::CONFIRMED,
|
||||||
|
'dispatched' => DeliveryStatus::CONFIRMED,
|
||||||
|
'podjeta_z_maszyny_przez_kuriera' => DeliveryStatus::PICKED_UP,
|
||||||
|
'podjeta_z_punktu_przez_kuriera' => DeliveryStatus::PICKED_UP,
|
||||||
|
'podjeta_z_punktu' => DeliveryStatus::PICKED_UP,
|
||||||
|
'odebrana_przez_kuriera' => DeliveryStatus::PICKED_UP,
|
||||||
|
'picked_up_from_point_by_courier' => DeliveryStatus::PICKED_UP,
|
||||||
|
'picked_up_by_the_courier' => DeliveryStatus::PICKED_UP,
|
||||||
|
'przekazal_przesylke_do_magazynu' => DeliveryStatus::IN_TRANSIT,
|
||||||
|
'przekazana_do_magazynu' => DeliveryStatus::IN_TRANSIT,
|
||||||
|
'transferred_the_parcel_to_the_warehouse' => DeliveryStatus::IN_TRANSIT,
|
||||||
|
'accepted_at_the_branch' => DeliveryStatus::IN_TRANSIT,
|
||||||
|
'przesylka_wyjechala_w_droge_do_punktu_docelowego' => DeliveryStatus::IN_TRANSIT,
|
||||||
|
'w_sortowni' => DeliveryStatus::IN_TRANSIT,
|
||||||
|
'wyjechala_w_droge_do_punktu_docelowego' => DeliveryStatus::IN_TRANSIT,
|
||||||
|
'wyslana_z_sortowni' => DeliveryStatus::IN_TRANSIT,
|
||||||
|
'w_doreczeniu' => DeliveryStatus::OUT_FOR_DELIVERY,
|
||||||
|
'wydana_do_doreczenia' => DeliveryStatus::OUT_FOR_DELIVERY,
|
||||||
|
'released_for_delivery' => DeliveryStatus::OUT_FOR_DELIVERY,
|
||||||
|
'dostarczana' => DeliveryStatus::OUT_FOR_DELIVERY,
|
||||||
|
'gotowa_do_odbioru' => DeliveryStatus::READY_FOR_PICKUP,
|
||||||
|
'oczekuje_na_odbior' => DeliveryStatus::READY_FOR_PICKUP,
|
||||||
|
'przesylka_oczekuje_na_odbior' => DeliveryStatus::READY_FOR_PICKUP,
|
||||||
|
'awaiting_pick_up' => DeliveryStatus::READY_FOR_PICKUP,
|
||||||
|
'dostarczona' => DeliveryStatus::DELIVERED,
|
||||||
|
'doreczona' => DeliveryStatus::DELIVERED,
|
||||||
|
'odebrana' => DeliveryStatus::DELIVERED,
|
||||||
|
'delivered' => DeliveryStatus::DELIVERED,
|
||||||
|
'zwrocona' => DeliveryStatus::RETURNED,
|
||||||
|
'zwrocona_do_nadawcy' => DeliveryStatus::RETURNED,
|
||||||
|
'returned_to_the_sender' => DeliveryStatus::RETURNED,
|
||||||
|
'anulowana' => DeliveryStatus::CANCELLED,
|
||||||
|
'cancelled' => DeliveryStatus::CANCELLED,
|
||||||
|
'odmowa_przyjecia' => DeliveryStatus::PROBLEM,
|
||||||
|
'uszkodzona' => DeliveryStatus::PROBLEM,
|
||||||
|
'zagubiona' => DeliveryStatus::PROBLEM,
|
||||||
|
];
|
||||||
|
|
||||||
|
private const ALLEGRO_EDGE_DESCRIPTIONS = [
|
||||||
|
'przygotowana_przez_nadawce' => 'Przesyłka przygotowana przez nadawcę',
|
||||||
|
'prepared_by_the_sender' => 'Przesyłka przygotowana przez nadawcę',
|
||||||
|
'nadana' => self::DESC_DISPATCHED,
|
||||||
|
'dispatched' => self::DESC_DISPATCHED,
|
||||||
|
'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' => self::DESC_PICKED_UP_BY_COURIER,
|
||||||
|
'picked_up_from_point_by_courier' => 'Podjęta z punktu przez kuriera',
|
||||||
|
'picked_up_by_the_courier' => self::DESC_PICKED_UP_BY_COURIER,
|
||||||
|
'przekazana_do_magazynu' => 'Przekazana do magazynu',
|
||||||
|
'transferred_the_parcel_to_the_warehouse' => 'Przekazana do magazynu',
|
||||||
|
'accepted_at_the_branch' => 'Przyjęta w oddziale',
|
||||||
|
'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' => self::DESC_OUT_FOR_DELIVERY,
|
||||||
|
'wydana_do_doreczenia' => 'Wydana do doręczenia',
|
||||||
|
'released_for_delivery' => 'Wydana do doręczenia',
|
||||||
|
'dostarczana' => 'Dostarczana',
|
||||||
|
'gotowa_do_odbioru' => 'Gotowa do odbioru',
|
||||||
|
'oczekuje_na_odbior' => self::DESC_AWAITING_PICKUP,
|
||||||
|
'przesylka_oczekuje_na_odbior' => self::DESC_AWAITING_PICKUP,
|
||||||
|
'awaiting_pick_up' => self::DESC_AWAITING_PICKUP,
|
||||||
|
'dostarczona' => 'Dostarczona',
|
||||||
|
'doreczona' => self::DESC_DELIVERED,
|
||||||
|
'odebrana' => 'Odebrana',
|
||||||
|
'delivered' => 'Dostarczona',
|
||||||
|
'zwrocona' => 'Zwrócona',
|
||||||
|
'zwrocona_do_nadawcy' => self::DESC_RETURNED_TO_SENDER,
|
||||||
|
'returned_to_the_sender' => self::DESC_RETURNED_TO_SENDER,
|
||||||
|
'anulowana' => 'Anulowana',
|
||||||
|
'cancelled' => 'Anulowana',
|
||||||
|
'odmowa_przyjecia' => 'Odmowa przyjęcia',
|
||||||
|
'uszkodzona' => 'Uszkodzona',
|
||||||
|
'zagubiona' => 'Zagubiona',
|
||||||
|
];
|
||||||
|
|
||||||
|
private const POLKURIER_MAP = [
|
||||||
|
// Oficjalne kody ORDER_STATUS z dokumentacji polkurier API v1.11 (marzec 2026)
|
||||||
|
'O' => DeliveryStatus::CREATED,
|
||||||
|
'P' => DeliveryStatus::CONFIRMED,
|
||||||
|
'A' => DeliveryStatus::CANCELLED,
|
||||||
|
'WP' => DeliveryStatus::IN_TRANSIT,
|
||||||
|
'D' => DeliveryStatus::DELIVERED,
|
||||||
|
'Z' => DeliveryStatus::RETURNED,
|
||||||
|
'W' => DeliveryStatus::PROBLEM,
|
||||||
|
];
|
||||||
|
|
||||||
|
private const POLKURIER_DESCRIPTIONS = [
|
||||||
|
'O' => 'Oczekuje na płatność',
|
||||||
|
'P' => 'Potwierdzone, list wygenerowany',
|
||||||
|
'A' => 'Anulowane',
|
||||||
|
'WP' => 'W przewozie',
|
||||||
|
'D' => 'Dostarczona',
|
||||||
|
'Z' => 'Zwrot do nadawcy',
|
||||||
|
'W' => 'Wyjątek',
|
||||||
|
];
|
||||||
|
|
||||||
|
private const PROVIDER_MAPS = [
|
||||||
|
'inpost' => self::INPOST_MAP,
|
||||||
|
'apaczka' => self::APACZKA_MAP,
|
||||||
|
'allegro_wza' => self::ALLEGRO_MAP,
|
||||||
|
'allegro_edge' => self::ALLEGRO_EDGE_MAP,
|
||||||
|
'polkurier' => self::POLKURIER_MAP,
|
||||||
|
];
|
||||||
|
|
||||||
|
private const PROVIDER_DESCRIPTIONS = [
|
||||||
|
'inpost' => self::INPOST_DESCRIPTIONS,
|
||||||
|
'apaczka' => self::APACZKA_DESCRIPTIONS,
|
||||||
|
'allegro_wza' => self::ALLEGRO_DESCRIPTIONS,
|
||||||
|
'allegro_edge' => self::ALLEGRO_EDGE_DESCRIPTIONS,
|
||||||
|
'polkurier' => self::POLKURIER_DESCRIPTIONS,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, array{normalized: string, description: string}>
|
||||||
|
*/
|
||||||
|
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<string, array{normalized_status: string, description: string}> $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<string, array{normalized_status: string, description: string}> $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
|
||||||
|
{
|
||||||
|
return self::PROVIDER_MAPS[$provider][$rawStatus] ?? DeliveryStatus::UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function description(string $provider, string $rawStatus): string
|
||||||
|
{
|
||||||
|
return self::PROVIDER_DESCRIPTIONS[$provider][$rawStatus] ?? $rawStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
90
src/Modules/Shipments/DeliveryTrackingUrlBuilder.php
Normal file
90
src/Modules/Shipments/DeliveryTrackingUrlBuilder.php
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Modules\Shipments;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Budowanie publicznych URL-i sledzenia przesylki na podstawie dostawcy
|
||||||
|
* (provider), nazwy kuriera (carrierId) i numeru przesylki.
|
||||||
|
*/
|
||||||
|
final class DeliveryTrackingUrlBuilder
|
||||||
|
{
|
||||||
|
private const TRACKING_INPOST_URL = 'https://inpost.pl/sledzenie-przesylek?number=';
|
||||||
|
private const TRACKING_ALLEGRO_URL = 'https://allegro.pl/allegrodelivery/sledzenie-paczki?numer=';
|
||||||
|
|
||||||
|
private const PROVIDER_TRACKING_URLS = [
|
||||||
|
'inpost' => self::TRACKING_INPOST_URL,
|
||||||
|
'allegro_wza' => self::TRACKING_ALLEGRO_URL,
|
||||||
|
'polkurier' => 'https://polkurier.pl/sledz-paczke/',
|
||||||
|
];
|
||||||
|
|
||||||
|
private const CARRIER_TRACKING_URLS = [
|
||||||
|
[['dpd'], 'https://tracktrace.dpd.com.pl/parcelDetails?p1='],
|
||||||
|
[['dhl'], 'https://www.dhl.com/pl-pl/home/sledzenie-przesylki.html?tracking-id='],
|
||||||
|
[['inpost', 'paczkomat'], self::TRACKING_INPOST_URL],
|
||||||
|
[['orlen', 'ruch'], 'https://www.orlenpaczka.pl/sledz-paczke/?numer='],
|
||||||
|
[['poczta', 'pocztex'], 'https://emonitoring.poczta-polska.pl/?numer='],
|
||||||
|
[['ups'], 'https://www.ups.com/track?tracknum='],
|
||||||
|
[['fedex'], 'https://www.fedex.com/fedextrack/?trknbr='],
|
||||||
|
[['gls'], 'https://gls-group.com/PL/pl/sledzenie-paczek?match='],
|
||||||
|
[['allegro'], self::TRACKING_ALLEGRO_URL],
|
||||||
|
];
|
||||||
|
|
||||||
|
public static function trackingUrl(string $provider, string $trackingNumber, string $carrierId = ''): ?string
|
||||||
|
{
|
||||||
|
$number = trim($trackingNumber);
|
||||||
|
if ($number === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$encoded = rawurlencode($number);
|
||||||
|
$providerUrl = self::providerTrackingUrl($provider, $encoded);
|
||||||
|
if ($provider === 'inpost') {
|
||||||
|
return $providerUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::carrierTrackingUrl($encoded, $carrierId) ?? $providerUrl ?? self::fallbackTrackingUrl($encoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function carrierTrackingUrl(string $encoded, string $carrierId): ?string
|
||||||
|
{
|
||||||
|
$carrier = strtolower(trim($carrierId));
|
||||||
|
return $carrier === '' ? null : self::matchCarrierByName($encoded, $carrier);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function providerTrackingUrl(string $provider, string $encoded): ?string
|
||||||
|
{
|
||||||
|
$baseUrl = self::PROVIDER_TRACKING_URLS[$provider] ?? null;
|
||||||
|
return $baseUrl === null ? null : $baseUrl . $encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function fallbackTrackingUrl(string $encoded): string
|
||||||
|
{
|
||||||
|
return 'https://www.google.com/search?q=' . $encoded . '+sledzenie+przesylki';
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function matchCarrierByName(string $encoded, string $carrier): ?string
|
||||||
|
{
|
||||||
|
foreach (self::CARRIER_TRACKING_URLS as [$patterns, $baseUrl]) {
|
||||||
|
if (self::carrierMatches($carrier, $patterns)) {
|
||||||
|
return $baseUrl . $encoded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, string> $patterns
|
||||||
|
*/
|
||||||
|
private static function carrierMatches(string $carrier, array $patterns): bool
|
||||||
|
{
|
||||||
|
foreach ($patterns as $pattern) {
|
||||||
|
if ($carrier === $pattern || str_contains($carrier, $pattern)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user