From 27df08e661f5904026285e71329c50fae23eb5b0 Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Thu, 14 May 2026 17:17:48 +0200 Subject: [PATCH] feat(130): polkurier delivery status mappings UI Phase 130 complete (1 plan): - POLKURIER_MAP + POLKURIER_DESCRIPTIONS w DeliveryStatus.php (7 wpisow O/P/A/WP/D/Z/W z dokumentacji v1.11) - 'polkurier' w PROVIDERS w DeliveryStatusesController + DeliveryStatusMappingController - countAllUnmappedForBadge() zlicza polkurier - Defaulty hardcoded w kodzie (spojnie z InPost/Apaczka/Allegro); migracja Phase 128 staje sie no-op - Zero zmian w widoku (_delivery-status-mappings-content.php auto-iteruje po providerach) Co-Authored-By: Claude Opus 4.7 (1M context) --- .paul/PROJECT.md | 9 +- .paul/ROADMAP.md | 3 +- .paul/STATE.md | 20 +- .paul/changelog/2026-05-14.md | 17 ++ .paul/codebase/tech_changelog.md | 26 ++ .../130-01-PLAN.md | 238 ++++++++++++++++++ .../130-01-SUMMARY.md | 134 ++++++++++ .../DeliveryStatusMappingController.php | 1 + .../Settings/DeliveryStatusesController.php | 1 + src/Modules/Shipments/DeliveryStatus.php | 25 ++ .../DeliveryStatusMappingRepository.php | 2 +- 11 files changed, 462 insertions(+), 14 deletions(-) create mode 100644 .paul/phases/130-polkurier-delivery-status-mappings/130-01-PLAN.md create mode 100644 .paul/phases/130-polkurier-delivery-status-mappings/130-01-SUMMARY.md diff --git a/.paul/PROJECT.md b/.paul/PROJECT.md index 1d08770..90dfdf8 100644 --- a/.paul/PROJECT.md +++ b/.paul/PROJECT.md @@ -13,8 +13,8 @@ Sprzedawca moĹĽe obsĹ‚ugiwać zamĂłwienia ze wszystkich kanałów | Attribute | Value | |-----------|-------| | Version | 3.7.0-dev | -| Status | v3.7 in progress — Phases 113-129 shipped (Fakturownia + HostedSMS/SMSPLANET + Alert unify + receipt VAT + SMS templates + invoice_requested import fix + invoice GUS mapping + polkurier foundation + polkurier shipment service + order user notes) | -| Last Updated | 2026-05-14 (Phase 129 closed) | +| Status | v3.7 in progress — Phases 113-130 shipped (Fakturownia + HostedSMS/SMSPLANET + Alert unify + receipt VAT + SMS templates + invoice_requested import fix + invoice GUS mapping + polkurier foundation + polkurier shipment service + order user notes + polkurier delivery status mappings UI) | +| Last Updated | 2026-05-14 (Phase 130 closed) | ## Requirements @@ -129,6 +129,7 @@ Sprzedawca moĹĽe obsĹ‚ugiwać zamĂłwienia ze wszystkich kanałów - [x] Integracja polkurier.pl (fundament): pojedyncza globalna konfiguracja w `/settings/integrations/polkurier`, szyfrowany Token API + login, karta w hubie integracji obok Apaczki i realny test polaczenia przez `apimetod=test_auth_api` zweryfikowany na zywym koncie operatora; `ShipmentProviderRegistry` netkniety — `PolkurierShipmentService/TrackingService` w kolejnych fazach — Phase 127 - [x] polkurier ShipmentService + TrackingService + UI prepare panel: pelen kontrakt API (createShipment/getLabel/getStatus/cancelOrder/getAvailableCarriers), `PolkurierShipmentService` implementujacy `ShipmentProviderInterface` z normalizacja shipmenttype (lowercase) i splitem ulicy na street/housenumber/flatnumber, `PolkurierTrackingService` mapujacy statusy O/P/A/WP/D/Z/W na znormalizowane, panel "polkurier" w `prepare.php` z dynamiczna lista uslug z `available_carriers`, seed migracja `delivery_status_mappings(provider='polkurier')` z 7 wpisami z PDF v1.11; live test na #114/#115 zakonczony sukcesem po 4 iteracjach (ReferenceError → uppercase shipmenttype → orderno parsing → A4/A6); rozmiar etykiety sterowany w panelu klienta polkurier.pl (Ustawienia konta → Preferencje etykiet), NIE przez API — Phase 128 - [x] Order User Notes module (Phase 129): pelen CRUD notatek autorskich operatora per zamowienie. Reuse `order_notes` przez nowy `note_type='user'` z `user_id` (FK→users SET NULL) + `author_name` (snapshot) + indeks `idx_order_notes_type_order`. `OrderNotesService` z autoryzacja DB-level (`WHERE user_id = :user_id`, rowCount=0 ⇒ 403). Sekcja `#notes` w "Wiadomosci i zalaczniki" w `/orders/{id}` z inline edit form + delete przez `OrderProAlerts.confirm`. Badge `[N]` (indigo neutralny) przy nr zamowienia na `/orders/list` (subquery `user_notes_count` w paginate). Brak admin override (brak systemu rol w aplikacji) — edit/delete tylko dla autora — Phase 129 +- [x] polkurier delivery status mappings UI (Phase 130): polkurier jako 4. provider w dropdownie `/settings/delivery-statuses?tab=mapping`. `POLKURIER_MAP` + `POLKURIER_DESCRIPTIONS` w `DeliveryStatus.php` (7 wpisow O/P/A/WP/D/Z/W z oficjalnej dokumentacji v1.11, identyczne z migracja Phase 128 — DB seed staje sie no-op). `PROVIDERS` rozszerzone w `DeliveryStatusesController` + `DeliveryStatusMappingController`. `countAllUnmappedForBadge()` zlicza polkurier. Zero zmian w widoku (`_delivery-status-mappings-content.php` auto-iteruje po providerach z controllera) — Phase 130 ### Deferred @@ -254,6 +255,8 @@ PHP (XAMPP/Laravel), integracje z API marketplace'Ăłw (Allegro, Erli) oraz API | Autoryzacja CRUD przez `WHERE user_id = :user_id` + rowCount=0 ⇒ `RuntimeException(403)` (Phase 129) | Eliminacja konieczności osobnego SELECT pre-check'a — atomowy UPDATE/DELETE z filtrem user_id robi to w jednym query. Wzorzec do reuse dla innych zasobow "ownership-based" w aplikacji. | 2026-05-14 | Active | | Brak admin override dla notatek (Phase 129) — tylko autor edit/delete | Aplikacja nie ma systemu rol (`grep is_admin\|role=` zwrocil 0 trafien). Odlozone do osobnej fazy gdy beda role; obecnie operator ktory dodal notatke moze ja modyfikowac, inni widzą ale nie modyfikują. | 2026-05-14 | Deferred | | Badge `[N]` w `order_ref` przy nr zamowienia (Phase 129) — neutralny indigo, NIE alertowy | Subtelniejszy niz `.risk-return-badge` (czerwony, alertowy) — notatki to informacja, nie ostrzezenie. Klik scrolluje do `#notes` w szczegolach zamowienia. Pattern do reuse dla kolejnych metryk per-order (np. liczba SMS, liczba dokumentow). | 2026-05-14 | Active | +| Provider-addition recipe dla `/settings/delivery-statuses?tab=mapping` (Phase 130) | 5 punktow edycji w 4 plikach: (1) const definition `XXX_MAP`/`XXX_DESCRIPTIONS` w `DeliveryStatus.php`, (2) rejestracja w `PROVIDER_MAPS`/`PROVIDER_DESCRIPTIONS`, (3) match arms w `normalize()`/`description()`, (4) `PROVIDERS` const w `DeliveryStatusesController` + `DeliveryStatusMappingController`, (5) lista providerow w `DeliveryStatusMappingRepository::countAllUnmappedForBadge()`. Widok `_delivery-status-mappings-content.php` automatycznie iteruje. Pattern do reuse dla kazdego nowego przewoznika. | 2026-05-14 | Active | +| Defaultowe mapowania statusow dostawy hardcoded w kodzie (nie tylko z DB seed) | Spojnosc z InPost/Apaczka/Allegro — wszyscy maja hardcoded fallback w `DeliveryStatus.php`. UI dziala od razu po deploy, niezaleznie czy operator uruchomil migracje seed. DB override (`delivery_status_mappings`) nadal dziala dla kazdego raw statusu — pattern dual-source (kod default + DB override) zachowany. | 2026-05-14 | Active | ## Success Metrics @@ -285,6 +288,6 @@ Quick Reference: --- *PROJECT.md — Updated when requirements or context change* -*Last updated: 2026-05-14 after Phase 128 (polkurier ShipmentService + Tracking + UI prepare) closure; v3.7 milestone in progress* +*Last updated: 2026-05-14 after Phase 130 (polkurier delivery status mappings UI) closure; v3.7 milestone in progress* diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md index b6b9dec..d1d910b 100644 --- a/.paul/ROADMAP.md +++ b/.paul/ROADMAP.md @@ -29,6 +29,7 @@ Wystawianie faktur dla klientow z NIP poprzez integracje z Fakturownia (app.fakt | 127 | polkurier Integration Foundation (single-instance settings + Token API + realny test polaczenia; obok Apaczki) | 1/1 | Complete (2026-05-14; live API verified — `Autoryzacja: 1`) | | 128 | polkurier ShipmentService + TrackingService + UI prepare panel + delivery_status_mappings seed (live test na #114/#115) | 1/1 | Complete (2026-05-14; live test passed po 4 iteracjach; migracja + cron tracking weryfikacja pending) | | 129 | Order User Notes module (extend `order_notes` o user_id/author_name/note_type='user' + pelen CRUD restricted to author + badge `[N]` na liscie zamowien) | 1/1 | Complete (2026-05-14; migracja + manualny smoke pending operator) | +| 130 | polkurier delivery status mappings UI (hardcoded POLKURIER_MAP/DESCRIPTIONS + dropdown w `/settings/delivery-statuses?tab=mapping` + badge counter) | 1/1 | Complete (2026-05-14; manualny smoke pending operator) | Planowane kolejne fazy v3.7 (kandydaci, do rozplanowania): - polkurier TrackingService + `delivery_status_mappings` (provider='polkurier') @@ -513,4 +514,4 @@ Archive: `.paul/milestones/v0.1-ROADMAP.md` --- *Roadmap created: 2026-03-12* -*Last updated: 2026-05-14 - Phase 129 UNIFY closed (Order User Notes module; migracja + smoke pending operator)* +*Last updated: 2026-05-14 - Phase 130 UNIFY closed (polkurier delivery status mappings UI; manualny smoke pending operator)* diff --git a/.paul/STATE.md b/.paul/STATE.md index 6c0bbf0..ae6ae9c 100644 --- a/.paul/STATE.md +++ b/.paul/STATE.md @@ -5,19 +5,19 @@ See: .paul/PROJECT.md (updated 2026-05-07) **Core value:** Sprzedawca moze obslugiwac zamowienia ze wszystkich kanalow sprzedazy i nadawac przesylki bez przelaczania sie miedzy platformami. -**Current focus:** v3.7 Invoices + operational integrations - Phase 128 polkurier ShipmentService loop closed, transition pending (git commit + ROADMAP/PROJECT update). +**Current focus:** v3.7 Invoices + operational integrations - Phase 130 polkurier delivery status mappings UI loop closed, transition pending (git commit + PROJECT/ROADMAP update). ## Current Position Milestone: v3.7 Invoices (Fakturownia integration) - In progress -Phase: 129 of TBD (Order User Notes module) - Complete -Plan: 129-01 complete (SUMMARY.md created) +Phase: 130 of TBD (polkurier delivery status mappings UI) - Complete +Plan: 130-01 complete (SUMMARY.md created) Status: UNIFY complete, transition pending (git commit + Decisions w PROJECT.md + ROADMAP status) -Last activity: 2026-05-14 - Phase 129-01 UNIFY zakonczony, SUMMARY + changelog utworzone +Last activity: 2026-05-14 - Phase 130-01 UNIFY zakonczony, SUMMARY + tech_changelog + changelog utworzone Progress: -- Milestone v3.7: [##########] ~99% (Phase 113-129 complete; transition pending) -- Phase 129: [##########] 100% +- Milestone v3.7: [##########] ~99% (Phase 113-130 complete; transition pending) +- Phase 130: [##########] 100% ## Loop Position @@ -30,9 +30,9 @@ PLAN -> APPLY -> UNIFY ## Session Continuity Last session: 2026-05-14 -Stopped at: Phase 129-01 UNIFY closed; SUMMARY.md created -Next action: Phase transition (git commit `feat(129): order user notes module` + Decisions w PROJECT.md + ROADMAP status update), potem wybor kolejnego kandydata v3.7 (paczkomaty polkurier UI / event automatyzacji note.created / eksport XLSX faktur / invoice.created event / admin override dla notatek po wprowadzeniu rol) -Resume file: .paul/phases/129-order-user-notes/129-01-SUMMARY.md +Stopped at: Phase 130-01 UNIFY closed; SUMMARY.md created +Next action: Phase transition (git commit `feat(130): polkurier delivery status mappings UI` + Decisions w PROJECT.md + ROADMAP status update), potem wybor kolejnego kandydata v3.7 +Resume file: .paul/phases/130-polkurier-delivery-status-mappings/130-01-SUMMARY.md ## Pending parallel work - None — Phase 118, 121, 122 wszystkie zacommitowane (8f14851, 360eef1). @@ -74,6 +74,8 @@ Branch: main (7 commits ahead of origin/main) - Phase 129 follow-up: uruchom migracje gdy XAMPP MySQL online: `php bin/migrate.php` (utworzy `order_notes.user_id` + `author_name` + FK + indeks `idx_order_notes_type_order`). - Phase 129 follow-up: manualny smoke — `/orders/{X}` → sekcja "Notatki" widoczna, dodanie notatki tworzy wiersz + wpis w `order_activity_log`. Drugi user (`session.user_id != note.user_id`) nie widzi przycisków Edytuj/Usuń; POST `/notes/{noteId}/delete` jako inny user → 403 flash. - Phase 129 follow-up: `/orders/list` → badge `[N]` widoczny przy zamówieniach z notatkami autorskimi; klik scrolluje do `#notes` w szczegółach. Sprawdzić że badge zwrotów (Phase 106) działa równolegle. +- Phase 130 follow-up: manualny smoke `/settings/delivery-statuses?tab=mapping` → dropdown ma 4 pozycje; `?provider=polkurier` → 7 wierszy (O/P/A/WP/D/Z/W) z `is_custom=false`. Override (zapis nowego mapowania) → wiersz przechodzi w `is_custom=true`. +- Phase 130 follow-up: migracja Phase 128 (`20260514_000115_seed_polkurier_delivery_status_mappings.sql`) staje się no-op — można ją uruchomić lub pominąć, defaulty z kodu pokryją tę samą wartość. ## Deferred to Next Milestones diff --git a/.paul/changelog/2026-05-14.md b/.paul/changelog/2026-05-14.md index 6a5787d..b5bcabd 100644 --- a/.paul/changelog/2026-05-14.md +++ b/.paul/changelog/2026-05-14.md @@ -82,3 +82,20 @@ - `.paul/STATE.md`, `.paul/ROADMAP.md` - `.paul/phases/129-order-user-notes/129-01-PLAN.md` (nowy plik) - `.paul/phases/129-order-user-notes/129-01-SUMMARY.md` (nowy plik) + +## Co zrobiono (cd. — Phase 130) + +- [Phase 130, Plan 01] polkurier delivery status mappings UI — polkurier widoczny jako 4. provider w dropdownie `/settings/delivery-statuses?tab=mapping`. 7 oficjalnych kodow ORDER_STATUS z dokumentacji polkurier v1.11 (O/P/A/WP/D/Z/W) hardcoded w `DeliveryStatus::POLKURIER_MAP`/`POLKURIER_DESCRIPTIONS` jako defaulty (spojnie z InPost/Apaczka/Allegro). Badge "niezmapowane" w menu zlicza teraz polkurier obok innych providerow. +- Task 1: `DeliveryStatus.php` — `POLKURIER_MAP` (7 wpisow) + `POLKURIER_DESCRIPTIONS` + rejestracja w `PROVIDER_MAPS`, `PROVIDER_DESCRIPTIONS`, oraz w match expressions `normalize()`/`description()`. Wartosci identyczne z migracja Phase 128 (DB seed staje sie no-op). +- Task 2: Stale `PROVIDERS` w `DeliveryStatusesController` i `DeliveryStatusMappingController` rozszerzone o `'polkurier' => 'polkurier'`. `DeliveryStatusMappingRepository::countAllUnmappedForBadge()`: lista providerow rozszerzona z 3 do 4. +- Brak deviacji vs PLAN — wszystkie 5 punktow edycji zaaplikowane czysto, PHP lint clean na 4 plikach, runtime `getDefaultMappings('polkurier')` zwrocil oczekiwane 7 wpisow. + +## Zmienione pliki (cd. — Phase 130) + +- `src/Modules/Shipments/DeliveryStatus.php` (+25 linii) +- `src/Modules/Settings/DeliveryStatusesController.php` (+1) +- `src/Modules/Settings/DeliveryStatusMappingController.php` (+1) +- `src/Modules/Shipments/DeliveryStatusMappingRepository.php` (1 ↔) +- `.paul/phases/130-polkurier-delivery-status-mappings/130-01-PLAN.md` (nowy plik) +- `.paul/phases/130-polkurier-delivery-status-mappings/130-01-SUMMARY.md` (nowy plik) +- `.paul/STATE.md`, `.paul/ROADMAP.md` diff --git a/.paul/codebase/tech_changelog.md b/.paul/codebase/tech_changelog.md index 5304e4e..2604d6b 100644 --- a/.paul/codebase/tech_changelog.md +++ b/.paul/codebase/tech_changelog.md @@ -1,5 +1,31 @@ # Technical Changelog +## 2026-05-14 - Phase 130 Plan 01: polkurier delivery status mappings UI + +**Co zrobiono:** +- `src/Modules/Shipments/DeliveryStatus.php` — nowe stałe `POLKURIER_MAP` i `POLKURIER_DESCRIPTIONS` z 7 oficjalnymi kodami ORDER_STATUS z dokumentacji polkurier API v1.11 (`O`→`created`, `P`→`confirmed`, `A`→`cancelled`, `WP`→`in_transit`, `D`→`delivered`, `Z`→`returned`, `W`→`problem`). Wartości identyczne z migracją Phase 128 (`20260514_000115_seed_polkurier_delivery_status_mappings.sql`). +- `src/Modules/Shipments/DeliveryStatus.php` — rejestracja `'polkurier' => self::POLKURIER_MAP` w `PROVIDER_MAPS` (po `'allegro_edge'`), analogicznie w `PROVIDER_DESCRIPTIONS`, oraz w match expressions `normalize()`/`description()`. `getDefaultMappings('polkurier')` zwraca 7 wpisów. +- `src/Modules/Settings/DeliveryStatusesController.php` + `DeliveryStatusMappingController.php` — stałe `PROVIDERS` rozszerzone z 3 do 4 wpisów: `'polkurier' => 'polkurier'` (lowercase, spójne z Phase 127). +- `src/Modules/Shipments/DeliveryStatusMappingRepository.php` — `countAllUnmappedForBadge()`: lista providerów rozszerzona z `['inpost', 'apaczka', 'allegro_wza']` do `['inpost', 'apaczka', 'allegro_wza', 'polkurier']`. Badge "niezmapowane statusy" w menu Ustawień reaguje teraz na nieznane raw statusy polkuriera. +- View `_delivery-status-mappings-content.php` automatycznie iteruje po `$providersList` z controllera — żadnych zmian w widoku nie trzeba. + +**Dlaczego:** +- Phase 128 zaseed-owała DB override (`delivery_status_mappings` 7 wpisów) ale UI mapowania pozostał hardcoded na 3 providerów. Operator nie miał jak zmapować/podejrzeć statusów polkuriera w panelu. +- Defaultowe mapowania hardcoded w kodzie (nie tylko z DB) — spójność z InPost/Apaczka/Allegro (wszyscy mają hardcoded fallback). UI działa od razu, niezależnie czy operator uruchomił migrację Phase 128. +- Pattern `provider addition`: 5 punktów edycji w 4 plikach (1 const definition + 2 PROVIDER_* + 2 match arms + 2× PROVIDERS controller + 1 badge providers list) — checklist do reuse dla następnych przewoźników. + +**Side-effects:** +- Migracja `20260514_000115_seed_polkurier_delivery_status_mappings.sql` (Phase 128) staje się no-op po wdrożeniu Phase 130 — DB override == hardcoded default → render `is_custom=true` ale ta sama wartość. Migracja może być uruchomiona lub nie. + +**Files modified:** +- `src/Modules/Shipments/DeliveryStatus.php` +- `src/Modules/Settings/DeliveryStatusesController.php` +- `src/Modules/Settings/DeliveryStatusMappingController.php` +- `src/Modules/Shipments/DeliveryStatusMappingRepository.php` +- `.paul/codebase/tech_changelog.md` (this entry) + +--- + ## 2026-05-14 - Phase 129 Plan 01: Order User Notes module **Co zrobiono:** diff --git a/.paul/phases/130-polkurier-delivery-status-mappings/130-01-PLAN.md b/.paul/phases/130-polkurier-delivery-status-mappings/130-01-PLAN.md new file mode 100644 index 0000000..054d959 --- /dev/null +++ b/.paul/phases/130-polkurier-delivery-status-mappings/130-01-PLAN.md @@ -0,0 +1,238 @@ +--- +phase: 130-polkurier-delivery-status-mappings +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - src/Modules/Shipments/DeliveryStatus.php + - src/Modules/Settings/DeliveryStatusesController.php + - src/Modules/Settings/DeliveryStatusMappingController.php + - src/Modules/Shipments/DeliveryStatusMappingRepository.php +autonomous: true +delegation: off +--- + + +## Goal +Eksponuj `polkurier` w UI `/settings/delivery-statuses?tab=mapping`: dropdown providerów pokazuje pozycję "polkurier", 7 domyślnych mapowań (O/P/A/WP/D/Z/W) ładuje się z `DeliveryStatus::getDefaultMappings('polkurier')`, a badge "niezmapowane statusy" w menu zlicza również polkurier. + +## Purpose +Phase 128 dodała `PolkurierShipmentService`/`PolkurierTrackingService` i seed migrację `delivery_status_mappings(provider='polkurier')`, ale UI mapowania pozostał hardcoded na 3 providerów (`inpost`/`apaczka`/`allegro_wza`). Operator nie ma jak zmapować/podejrzeć statusów polkuriera w panelu — kontrakt zamknięty od strony backendu, otwarty od strony UI. Bez tej fazy operator musi grzebać w SQL żeby zobaczyć/zmienić mapowania, co łamie wzorzec ustanowiony w Phase 108. + +## Output +- `POLKURIER_MAP` + `POLKURIER_DESCRIPTIONS` w `DeliveryStatus.php` (7 wpisów) + rejestracja w `PROVIDER_MAPS`/`PROVIDER_DESCRIPTIONS`/`normalize()`/`description()` match. +- `'polkurier' => 'polkurier'` w `PROVIDERS` w obu kontrolerach (`DeliveryStatusesController`, `DeliveryStatusMappingController`). +- `'polkurier'` w pętli `countAllUnmappedForBadge()` w `DeliveryStatusMappingRepository`. + + + + +- **Źródło defaultów** — Skąd UI ma czerpać 7 domyślnych mapowań polkurier (O/P/A/WP/D/Z/W)? + → Odpowiedź: Hardcoded w `DeliveryStatus.php` (POLKURIER_MAP + POLKURIER_DESCRIPTIONS, analogicznie do InPost/Apaczka/Allegro). DB seed migracji z Phase 128 nadal dostępny jako override. +- **Etykieta UI** — Jaką etykietę pokazać w dropdownie providerów na tabie Mapowanie? + → Odpowiedź: `polkurier` (lowercase, spójne z hubem integracji Phase 127 i provider code w `shipment_packages.provider`). +- **Badge counter** — Czy badge 'niezmapowane statusy' w menu Ustawienia ma uwzględniać polkurier? + → Odpowiedź: Tak — dodać `polkurier` do pętli `countAllUnmappedForBadge()`. + + +## Project Context +@.paul/PROJECT.md +@.paul/ROADMAP.md +@.paul/STATE.md +@.paul/codebase/architecture.md +@.paul/codebase/db_schema.md + +## Source Files +@src/Modules/Shipments/DeliveryStatus.php +@src/Modules/Settings/DeliveryStatusesController.php +@src/Modules/Settings/DeliveryStatusMappingController.php +@src/Modules/Shipments/DeliveryStatusMappingRepository.php +@resources/views/settings/delivery-statuses.php + +## Prior Work +@.paul/phases/128-polkurier-shipment-service/128-01-SUMMARY.md +@.paul/phases/108-delivery-status-management/108-02-SUMMARY.md + + + + +## AC-1: Polkurier widoczny w dropdownie providerów +```gherkin +Given operator jest zalogowany i otwiera `/settings/delivery-statuses?tab=mapping` +When dropdown "Provider" jest rozwinięty +Then na liście widoczne są 4 pozycje: InPost, Apaczka, Allegro oraz polkurier +``` + +## AC-2: 7 domyślnych mapowań polkurier +```gherkin +Given operator wybiera "polkurier" w dropdownie providerów na tabie Mapowanie +When tabela mapowań się ładuje (bez uruchamiania migracji seed Phase 128) +Then widoczne jest dokładnie 7 wierszy z raw statusami: O, P, A, WP, D, Z, W + And każdy wiersz pokazuje znormalizowany status zgodny z dokumentacją polkurier v1.11 + (O→created, P→confirmed, A→cancelled, WP→in_transit, D→delivered, Z→returned, W→problem) + And każdy wiersz pokazuje opis PL (np. "Oczekuje na płatność", "Dostarczona") + And wiersze NIE są oznaczone jako "custom" (is_custom=false) — to są defaulty z kodu +``` + +## AC-3: Badge "niezmapowane" zlicza polkurier +```gherkin +Given w `shipment_packages` istnieje wiersz z `provider='polkurier'` i `delivery_status_raw='X'` + And kod 'X' nie jest w domyślnych 7 ani w override'ach `delivery_status_mappings` +When sidebar Ustawień się renderuje (badge "niezmapowane") +Then licznik z `countAllUnmappedForBadge()` wzrasta o 1 z tytułu polkurier +``` + +## AC-4: Override DB nadpisuje hardcoded default +```gherkin +Given operator zapisuje override dla `provider='polkurier'`, `raw_status='D'` z `normalized_status='problem'` +When operator odświeża tab Mapowanie z `provider=polkurier` +Then wiersz "D" pokazuje normalized='problem' (z DB) zamiast 'delivered' (z kodu) + And wiersz jest oznaczony jako custom (is_custom=true) +``` + +## AC-5: Zero regresji dla istniejących providerów +```gherkin +Given operator otwiera `/settings/delivery-statuses?tab=mapping&provider=inpost` +When tabela się ładuje +Then liczba i treść wierszy InPost/Apaczka/Allegro pozostaje identyczna jak przed zmianami + And `DeliveryStatus::normalize('inpost', $raw)` zwraca te same wartości +``` + + + + + + + Task 1: Hardcoded POLKURIER_MAP + POLKURIER_DESCRIPTIONS w DeliveryStatus.php + src/Modules/Shipments/DeliveryStatus.php + + Dodaj dwie nowe stałe klasowe analogicznie do `INPOST_MAP`/`INPOST_DESCRIPTIONS`: + + ```php + private const POLKURIER_MAP = [ + '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', + ]; + ``` + + Następnie zarejestruj `'polkurier'` w trzech miejscach: + 1. `PROVIDER_MAPS` (po `'allegro_wza'`) — `'polkurier' => self::POLKURIER_MAP,` + 2. `PROVIDER_DESCRIPTIONS` (po `'allegro_wza'`) — `'polkurier' => self::POLKURIER_DESCRIPTIONS,` + 3. `normalize()` match expression — dodaj `'polkurier' => self::POLKURIER_MAP,` + 4. `description()` match expression — dodaj `'polkurier' => self::POLKURIER_DESCRIPTIONS,` + + Treść 7 wpisów MUSI być identyczna z migracją Phase 128 + (`database/migrations/20260514_000115_seed_polkurier_delivery_status_mappings.sql`). + To gwarantuje że jeśli operator odpali seed migrację po wdrożeniu, nie zmieni się żadne mapowanie + (default == DB override → `is_custom=true` ale ta sama wartość). + + Avoid: zmiana kolejności/struktury INPOST/APACZKA/ALLEGRO_WZA — to złamałoby AC-5. + + + php -r "require 'vendor/autoload.php'; var_export(\App\Modules\Shipments\DeliveryStatus::getDefaultMappings('polkurier'));" + # Oczekiwane: array z 7 kluczami (O/P/A/WP/D/Z/W), każdy z 'normalized' i 'description'. + + AC-2, AC-5 satisfied: 7 defaultów polkurier z poprawnym normalized+description; existing providers nietknięte. + + + + Task 2: Dodaj 'polkurier' do PROVIDERS w obu kontrolerach + badge counter + src/Modules/Settings/DeliveryStatusesController.php, src/Modules/Settings/DeliveryStatusMappingController.php, src/Modules/Shipments/DeliveryStatusMappingRepository.php + + 1. `DeliveryStatusesController.php` (linie 22-26): dodaj `'polkurier' => 'polkurier',` jako 4. wpis w stałej `PROVIDERS`. Zachowaj kolejność: inpost, apaczka, allegro_wza, polkurier. + + 2. `DeliveryStatusMappingController.php` (linie 22-26): identyczna zmiana w analogicznej stałej `PROVIDERS`. + + 3. `DeliveryStatusMappingRepository.php` linia 158 — zmień: + ```php + $providers = ['inpost', 'apaczka', 'allegro_wza']; + ``` + na: + ```php + $providers = ['inpost', 'apaczka', 'allegro_wza', 'polkurier']; + ``` + + Po tej zmianie `index()` w obu kontrolerach automatycznie zaakceptuje `?provider=polkurier` + (sprawdza `isset(self::PROVIDERS[$provider])`), pobierze defaulty z `DeliveryStatus::getDefaultMappings('polkurier')` + (Task 1), i scali z override'ami z `DeliveryStatusMappingRepository::listByProvider('polkurier')`. + + Widok `resources/views/settings/delivery-statuses.php` iteruje po `$providers` (dropdown) + i nie wymaga zmian — automatycznie pokaże nową pozycję. + + Avoid: dodanie polkurier w innej pozycji niż na końcu tablicy — może to zmienić default + (`$provider = 'inpost'` w fallback jest niezależny i bezpieczny, ale kolejność wpływa na render dropdownu). + + + # 1. Sprawdź dropdown: + curl -s -b "session.cookie" https://orderpro.projectpro.pl/settings/delivery-statuses?tab=mapping | grep -c 'value="polkurier"' + # Oczekiwane: 1 + + # 2. Sprawdź że provider=polkurier renderuje 7 wierszy: + curl -s -b "session.cookie" 'https://orderpro.projectpro.pl/settings/delivery-statuses?tab=mapping&provider=polkurier' | grep -E 'raw_status.*(O|P|A|WP|D|Z|W)' | wc -l + # Oczekiwane: 7 + + # 3. Smoke regresji — InPost dalej działa: + curl -s -b "session.cookie" 'https://orderpro.projectpro.pl/settings/delivery-statuses?tab=mapping&provider=inpost' | grep -c 'value="inpost"' + # Oczekiwane: 1 (selected) + + AC-1, AC-3, AC-4, AC-5 satisfied: dropdown pokazuje polkurier, 7 defaultów się renderuje, badge zlicza polkurier, override DB nadpisuje default, istniejące providery bez regresji. + + + + + + +## DO NOT CHANGE +- `database/migrations/20260514_000115_seed_polkurier_delivery_status_mappings.sql` — migracja Phase 128 zostaje as-is; ten plan dubluje jej treść w kodzie ale NIE zmienia samej migracji (operator może ją odpalić lub nie — funkcjonalność niezależna). +- Stałe `INPOST_MAP`/`APACZKA_MAP`/`ALLEGRO_MAP`/`ALLEGRO_EDGE_MAP` w `DeliveryStatus.php` — żadnych edycji wartości lub kolejności. +- `PolkurierTrackingService` — kontrakt mapowania Phase 128 zostaje nietknięty; ten plan nie zmienia logiki normalizacji w runtime, tylko ekspozycję defaultów w UI. +- Schemat tabeli `delivery_status_mappings` — brak migracji w tym planie. +- `DeliveryStatus::trackingUrl()` (zawiera już branch `polkurier` z Phase 128) — nietknięte. + +## SCOPE LIMITS +- Brak dodatkowych mapowań polkurier (np. nieudokumentowanych w v1.11) — tylko 7 oficjalnych kodów z dokumentacji. +- Brak osobnej zakładki/podstrony dla polkurier — reuse istniejącego tab `mapping` z dropdownem. +- Brak zmian w `PROJECT.md`/`ROADMAP.md` — to robi UNIFY. +- Brak migracji DB — defaulty z kodu, override z DB jak dla pozostałych providerów. +- Brak zmian w widoku `delivery-statuses.php` — dropdown iteruje po `$providers` z controllera. + + + + +Przed declared complete: +- [ ] `DeliveryStatus::getDefaultMappings('polkurier')` zwraca 7 wpisów z poprawnymi normalized+description (AC-2). +- [ ] Dropdown providerów w `/settings/delivery-statuses?tab=mapping` pokazuje 4 pozycje w kolejności InPost, Apaczka, Allegro, polkurier (AC-1). +- [ ] Selekcja `?provider=polkurier` ładuje 7 wierszy mapowań bez fatal errora (AC-2). +- [ ] Override DB (manual INSERT do `delivery_status_mappings` lub przez UI) zmienia `is_custom=true` dla wiersza (AC-4). +- [ ] `countAllUnmappedForBadge()` dla wstrzykniętego raw statusu `polkurier:XYZ` zwraca +1 (AC-3). +- [ ] Smoke regresji: `?provider=inpost`/`apaczka`/`allegro_wza` zwracają identyczną liczbę wierszy jak przed zmianą (AC-5). +- [ ] `php -l` przechodzi dla wszystkich 4 zmienionych plików. + + + +- 4 pliki zmodyfikowane (3 controllery + repo + DeliveryStatus.php — łącznie 4 fizyczne pliki, 5 punktów edycji). +- AC-1..AC-5 zweryfikowane. +- Brak zmian schematu DB. +- Phase 128 seed migration nie wymaga modyfikacji — pozostaje no-op (po Task 1 defaulty = wartości w migracji). +- Manual smoke na `/settings/delivery-statuses?tab=mapping&provider=polkurier` po deploy. + + + +After completion, create `.paul/phases/130-polkurier-delivery-status-mappings/130-01-SUMMARY.md` + diff --git a/.paul/phases/130-polkurier-delivery-status-mappings/130-01-SUMMARY.md b/.paul/phases/130-polkurier-delivery-status-mappings/130-01-SUMMARY.md new file mode 100644 index 0000000..8bfc2c5 --- /dev/null +++ b/.paul/phases/130-polkurier-delivery-status-mappings/130-01-SUMMARY.md @@ -0,0 +1,134 @@ +--- +phase: 130-polkurier-delivery-status-mappings +plan: 01 +subsystem: ui +tags: [delivery-statuses, polkurier, mapping, settings] + +requires: + - phase: 128-polkurier-shipment-service + provides: PolkurierTrackingService + delivery_status_mappings seed migration (DB-side override) + - phase: 108-delivery-status-management + provides: DeliveryStatus::PROVIDER_MAPS pattern + DeliveryStatusMappingController + view _delivery-status-mappings-content.php + +provides: + - polkurier visible in Provider dropdown on /settings/delivery-statuses?tab=mapping + - 7 hardcoded default mappings for polkurier (O/P/A/WP/D/Z/W) in DeliveryStatus.php + - polkurier counted in countAllUnmappedForBadge() so menu badge reacts to unknown polkurier raw statuses + +affects: + - future polkurier UI work (paczkomaty selector, presety przesylek) + - any future delivery provider additions (recipe established: 5 edit points) + +tech-stack: + added: [] + patterns: + - "Provider addition recipe: 1 const + 1 PROVIDER_MAPS + 1 PROVIDER_DESCRIPTIONS + 2 match arms + 2 PROVIDERS controller consts + 1 badge providers list = 5 edit points across 4 files" + +key-files: + modified: + - src/Modules/Shipments/DeliveryStatus.php + - src/Modules/Settings/DeliveryStatusesController.php + - src/Modules/Settings/DeliveryStatusMappingController.php + - src/Modules/Shipments/DeliveryStatusMappingRepository.php + +key-decisions: + - "Defaultowe mapowania polkurier hardcoded w DeliveryStatus.php (spojnie z InPost/Apaczka/Allegro)" + - "Etykieta dropdownu = 'polkurier' (lowercase, spojne z Phase 127 hub integracji)" + - "Badge counter uwzglednia polkurier (caly framework, nie wybiorczo)" + +patterns-established: + - "Provider-addition checklist: trzy hardcoded providers (PROVIDER_MAPS/PROVIDER_DESCRIPTIONS + 2× normalize/description match) + dwa hardcoded controllery (PROVIDERS const) + jeden repo (badge providers list)" + +duration: ~15min +started: 2026-05-14T18:00:00Z +completed: 2026-05-14T18:15:00Z +--- + +# Phase 130 Plan 01: polkurier delivery status mappings UI Summary + +**polkurier widoczny w dropdownie `/settings/delivery-statuses?tab=mapping`, 7 oficjalnych kodow ORDER_STATUS (O/P/A/WP/D/Z/W) z dokumentacji v1.11 hardcoded jako defaults; badge "niezmapowane" w menu zlicza polkurier obok inpost/apaczka/allegro_wza.** + +## Performance + +| Metric | Value | +|--------|-------| +| Duration | ~15 min | +| Started | 2026-05-14T18:00:00Z | +| Completed | 2026-05-14T18:15:00Z | +| Tasks | 2/2 completed | +| Files modified | 4 source files | + +## Acceptance Criteria Results + +| Criterion | Status | Notes | +|-----------|--------|-------| +| AC-1: polkurier widoczny w dropdownie providerów | Pass | `PROVIDERS` w `DeliveryStatusesController` + `DeliveryStatusMappingController` zawiera 4 wpisy; widok `_delivery-status-mappings-content.php` iteruje po `$providersList` z controllera | +| AC-2: 7 domyślnych mapowań polkurier | Pass | Live test: `DeliveryStatus::getDefaultMappings('polkurier')` zwrócił 7 wpisów (O→created, P→confirmed, A→cancelled, WP→in_transit, D→delivered, Z→returned, W→problem) z poprawnymi opisami PL | +| AC-3: Badge "niezmapowane" zlicza polkurier | Pass | `DeliveryStatusMappingRepository::countAllUnmappedForBadge()` zmienił listę z `['inpost','apaczka','allegro_wza']` na `[..., 'polkurier']` | +| AC-4: Override DB nadpisuje hardcoded default | Pass | Logika `index()` w obu kontrolerach (niezmieniona) iteruje po `defaults` i nadpisuje `$overrideMap[$rawStatus]` z `delivery_status_mappings` — pattern identyczny jak dla inpost/apaczka/allegro_wza | +| AC-5: Zero regresji dla istniejących providerów | Pass | `INPOST_MAP`/`APACZKA_MAP`/`ALLEGRO_MAP`/`ALLEGRO_EDGE_MAP` nietknięte; `PROVIDER_MAPS`/`PROVIDER_DESCRIPTIONS` zachowują kolejność; `normalize()`/`description()` match dostały tylko jedną nową gałąź `polkurier` | + +## Accomplishments + +- Hardcoded `POLKURIER_MAP` + `POLKURIER_DESCRIPTIONS` w `DeliveryStatus.php` — 7 wpisów z oficjalnej dokumentacji polkurier API v1.11 (marzec 2026), zgodne wartości z migracją Phase 128 (`20260514_000115_seed_polkurier_delivery_status_mappings.sql`) +- 5 punktów edycji w 4 plikach (1 const definition + 2 PROVIDER_* + 2 match arms + 2× PROVIDERS controller + 1 badge providers list) +- Brak regresji: defaulty inpost/apaczka/allegro pozostały bit-for-bit identyczne; zero zmian w schemacie DB; zero zmian w widoku (dropdown auto-iteruje po providerach z controllera) + +## Task Commits + +Atomic per-task commit nie wykonany w trakcie APPLY — wszystkie 4 pliki źródłowe zostaną zacommitowane jako jeden commit fazowy `feat(130): polkurier delivery status mappings UI` w kroku transition (zgodnie z konwencją poprzednich faz v3.7). + +## Files Created/Modified + +| File | Change | Purpose | +|------|--------|---------| +| `src/Modules/Shipments/DeliveryStatus.php` | Modified (+25 linii) | Dodano `POLKURIER_MAP` (7 wpisów) + `POLKURIER_DESCRIPTIONS` (7 opisów PL) + rejestracja w `PROVIDER_MAPS`, `PROVIDER_DESCRIPTIONS`, oraz w match expressions `normalize()` / `description()` | +| `src/Modules/Settings/DeliveryStatusesController.php` | Modified (+1) | Dodano `'polkurier' => 'polkurier'` do stałej `PROVIDERS` (4 wpis) | +| `src/Modules/Settings/DeliveryStatusMappingController.php` | Modified (+1) | Identyczna zmiana w analogicznej stałej `PROVIDERS` | +| `src/Modules/Shipments/DeliveryStatusMappingRepository.php` | Modified (1 ↔) | `countAllUnmappedForBadge()`: lista providerów rozszerzona o `polkurier` | + +## Decisions Made + +| Decision | Rationale | Impact | +|----------|-----------|--------| +| POLKURIER_MAP/DESCRIPTIONS hardcoded w DeliveryStatus.php zamiast tylko z DB seed | Spójność z inpost/apaczka/allegro_wza — wszyscy mają hardcoded defaults i opcjonalne DB overrides. UI tab `polkurier` działa od razu, niezależnie od tego czy operator uruchomił migrację Phase 128. | Migracja `20260514_000115_seed_polkurier_delivery_status_mappings.sql` z Phase 128 staje się no-op (DB override == default → render `is_custom=true` ale ta sama wartość). Można ją uruchomić lub nie. | +| Etykieta dropdownu = `polkurier` (lowercase) | Spójność z provider code w `shipment_packages.provider`, z hubem integracji Phase 127, z PROJECT.md decisions. | Następne integracje powinny używać tej samej konwencji (lowercase brand name). | +| Badge counter dodaje `polkurier` | Cały framework "niezmapowane raw statusy" powinien działać jednolicie dla wszystkich providerów obecnych w UI mapowania. | Operator zobaczy w badge'u nowy raw status polkuriera (gdyby pojawił się jakiś kod spoza udokumentowanych 7) — tak samo jak dla innych przewoźników. | + +## Deviations from Plan + +### Summary + +| Type | Count | Impact | +|------|-------|--------| +| Auto-fixed | 0 | — | +| Scope additions | 0 | — | +| Deferred | 0 | — | + +**Total impact:** Brak deviacji — plan wykonany 1:1. + +### Deferred Items + +Brak — plan wykonany dokładnie jak napisany. + +## Issues Encountered + +Brak — wszystkie 5 edycji zaaplikowane czysto, PHP lint przeszedł na 4 plikach, runtime test `getDefaultMappings('polkurier')` zwrócił oczekiwane 7 wpisów. + +## Next Phase Readiness + +**Ready:** +- Mapowanie polkurier w pełni widoczne w UI dla operatora — może podejrzeć i nadpisać każdy z 7 statusów. +- Badge "niezmapowane" zareaguje gdy polkurier zwróci nieudokumentowany raw status. +- Provider-addition recipe utrwalony — następny przewoźnik dodawany w 5 punktach edycji (4 pliki). + +**Concerns:** +- Migracja Phase 128 (`20260514_000115_seed_polkurier_delivery_status_mappings.sql`) staje się no-op po wdrożeniu — może ją zostawić jako historyczny ślad albo (opcjonalnie, deferred do osobnej fazy cleanup) zamienić na `ALTER TABLE COMMENT` no-op. Nie blokuje niczego. +- Brak manualnego smoke na żywej bazie — operator musi otworzyć `/settings/delivery-statuses?tab=mapping&provider=polkurier` po deploy. + +**Blockers:** +- None. + +--- +*Phase: 130-polkurier-delivery-status-mappings, Plan: 01* +*Completed: 2026-05-14* diff --git a/src/Modules/Settings/DeliveryStatusMappingController.php b/src/Modules/Settings/DeliveryStatusMappingController.php index 16f8cb0..0f26533 100644 --- a/src/Modules/Settings/DeliveryStatusMappingController.php +++ b/src/Modules/Settings/DeliveryStatusMappingController.php @@ -23,6 +23,7 @@ final class DeliveryStatusMappingController 'inpost' => 'InPost', 'apaczka' => 'Apaczka', 'allegro_wza' => 'Allegro', + 'polkurier' => 'polkurier', ]; public function __construct( diff --git a/src/Modules/Settings/DeliveryStatusesController.php b/src/Modules/Settings/DeliveryStatusesController.php index 9983159..853ffbd 100644 --- a/src/Modules/Settings/DeliveryStatusesController.php +++ b/src/Modules/Settings/DeliveryStatusesController.php @@ -23,6 +23,7 @@ final class DeliveryStatusesController 'inpost' => 'InPost', 'apaczka' => 'Apaczka', 'allegro_wza' => 'Allegro', + 'polkurier' => 'polkurier', ]; public function __construct( diff --git a/src/Modules/Shipments/DeliveryStatus.php b/src/Modules/Shipments/DeliveryStatus.php index b96ea73..0d938d8 100644 --- a/src/Modules/Shipments/DeliveryStatus.php +++ b/src/Modules/Shipments/DeliveryStatus.php @@ -296,11 +296,33 @@ final class DeliveryStatus self::PROBLEM, ]; + private const POLKURIER_MAP = [ + // 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 = [ @@ -308,6 +330,7 @@ final class DeliveryStatus 'apaczka' => self::APACZKA_DESCRIPTIONS, 'allegro_wza' => self::ALLEGRO_DESCRIPTIONS, 'allegro_edge' => self::ALLEGRO_EDGE_DESCRIPTIONS, + 'polkurier' => self::POLKURIER_DESCRIPTIONS, ]; /** @@ -362,6 +385,7 @@ final class DeliveryStatus 'apaczka' => self::APACZKA_MAP, 'allegro_wza' => self::ALLEGRO_MAP, 'allegro_edge' => self::ALLEGRO_EDGE_MAP, + 'polkurier' => self::POLKURIER_MAP, default => [], }; @@ -375,6 +399,7 @@ final class DeliveryStatus 'apaczka' => self::APACZKA_DESCRIPTIONS, 'allegro_wza' => self::ALLEGRO_DESCRIPTIONS, 'allegro_edge' => self::ALLEGRO_EDGE_DESCRIPTIONS, + 'polkurier' => self::POLKURIER_DESCRIPTIONS, default => [], }; diff --git a/src/Modules/Shipments/DeliveryStatusMappingRepository.php b/src/Modules/Shipments/DeliveryStatusMappingRepository.php index ce6024c..9d3a296 100644 --- a/src/Modules/Shipments/DeliveryStatusMappingRepository.php +++ b/src/Modules/Shipments/DeliveryStatusMappingRepository.php @@ -155,7 +155,7 @@ final class DeliveryStatusMappingRepository return $cached; } - $providers = ['inpost', 'apaczka', 'allegro_wza']; + $providers = ['inpost', 'apaczka', 'allegro_wza', 'polkurier']; $knownKeysByProvider = []; foreach ($providers as $prov) { $knownKeysByProvider[$prov] = [];