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.
+
+
+
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] = [];