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) <noreply@anthropic.com>
This commit is contained in:
2026-05-14 17:17:48 +02:00
parent 48351b5f36
commit 27df08e661
11 changed files with 462 additions and 14 deletions

View File

@@ -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
---
<objective>
## 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`.
</objective>
<context>
<clarifications>
- **Ź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()`.
</clarifications>
## 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
</context>
<acceptance_criteria>
## 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
(Ocreated, Pconfirmed, Acancelled, WPin_transit, Ddelivered, Zreturned, Wproblem)
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
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Hardcoded POLKURIER_MAP + POLKURIER_DESCRIPTIONS w DeliveryStatus.php</name>
<files>src/Modules/Shipments/DeliveryStatus.php</files>
<action>
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.
</action>
<verify>
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'.
</verify>
<done>AC-2, AC-5 satisfied: 7 defaultów polkurier z poprawnym normalized+description; existing providers nietknięte.</done>
</task>
<task type="auto">
<name>Task 2: Dodaj 'polkurier' do PROVIDERS w obu kontrolerach + badge counter</name>
<files>src/Modules/Settings/DeliveryStatusesController.php, src/Modules/Settings/DeliveryStatusMappingController.php, src/Modules/Shipments/DeliveryStatusMappingRepository.php</files>
<action>
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).
</action>
<verify>
# 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)
</verify>
<done>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.</done>
</task>
</tasks>
<boundaries>
## 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.
</boundaries>
<verification>
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.
</verification>
<success_criteria>
- 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.
</success_criteria>
<output>
After completion, create `.paul/phases/130-polkurier-delivery-status-mappings/130-01-SUMMARY.md`
</output>

View File

@@ -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*