--- phase: 29-delivery-status-mapping-ui plan: 01 type: execute wave: 1 depends_on: ["28-01"] files_modified: - database/migrations/20260323_000070_create_delivery_status_mappings_table.sql - src/Modules/Shipments/DeliveryStatusMappingRepository.php - src/Modules/Shipments/DeliveryStatus.php - src/Modules/Settings/DeliveryStatusMappingController.php - resources/views/settings/delivery-status-mappings.php - routes/web.php autonomous: false --- ## Goal Umożliwić użytkownikowi konfigurację mapowania surowych statusów z API przewoźników na znormalizowane statusy widoczne w aplikacji — bez zmian w kodzie. ## Purpose Każdy przewoźnik zwraca inne statusy (InPost: `adopted_at_sorting_center`, Apaczka: `NEW`/`CONFIRMED`, Allegro: `IN_TRANSIT`). Obecnie mapowania są zahardkodowane w DeliveryStatus.php. Użytkownik powinien móc: - Zobaczyć wszystkie mapowania (domyślne + własne) - Zmienić przypisanie surowego statusu do innego znormalizowanego statusu - Zmienić opis wyświetlany przy surowym statusie ## Output - Tabela DB `delivery_status_mappings` na custom overrides - Strona ustawień z tabelą mapowań per provider - DeliveryStatus sprawdza DB overrides przed fallback na stałe ## Project Context @.paul/PROJECT.md @.paul/ROADMAP.md @.paul/STATE.md ## Prior Work @.paul/phases/28-shipment-tracking-ui/28-01-SUMMARY.md ## Source Files @src/Modules/Shipments/DeliveryStatus.php @src/Modules/Settings/AllegroStatusMappingController.php @src/Modules/Settings/AllegroStatusMappingRepository.php @resources/views/settings/statuses.php @routes/web.php ## Required Skills (from SPECIAL-FLOWS.md) | Skill | Priority | When to Invoke | Loaded? | |-------|----------|----------------|---------| | sonar-scanner | required | Po APPLY, przed UNIFY | ○ | ## Skill Invocation Checklist - [ ] sonar-scanner uruchomiony po zakończeniu APPLY ## AC-1: Lista mapowań per provider ```gherkin Given użytkownik jest na stronie Ustawienia > Mapowanie statusów dostawy When wybiera providera (InPost / Apaczka / Allegro) Then widzi tabelę z kolumnami: Status surowy, Opis, Status znormalizowany And każdy wiersz pokazuje aktualne przypisanie (domyślne lub custom) And wiersze z custom override są wyróżnione wizualnie ``` ## AC-2: Edycja mapowania statusu ```gherkin Given użytkownik widzi tabelę mapowań dla wybranego providera When zmienia "Status znormalizowany" w wierszu (select z opcjami: Utworzona, Potwierdzona, W tranzycie, etc.) And klika Zapisz Then mapowanie jest zapisane w tabeli delivery_status_mappings And przy następnym pobraniu statusu z API, nowe mapowanie jest używane ``` ## AC-3: Edycja opisu statusu ```gherkin Given użytkownik widzi tabelę mapowań When zmienia tekst w kolumnie "Opis" dla surowego statusu And klika Zapisz Then opis jest zapisany w delivery_status_mappings And tooltip w badge'u statusu pokazuje nowy opis ``` ## AC-4: Reset do domyślnych ```gherkin Given użytkownik ma custom override dla statusu When klika przycisk "Resetuj" obok wiersza Then custom override jest usunięty z delivery_status_mappings And mapowanie wraca do domyślnego z DeliveryStatus.php ``` ## AC-5: DeliveryStatus używa custom overrides ```gherkin Given istnieje wpis w delivery_status_mappings (provider='apaczka', raw_status='NEW', normalized_status='confirmed') When system wywołuje DeliveryStatus::normalize('apaczka', 'NEW') Then zwraca 'confirmed' (z DB) zamiast 'created' (domyślny) ``` Task 1: Migracja DB + Repository database/migrations/20260323_000070_create_delivery_status_mappings_table.sql, src/Modules/Shipments/DeliveryStatusMappingRepository.php **Migracja — tabela delivery_status_mappings:** ```sql CREATE TABLE IF NOT EXISTS delivery_status_mappings ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, provider VARCHAR(32) NOT NULL, raw_status VARCHAR(64) NOT NULL, normalized_status VARCHAR(32) NOT NULL, description VARCHAR(255) NOT NULL DEFAULT '', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uq_provider_raw (provider, raw_status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ``` Idempotentna (IF NOT EXISTS). **DeliveryStatusMappingRepository:** - `listByProvider(string $provider): array` — SELECT * WHERE provider = :provider - `upsertMapping(string $provider, string $rawStatus, string $normalizedStatus, string $description): void` — INSERT ON DUPLICATE KEY UPDATE - `deleteMapping(string $provider, string $rawStatus): void` — DELETE WHERE provider AND raw_status - `getAllOverrides(): array` — SELECT * (dla cache w DeliveryStatus) Wzoruj na AllegroStatusMappingRepository (konstruktor z PDO, prepared statements). Avoid: nie dodawaj logiki biznesowej do repozytorium. php -l na obu plikach Migracja wykonuje się bez błędów na DB Tabela DB gotowa, repozytorium CRUD działa — fundament dla AC-2, AC-3, AC-4 Task 2: DeliveryStatus — DB overrides + kontroler + widok + routing src/Modules/Shipments/DeliveryStatus.php, src/Modules/Settings/DeliveryStatusMappingController.php, resources/views/settings/delivery-status-mappings.php, routes/web.php **DeliveryStatus — metody z DB override:** - Dodaj nowe statyczne metody: - `normalizeWithOverrides(string $provider, string $rawStatus, array $overrides): string` - `descriptionWithOverrides(string $provider, string $rawStatus, array $overrides): string` - $overrides to tablica ['provider:raw_status' => ['normalized_status' => X, 'description' => Y]] - Najpierw szukaj w $overrides, potem fallback na istniejące stałe (INPOST_MAP, etc.) - Dodaj `getDefaultMappings(string $provider): array` — zwraca wszystkie mapowania z hardkodowanych stałych jako tablicę [raw_status => [normalized, description]] **DeliveryStatusMappingController:** - Konstruktor: Template, Translator, AuthService, DeliveryStatusMappingRepository - `index(Request): Response`: - Parametr GET `provider` (domyślnie 'inpost') - Pobierz domyślne mapowania z DeliveryStatus::getDefaultMappings($provider) - Pobierz custom overrides z repo: listByProvider($provider) - Merge: custom nadpisuje domyślne - Przekaż do widoku: provider, mappings, providers list, csrfToken, normalized status options - `save(Request): Response`: - Waliduj CSRF - Odczytaj POST: provider, raw_status, normalized_status, description - Waliduj: normalized_status musi być jednym z DeliveryStatus::ALL_STATUSES - Wywołaj repo->upsertMapping(...) - Flash success, redirect - `reset(Request): Response`: - Waliduj CSRF - Odczytaj POST: provider, raw_status - Wywołaj repo->deleteMapping(...) - Flash success, redirect **Widok delivery-status-mappings.php:** - Nawigacja providerów: InPost | Apaczka | Allegro (linki z ?provider=X) - Tabela z kolumnami: - Status surowy (readonly, tekst) - Opis (input text, edytowalny) - Status znormalizowany (select z opcjami z LABEL_PL) - Akcje: Zapisz (jeśli zmieniony) | Resetuj (jeśli custom) - Każdy wiersz jako osobny mini-formularz POST (jak w statuses.php pattern) - Wiersze z custom override: dodaj klasę CSS `is-custom` (np. lekkie tło) - Formularz bulk save: jeden przycisk "Zapisz wszystkie" z JS zbierającym dane **routes/web.php:** - GET `/settings/delivery-status-mappings` → index - POST `/settings/delivery-status-mappings/save` → save - POST `/settings/delivery-status-mappings/reset` → reset - Wszystkie z $authMiddleware - Zainicjalizuj kontroler w sekcji controller instantiation **Menu nawigacji:** - Dodaj link "Mapowanie statusów" w menu ustawień (layout/sidebar) Avoid: - NIE zmieniaj istniejących metod normalize() i description() — nowe metody obok - NIE usuwaj hardkodowanych stałych — to fallback - NIE dodawaj JS frameworków — czyste formularze HTML php -l na wszystkich zmienionych plikach PHP Strona /settings/delivery-status-mappings ładuje się bez błędów Zmiana mapowania i zapis działa AC-1, AC-2, AC-3, AC-4, AC-5 satisfied Strona ustawień mapowania statusów dostawy — tabela mapowań per provider, edycja przypisań i opisów, reset do domyślnych. 1. Otwórz Ustawienia > Mapowanie statusów dostawy 2. Sprawdź zakładkę InPost — tabela ze statusami i ich opisami 3. Przełącz na Apaczka — sprawdź czy statusy tekstowe (NEW, CONFIRMED) są widoczne 4. Zmień mapowanie jednego statusu (np. NEW → Potwierdzona zamiast Utworzona) i Zapisz 5. Sprawdź czy badge w zamówieniu #22 odzwierciedla zmianę 6. Kliknij Resetuj — sprawdź czy wraca do domyślnego 7. Zmień opis statusu i sprawdź tooltip w badge'u Type "approved" to continue, or describe issues to fix ## DO NOT CHANGE - src/Modules/Shipments/ShipmentTrackingInterface.php - src/Modules/Shipments/InpostTrackingService.php - src/Modules/Shipments/ApaczkaTrackingService.php - src/Modules/Shipments/AllegroTrackingService.php - src/Modules/Cron/ShipmentTrackingHandler.php - Istniejące stałe w DeliveryStatus.php (INPOST_MAP, APACZKA_MAP, etc.) — to fallback ## SCOPE LIMITS - Tylko UI do konfiguracji mapowań — bez zmian w logice trackingu - Brak nowych zależności npm/composer - Brak JavaScript frameworków — czyste formularze HTML + POST - Tracking services (InPost/Apaczka/Allegro) muszą przekazywać overrides do DeliveryStatus — to integracja w ShipmentTrackingHandler, nie w tym planie jeśli wymaga dużych zmian Before declaring plan complete: - [ ] php -l przechodzi na wszystkich zmienionych plikach - [ ] Migracja wykonuje się bez błędów - [ ] Strona mapowań ładuje się dla każdego providera - [ ] Zmiana mapowania zapisuje się w DB - [ ] Reset usuwa custom override - [ ] DeliveryStatus::normalizeWithOverrides() zwraca custom wartość gdy override istnieje - [ ] DeliveryStatus::normalizeWithOverrides() zwraca domyślną gdy brak override - [ ] All acceptance criteria met - Tabela delivery_status_mappings utworzona - Strona ustawień działa z 3 providerami - Edycja mapowania i opisu zapisuje się poprawnie - Reset do domyślnych działa - DeliveryStatus respektuje custom overrides - Brak nowych zależności After completion, create `.paul/phases/29-delivery-status-mapping-ui/29-01-SUMMARY.md`