--- phase: 75-pull-status-mapping plan: 01 type: execute wave: 1 depends_on: [] files_modified: - database/migrations/20260407_000079_pull_status_mappings.sql - src/Modules/Settings/ShopproPullStatusMappingRepository.php - src/Modules/Settings/ShopproIntegrationsController.php - src/Modules/Settings/ShopproOrdersSyncService.php - resources/views/settings/shoppro.php - resources/lang/pl.php - routes/web.php - DOCS/DB_SCHEMA.md - DOCS/ARCHITECTURE.md - DOCS/TECH_CHANGELOG.md autonomous: true --- ## Goal Rozdzielenie mapowania statusów shopPRO na dwa niezależne kierunki: PUSH (orderPRO→shopPRO, istniejąca tabela) i PULL (shopPRO→orderPRO, nowa tabela). Naprawa bugu Phase 74 gdzie wiele statusów orderPRO mapuje na ten sam kod shopPRO, a pull direction bierze alfabetycznie pierwszy (zawsze "do_odbioru" zamiast "w_realizacji"). ## Purpose Zamówienia importowane z shopPRO dostają złe statusy w orderPRO. Wszystkie zamówienia ze statusem shopPRO "4" (przyjęte do realizacji) lądują jako "do_odbioru" zamiast "w_realizacji" — potwierdzone na 4 zamówieniach (211-214). ## Output - Nowa tabela `order_status_pull_mappings` (shopPRO code → orderPRO code, UNIQUE na shoppro side) - Sekcja "Mapowanie przy imporcie" w UI pod istniejącym mapowaniem push - `buildStatusMap()` korzysta z nowej tabeli pull zamiast odwracania push mappings - Naprawione zamówienia 211-214 ## Project Context @.paul/PROJECT.md @.paul/ROADMAP.md @.paul/STATE.md ## Source Files @src/Modules/Settings/ShopproIntegrationsController.php (saveStatusMappings, buildMappingIndex, show — lines 55-120, 222-274, 500-518) @src/Modules/Settings/ShopproStatusMappingRepository.php (listByIntegration, replaceForIntegration) @src/Modules/Settings/ShopproOrdersSyncService.php (buildStatusMap — lines 302-318) @resources/views/settings/shoppro.php (status mapping form — lines 195-265) @resources/lang/pl.php (order_statuses section — lines 1008-1040) @routes/web.php (shoppro routes — lines 449-450) ## AC-1: Nowa tabela pull mappings ```gherkin Given tabela order_status_pull_mappings istnieje When zapiszę mapowanie shopPRO kod "4" → orderPRO "w_realizacji" dla integracji 7 Then wiersz jest zapisany z UNIQUE constraint na (integration_id, shoppro_status_code) And nie mogę zapisać drugiego wiersza z tym samym (integration_id, shoppro_status_code) ``` ## AC-2: Sekcja pull mapping w UI ```gherkin Given jestem na stronie Ustawienia > shopPRO > Statusy When widzę formularz mapowania Then pod sekcją "Wysyłka statusów" widzę sekcję "Import statusów" And sekcja importu pokazuje statusy shopPRO po lewej z dropdownem orderPRO po prawej And mogę zapisać mapowanie pull niezależnie od push ``` ## AC-3: Import używa pull mappings ```gherkin Given mam pull mapping: shopPRO "4" → orderPRO "w_realizacji" When cron importuje zamówienie ze statusem shopPRO "4" Then zamówienie dostaje external_status_id = "w_realizacji" And stara tabela order_status_mappings nie jest używana do pull ``` ## AC-4: Naprawa istniejących zamówień ```gherkin Given zamówienia 211-214 mają external_status_id = "do_odbioru" When pull mapping shopPRO "4" → "w_realizacji" jest skonfigurowane And cron się uruchomi Then zamówienia 211-214 zostaną zaktualizowane na "w_realizacji" ``` Task 1: Migracja DB + Repository database/migrations/20260407_000079_pull_status_mappings.sql, src/Modules/Settings/ShopproPullStatusMappingRepository.php 1. Utworzyć migrację SQL tworzącą tabelę `order_status_pull_mappings`: - id INT AUTO_INCREMENT PRIMARY KEY - integration_id INT NOT NULL - shoppro_status_code VARCHAR(100) NOT NULL - shoppro_status_name VARCHAR(255) DEFAULT NULL - orderpro_status_code VARCHAR(100) NOT NULL - created_at DATETIME DEFAULT CURRENT_TIMESTAMP - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP - UNIQUE INDEX (integration_id, shoppro_status_code) - INDEX (integration_id) 2. W migracji: pre-populate z istniejących danych. Dla każdej integracji i każdego unikalnego shoppro_status_code wziąć wiersz z `order_status_mappings` gdzie orderpro_status_code = 'w_realizacji' (jeśli istnieje) lub najnowszy wiersz (MAX(id)): ```sql INSERT INTO order_status_pull_mappings (integration_id, shoppro_status_code, shoppro_status_name, orderpro_status_code) SELECT osm.integration_id, osm.shoppro_status_code, osm.shoppro_status_name, osm.orderpro_status_code FROM order_status_mappings osm INNER JOIN ( SELECT integration_id, shoppro_status_code, MAX(id) as max_id FROM order_status_mappings WHERE shoppro_status_code <> '' GROUP BY integration_id, shoppro_status_code ) latest ON osm.id = latest.max_id WHERE osm.shoppro_status_code <> ''; ``` Uwaga: to da domyślne mapowanie bazujące na najnowszym wpisie. Użytkownik może potem skorygować w UI. 3. Utworzyć `ShopproPullStatusMappingRepository` z metodami: - `listByIntegration(int $integrationId): array` — zwraca wiersze z pull tabeli - `replaceForIntegration(int $integrationId, array $mappings): void` — DELETE + INSERT (analogicznie do ShopproStatusMappingRepository) - Wzorować na istniejącym ShopproStatusMappingRepository ale z odwróconymi kolumnami logicznymi (klucz = shoppro_status_code) - Migracja SQL parsuje się bez błędów: `php -r "echo 'OK';"` (manualna weryfikacja składni) - Repository ma metody listByIntegration i replaceForIntegration - UNIQUE na (integration_id, shoppro_status_code) zapobiega duplikatom AC-1 satisfied: Tabela pull mappings z UNIQUE na shoppro side istnieje i ma repository Task 2: UI sekcja pull mapping + controller + routing resources/views/settings/shoppro.php, src/Modules/Settings/ShopproIntegrationsController.php, routes/web.php, resources/lang/pl.php 1. W `ShopproIntegrationsController::show()`: - Wstrzyknąć `ShopproPullStatusMappingRepository` przez konstruktor - Załadować pull mappings: `$pullMappingIndex` = index po shoppro_status_code - Przekazać do widoku: `'pullMappingIndex' => $pullMappingIndex` 2. Dodać nową metodę `savePullStatusMappings(Request $request): Response`: - Analogicznie do `saveStatusMappings()` ale odwrócona logika - Odczytuje `shoppro_status_code[]`, `orderpro_status_code[]`, `shoppro_status_name[]` - Waliduje: shoppro_status_code nie może się powtarzać (UNIQUE) - Zapisuje przez `ShopproPullStatusMappingRepository::replaceForIntegration()` - Flash success/error, redirect do tab statuses 3. W `routes/web.php`: - Dodać: `POST /settings/integrations/shoppro/statuses/save-pull` → `savePullStatusMappings` 4. W `resources/views/settings/shoppro.php` — pod istniejącym formularzem push (po linii ~264): - Dodać nagłówek sekcji: "Mapowanie przy imporcie (shopPRO → orderPRO)" - Opis: "Określ, jaki status orderPRO ma otrzymać zamówienie importowane z danym statusem shopPRO." - Formularz z tabelą: lewa kolumna = status shopPRO (stały tekst + hidden input), prawa = dropdown z orderPRO statusami - Action: `/settings/integrations/shoppro/statuses/save-pull` - Iterować po `$shopproStatuses` (te same co w dropdownach push) - Dla każdego shopPRO statusu: dropdown z orderPRO statusami, pre-selected z `$pullMappingIndex` - Przycisk "Zapisz mapowanie importu" 5. W `resources/lang/pl.php` dodać klucze tłumaczeń: - `settings.order_statuses.pull.title` → 'Mapowanie przy imporcie (shopPRO → orderPRO)' - `settings.order_statuses.pull.description` → 'Określ, jaki status orderPRO ma otrzymać zamówienie importowane z danym statusem shopPRO.' - `settings.order_statuses.pull.save` → 'Zapisz mapowanie importu' - `settings.order_statuses.pull.saved` → 'Mapowanie importu zapisane.' - `settings.order_statuses.pull.save_failed` → 'Błąd zapisu mapowania importu.' - Strona /settings/integrations/shoppro?tab=statuses wyświetla dwie sekcje mapowania - Formularz pull ma shopPRO statusy po lewej, dropdown orderPRO po prawej - Zapis działa i dane trafiają do tabeli order_status_pull_mappings AC-2 satisfied: Sekcja pull mapping w UI z niezależnym zapisem Task 3: buildStatusMap() z pull tabeli + naprawienie etykiet sekcji push src/Modules/Settings/ShopproOrdersSyncService.php, src/Modules/Settings/ShopproIntegrationsController.php 1. W `ShopproOrdersSyncService`: - Wstrzyknąć `ShopproPullStatusMappingRepository` przez konstruktor - Zmienić `buildStatusMap()` aby korzystała z pull tabeli: ```php private function buildStatusMap(int $integrationId): array { $rows = $this->pullStatusMappings->listByIntegration($integrationId); $map = []; foreach ($rows as $row) { $shopCode = strtolower(trim((string) ($row['shoppro_status_code'] ?? ''))); $orderCode = strtolower(trim((string) ($row['orderpro_status_code'] ?? ''))); if ($shopCode === '' || $orderCode === '') { continue; } $map[$shopCode] = $orderCode; // No "first wins" — UNIQUE constraint guarantees one row per shopCode } return $map; } ``` - Usunąć `if (!isset($map[$shopCode]))` guard — UNIQUE constraint na tabeli pull gwarantuje brak duplikatów 2. Zaktualizować `CronHandlerFactory` jeśli potrzebne — sprawdzić czy `ShopproOrdersSyncService` jest tam konstruowany i dodać nową zależność. 3. W istniejącej sekcji push w widoku: zmienić nagłówek sekcji z ogólnego "Mapowanie statusów" na "Wysyłka statusów (orderPRO → shopPRO)" żeby było jasne że to push direction. Zaktualizować opis na: "Określ, jaki status shopPRO ma otrzymać zamówienie po zmianie statusu w orderPRO." - buildStatusMap() odczytuje z order_status_pull_mappings - Przy pull mapping shopPRO "4" → "w_realizacji", import zamówienia ze statusem "4" daje external_status_id = "w_realizacji" - Nie ma już "first wins" — każdy shopPRO code ma dokładnie jedno mapowanie AC-3 satisfied: Import używa pull mappings z nowej tabeli. AC-4 będzie spełnione po następnym uruchomieniu crona. ## DO NOT CHANGE - `order_status_mappings` — istniejąca tabela push mappings, nie modyfikować struktury - `ShopproStatusMappingRepository` — istniejący repo push, nie modyfikować - `ShopproStatusSyncService` (push direction) — nie zmieniać, push nadal korzysta ze starej tabeli - `ShopproPaymentStatusSyncService` — nie zmieniać - Allegro status mappings — nie dotyczy ## SCOPE LIMITS - Nie naprawiamy ręcznie zamówień 211-214 — po deploy cron je zaktualizuje automatycznie - Nie dodajemy pull mapping dla Allegro (osobna faza jeśli potrzebne) - Nie zmieniamy logiki push mappings - Nie dodajemy walidacji UI (JS) — duplikaty są blokowane przez UNIQUE constraint w DB Before declaring plan complete: - [ ] Migracja SQL tworzy tabelę i pre-populuje dane - [ ] Repository CRUD działa (list, replace) - [ ] UI wyświetla dwie sekcje: push i pull - [ ] Zapis pull mappings trafia do nowej tabeli - [ ] buildStatusMap() czyta z pull tabeli - [ ] Brak regresji w push direction (ShopproStatusSyncService niezmodyfikowany) - [ ] Tłumaczenia kompletne - [ ] DOCS zaktualizowane - Nowa tabela order_status_pull_mappings istnieje i ma UNIQUE na (integration_id, shoppro_status_code) - UI ma dwie odrębne sekcje mapowania (push i pull) z jasnymi etykietami kierunku - Import zamówień z shopPRO korzysta wyłącznie z pull tabeli - Zamówienia ze statusem shopPRO "4" trafiają jako "w_realizacji" (nie "do_odbioru") - Push sync nie jest naruszony After completion, create `.paul/phases/75-pull-status-mapping/75-01-SUMMARY.md`