Files
orderPRO/.paul/phases/75-pull-status-mapping/75-01-PLAN.md
2026-04-07 20:32:43 +02:00

255 lines
12 KiB
Markdown

---
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
---
<objective>
## 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
</objective>
<context>
## 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)
</context>
<acceptance_criteria>
## 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"
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Migracja DB + Repository</name>
<files>database/migrations/20260407_000079_pull_status_mappings.sql, src/Modules/Settings/ShopproPullStatusMappingRepository.php</files>
<action>
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)
</action>
<verify>
- 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
</verify>
<done>AC-1 satisfied: Tabela pull mappings z UNIQUE na shoppro side istnieje i ma repository</done>
</task>
<task type="auto">
<name>Task 2: UI sekcja pull mapping + controller + routing</name>
<files>resources/views/settings/shoppro.php, src/Modules/Settings/ShopproIntegrationsController.php, routes/web.php, resources/lang/pl.php</files>
<action>
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.'
</action>
<verify>
- 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
</verify>
<done>AC-2 satisfied: Sekcja pull mapping w UI z niezależnym zapisem</done>
</task>
<task type="auto">
<name>Task 3: buildStatusMap() z pull tabeli + naprawienie etykiet sekcji push</name>
<files>src/Modules/Settings/ShopproOrdersSyncService.php, src/Modules/Settings/ShopproIntegrationsController.php</files>
<action>
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."
</action>
<verify>
- 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
</verify>
<done>AC-3 satisfied: Import używa pull mappings z nowej tabeli. AC-4 będzie spełnione po następnym uruchomieniu crona.</done>
</task>
</tasks>
<boundaries>
## 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
</boundaries>
<verification>
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
</verification>
<success_criteria>
- 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
</success_criteria>
<output>
After completion, create `.paul/phases/75-pull-status-mapping/75-01-SUMMARY.md`
</output>