From 03c18f67825abdedd5c32895e41ea5c088176736 Mon Sep 17 00:00:00 2001 From: Jacek Pyziak Date: Fri, 13 Mar 2026 17:44:42 +0100 Subject: [PATCH] plan(07-pre-expansion-fixes): create 5 plans for pre-expansion fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 07-01: Performance — N+1 subqueries, information_schema static, DB indexes 07-02: Stability — SSL verification (4 clients), cron throttle→DB, migration 000014b 07-03: UX — orderpro_to_allegro disable, orders list items 14-17 07-04: Tests — AllegroTokenManager + AllegroOrderImportService unit tests 07-05: InPost ShipmentProviderInterface (replaces allegro_wza workaround) Co-Authored-By: Claude Sonnet 4.6 --- .paul/ROADMAP.md | 21 +- .paul/STATE.md | 29 +- .../07-pre-expansion-fixes/07-01-PLAN.md | 220 +++++++++++++ .../07-pre-expansion-fixes/07-02-PLAN.md | 288 ++++++++++++++++++ .../07-pre-expansion-fixes/07-03-PLAN.md | 286 +++++++++++++++++ .../07-pre-expansion-fixes/07-04-PLAN.md | 179 +++++++++++ .../07-pre-expansion-fixes/07-05-PLAN.md | 220 +++++++++++++ 7 files changed, 1226 insertions(+), 17 deletions(-) create mode 100644 .paul/phases/07-pre-expansion-fixes/07-01-PLAN.md create mode 100644 .paul/phases/07-pre-expansion-fixes/07-02-PLAN.md create mode 100644 .paul/phases/07-pre-expansion-fixes/07-03-PLAN.md create mode 100644 .paul/phases/07-pre-expansion-fixes/07-04-PLAN.md create mode 100644 .paul/phases/07-pre-expansion-fixes/07-05-PLAN.md diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md index 3784417..2b3b76a 100644 --- a/.paul/ROADMAP.md +++ b/.paul/ROADMAP.md @@ -6,13 +6,26 @@ orderPRO to narzędzie do wielokanałowego zarządzania sprzedażą. Projekt prz ## Current Milestone -Brak aktywnego milestone. +**v0.2 Pre-Expansion Fixes** (v0.2.0) +Status: 🔄 In Progress +Phases: 0/1 complete -Run `/paul:discuss-milestone` lub `/paul:milestone` aby zdefiniować v0.2. +## Phases -## Next Milestone +| Phase | Name | Plans | Status | Completed | +|-------|------|-------|--------|-----------| +| 7 | Pre-Expansion Fixes | 0/5 | 🔄 Planning | — | -> Niezdefiniowany. Uruchom `/paul:discuss-milestone` aby omówić zakres lub `/paul:milestone` aby stworzyć bezpośrednio. +## Phase Details + +### Phase 7 — Pre-Expansion Fixes +Naprawa krytycznych problemów wydajnościowych, bezpieczeństwa i UX przed rozbudową aplikacji o nowe integracje i funkcje. + +- **Plan 07-01** — Performance: N+1 subqueries + information_schema cache + DB indexes — *Not started* +- **Plan 07-02** — Stability: SSL verification + cron throttle DB + migration 000014b — *Not started* +- **Plan 07-03** — UX: orderpro_to_allegro disable + lista zamówień (items 14-17) — *Not started* +- **Plan 07-04** — Tests: AllegroTokenManager + AllegroOrderImportService unit tests — *Not started* +- **Plan 07-05** — InPost: ShipmentProviderInterface implementation — *Not started* ## Completed Milestones diff --git a/.paul/STATE.md b/.paul/STATE.md index beb441e..2157482 100644 --- a/.paul/STATE.md +++ b/.paul/STATE.md @@ -5,25 +5,26 @@ See: .paul/PROJECT.md (updated 2026-03-12) **Core value:** Sprzedawca może obsługiwać zamówienia ze wszystkich kanałów sprzedaży i nadawać przesyłki bez przełączania się między platformami. -**Current focus:** Awaiting next milestone. v0.1 complete — uruchom /paul:discuss-milestone lub /paul:milestone. +**Current focus:** Faza 07 — Pre-Expansion Fixes. 5 planów utworzonych, gotowe do APPLY. ## Current Position -Milestone: Awaiting next milestone -Phase: None active -Plan: None -Status: Milestone v0.1 Initial Release complete — ready for next -Last activity: 2026-03-13 — Milestone v0.1 completed +Milestone: v0.2 Pre-Expansion Fixes +Phase: 7 of TBD (07-pre-expansion-fixes) — Planning +Plan: 07-01 do 07-05 CREATED, awaiting approval +Status: PLANy gotowe — wybrać plan do APPLY +Last activity: 2026-03-13 — Faza 07 zaplanowana (5 planów) Progress: - v0.1 Initial Release: [██████████] 100% ✓ +- v0.2 Pre-Expansion Fixes: [░░░░░░░░░░] 0% (0/5 planów) ## Loop Position Current loop state: ``` PLAN ──▶ APPLY ──▶ UNIFY - ○ ○ ○ [Milestone complete — ready for next] + ✓ ○ ○ [07-01 plan gotowy — zacznij od /paul:apply 07-01] ``` ## Accumulated Context @@ -91,13 +92,15 @@ Brak. ## Session Continuity Last session: 2026-03-13 -Stopped at: Milestone v0.1 Initial Release complete — /paul:complete-milestone executed -Next action: /paul:discuss-milestone -Resume file: .paul/MILESTONES.md +Stopped at: Faza 07 zaplanowana — 5 planów gotowych do wykonania +Next action: /paul:apply .paul/phases/07-pre-expansion-fixes/07-01-PLAN.md +Resume file: .paul/phases/07-pre-expansion-fixes/07-01-PLAN.md Resume context: -- v0.1: 6 faz, 15 planów zamknięte -- Deferred: AllegroImportScheduleService (AllegroIntegrationController 25 metod), CI/CD SonarQube GitHub Actions -- git tag: v0.1.0 na main +- 07-01: Performance (N+1 subqueries, information_schema static, DB indexes) +- 07-02: SSL verification + cron throttle DB + migration 000014b +- 07-03: UX fixes (orderpro_to_allegro disable, items 14-17) — ma checkpoint +- 07-04: Tests (AllegroTokenManager + AllegroOrderImportService) +- 07-05: InPost ShipmentProviderInterface — ma checkpoint:decision --- *STATE.md — Updated after every significant action* diff --git a/.paul/phases/07-pre-expansion-fixes/07-01-PLAN.md b/.paul/phases/07-pre-expansion-fixes/07-01-PLAN.md new file mode 100644 index 0000000..8e1e652 --- /dev/null +++ b/.paul/phases/07-pre-expansion-fixes/07-01-PLAN.md @@ -0,0 +1,220 @@ +--- +phase: 07-pre-expansion-fixes +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - src/Modules/Orders/OrdersRepository.php + - database/migrations/20260313_000048_add_orders_performance_indexes.sql +autonomous: true +--- + + +## Goal +Wyeliminować dwa bottlenecki wydajnościowe w module Orders: 4 correlated subqueries na każdy wiersz listy zamówień oraz zapytanie do information_schema przy każdym żądaniu HTTP. + +## Purpose +Lista zamówień jest głównym widokiem aplikacji. Przy 50 wierszach na stronę, correlated subqueries oznaczają 200+ dodatkowych zapytań per page load. `canResolveMappedMedia()` uderza w `information_schema` przy każdym żądaniu do OrdersRepository — notoriously slow na MySQL. Oba problemy narastają z liczbą zamówień. + +## Output +- `OrdersRepository::buildListSql()` — 4 subqueries zastąpione aggregating LEFT JOIN +- `OrdersRepository::canResolveMappedMedia()` — wynik cachowany w `static` property +- Nowa migracja z brakującymi indeksami dla `orders` table + + + +## Project Context +@.paul/PROJECT.md + +## Source Files +@src/Modules/Orders/OrdersRepository.php + + + + +## AC-1: Brak correlated subqueries w liście zamówień +```gherkin +Given OrdersRepository::buildListSql() zawiera 4 correlated subqueries (items_count, items_qty, shipments_count, documents_count) +When metoda buduje SQL dla listy zamówień +Then SQL nie zawiera "(SELECT COUNT(*) FROM order_items" ani podobnych correlated subqueries + AND zwracane kolumny items_count, items_qty, shipments_count, documents_count nadal istnieją + AND logika transformOrderRow() w pełni działa (te pola nadal trafiają do wyniku) +``` + +## AC-2: information_schema nie jest odpytywany per-request +```gherkin +Given canResolveMappedMedia() używa instance property $this->supportsMappedMedia jako cache +When OrdersRepository jest instanciowany dwukrotnie w tej samej sesji PHP (np. dwa wywołania repository) +Then information_schema.COLUMNS jest zapytany co najwyżej raz na cykl PHP (static property) +``` + +## AC-3: Brakujące indeksy dodane migracją +```gherkin +Given tabela orders nie ma indeksów na kolumnach source, external_status_id, ordered_at +When tworzona jest nowa migracja i wykonana przez migrator +Then tabela orders ma indeksy na: source, external_status_id, ordered_at + AND migracja jest idempotentna (IF NOT EXISTS lub ADD INDEX IF NOT EXISTS lub ALTER IGNORE) +``` + + + + + + + Task 1: Zamień 4 correlated subqueries na aggregating LEFT JOINs w buildListSql() + src/Modules/Orders/OrdersRepository.php + + W metodzie `buildListSql()` (linie ~135-171) zamień: + ```sql + (SELECT COUNT(*) FROM order_items oi WHERE oi.order_id = o.id) AS items_count, + (SELECT COALESCE(SUM(oi.quantity), 0) FROM order_items oi WHERE oi.order_id = o.id) AS items_qty, + (SELECT COUNT(*) FROM order_shipments sh WHERE sh.order_id = o.id) AS shipments_count, + (SELECT COUNT(*) FROM order_documents od WHERE od.order_id = o.id) AS documents_count + ``` + Na: + ```sql + COALESCE(oi_agg.items_count, 0) AS items_count, + COALESCE(oi_agg.items_qty, 0) AS items_qty, + COALESCE(sh_agg.shipments_count, 0) AS shipments_count, + COALESCE(od_agg.documents_count, 0) AS documents_count + ``` + I dodaj do klauzuli FROM (po istniejących LEFT JOINach, przed WHERE): + ```sql + LEFT JOIN ( + SELECT order_id, + COUNT(*) AS items_count, + COALESCE(SUM(quantity), 0) AS items_qty + FROM order_items + GROUP BY order_id + ) oi_agg ON oi_agg.order_id = o.id + LEFT JOIN ( + SELECT order_id, COUNT(*) AS shipments_count + FROM order_shipments + GROUP BY order_id + ) sh_agg ON sh_agg.order_id = o.id + LEFT JOIN ( + SELECT order_id, COUNT(*) AS documents_count + FROM order_documents + GROUP BY order_id + ) od_agg ON od_agg.order_id = o.id + ``` + UWAGA: buildListSql() generuje SQL jako string — upewnij się że nowe JOINy są wstawione + przed `$whereSql` (który jest konkatenowany na końcu z klauzulą WHERE). + + Sprawdź jak wygląda $whereSql — czy zawiera "WHERE" czy tylko "AND ..."? + Jeśli WHERE jest w $whereSql, nowe JOINy idą bezpośrednio przed nim. + + NIE zmieniaj metody transformOrderRow() — klucze tablicy pozostają te same. + + + php -l src/Modules/Orders/OrdersRepository.php + grep -c "SELECT COUNT\|SELECT COALESCE\|subquery" src/Modules/Orders/OrdersRepository.php + # Powinno zwrócić 0 dla wzorców correlated subquery w buildListSql + + AC-1 satisfied: buildListSql() używa LEFT JOIN zamiast correlated subqueries + + + + Task 2: Zamień instance property na static w canResolveMappedMedia() + src/Modules/Orders/OrdersRepository.php + + Znajdź deklarację instance property (okolice klasy): + ```php + private ?bool $supportsMappedMedia = null; + ``` + Zmień na: + ```php + private static ?bool $supportsMappedMedia = null; + ``` + W metodzie `canResolveMappedMedia()` zmień referencje z `$this->supportsMappedMedia` na + `self::$supportsMappedMedia` (we wszystkich miejscach metody — przypisanie i odczyt). + + To jedyna zmiana. Nie modyfikuj logiki zapytania ani żadnej innej metody. + + + php -l src/Modules/Orders/OrdersRepository.php + grep -n "supportsMappedMedia" src/Modules/Orders/OrdersRepository.php + # Powinno pokazać: private static ?bool i self::$supportsMappedMedia + + AC-2 satisfied: canResolveMappedMedia() używa static property + + + + Task 3: Migracja z brakującymi indeksami dla tabeli orders + database/migrations/20260313_000048_add_orders_performance_indexes.sql + + Stwórz plik `database/migrations/20260313_000048_add_orders_performance_indexes.sql`. + + Przed pisaniem migracji, sprawdź jakie indeksy już istnieją: + ```bash + grep -n "INDEX\|KEY " database/migrations/20260302_000018_create_orders_tables_and_schedule.sql + grep -rn "ADD INDEX\|ADD KEY" database/migrations/ | grep -i "order" + ``` + + Migracja powinna dodać brakujące indeksy: + ```sql + -- Indeksy dla typowych filtrów i sortowań na liście zamówień + ALTER TABLE orders + ADD INDEX IF NOT EXISTS orders_source_idx (source), + ADD INDEX IF NOT EXISTS orders_external_status_idx (external_status_id), + ADD INDEX IF NOT EXISTS orders_ordered_at_idx (ordered_at), + ADD INDEX IF NOT EXISTS orders_source_status_idx (source, external_status_id); + + -- Indeks dla allegro status mapping lookup (JOIN w buildListSql) + ALTER TABLE allegro_order_status_mappings + ADD INDEX IF NOT EXISTS allegro_status_code_idx (allegro_status_code) + -- tylko jeśli ten indeks nie istnieje (sprawdź migration 000038) + ``` + + WAŻNE: Sprawdź aktualny schemat przed dodaniem indeksu — nie twórz duplikatów. + Użyj `IF NOT EXISTS` gdzie MySQL to obsługuje (MySQL 8.0+), albo sformułuj jako + osobne polecenia z IGNORE: `ALTER TABLE orders ADD IGNORE INDEX ...` + + Sprawdź też `database/migrations/20260308_000038_ensure_order_status_mappings_table.sql` + aby wiedzieć jakie indeksy allegro_order_status_mappings już ma. + + + php -l database/migrations/20260313_000048_add_orders_performance_indexes.sql 2>/dev/null || echo "SQL file - no php lint needed" + # Sprawdź czy plik istnieje i nie jest pusty + cat database/migrations/20260313_000048_add_orders_performance_indexes.sql + + AC-3 satisfied: migracja z indeksami na source, external_status_id, ordered_at istnieje + + + + + + +## DO NOT CHANGE +- Logika filtrowania ($whereSql) — tylko zmiana mechanizmu agregacji, nie filtrów +- Metoda transformOrderRow() — zwracane klucze muszą pozostać identyczne +- Inne metody OrdersRepository poza buildListSql() i canResolveMappedMedia() +- Istniejące pliki migracji — tylko NOWY plik 000048 + +## SCOPE LIMITS +- Tylko optymalizacja zapytania list; nie zmieniamy zapytań detail/find +- Nie dodajemy nowych kolumn ani nie zmieniamy schematu tabel — tylko indeksy +- Nie implementujemy cache'owania na poziomie Redis/Memcached — tylko static property + + + + +Przed zamknięciem planu: +- [ ] php -l src/Modules/Orders/OrdersRepository.php — brak błędów +- [ ] grep buildListSql src/Modules/Orders/OrdersRepository.php — brak "SELECT COUNT(*) FROM order_items WHERE oi.order_id" +- [ ] grep "supportsMappedMedia" src/Modules/Orders/OrdersRepository.php — pokazuje "static" +- [ ] Plik migracji 000048 istnieje i jest nieopusty +- [ ] Lista zamówień ładuje się bez błędów PHP + + + +- 4 correlated subqueries usunięte z buildListSql() +- canResolveMappedMedia() z static property +- Migracja 000048 z indeksami na source, external_status_id, ordered_at +- Zero błędów składniowych PHP + + + +Po zakończeniu utwórz `.paul/phases/07-pre-expansion-fixes/07-01-SUMMARY.md` + diff --git a/.paul/phases/07-pre-expansion-fixes/07-02-PLAN.md b/.paul/phases/07-pre-expansion-fixes/07-02-PLAN.md new file mode 100644 index 0000000..ecba5f1 --- /dev/null +++ b/.paul/phases/07-pre-expansion-fixes/07-02-PLAN.md @@ -0,0 +1,288 @@ +--- +phase: 07-pre-expansion-fixes +plan: 02 +type: execute +wave: 1 +depends_on: [] +files_modified: + - src/Modules/Settings/AllegroApiClient.php + - src/Modules/Settings/AllegroOAuthClient.php + - src/Modules/Settings/ShopproApiClient.php + - src/Modules/Settings/ApaczkaApiClient.php + - src/Core/Application.php + - database/migrations/20260313_000049_add_cron_last_run_at_setting.sql + - .env.example +autonomous: true +--- + + +## Goal +Naprawić trzy problemy stabilności przed rozbudową: zduplikowany numer migracji 000014, brak weryfikacji SSL w 4 klientach API, cron throttle przechowywany w sesji zamiast bazie danych. + +## Purpose +Każdy nowy klient API dodany bez wzorca SSL verification powiela podatność. Cron throttle w sesji powoduje wielokrotne uruchamianie crona przy wielu aktywnych sesjach. Duplikat migracji wprowadza niejednoznaczność przy deploy na nowe środowisko. Wszystkie trzy to fundament do naprawy przed dodawaniem nowych integracji. + +## Output +- 4 pliki ApiClient z `CURLOPT_SSL_VERIFYPEER => true` + `CURLOPT_CAINFO` +- `.env.example` z `CURL_CA_BUNDLE_PATH` +- `Application::isWebCronThrottled()` czytający timestamp z `app_settings` zamiast `$_SESSION` +- Nowa migracja seed dla klucza `cron_web_last_run_at` w `app_settings` +- Migracja 000014b (rename duplikatu) + + + +## Project Context +@.paul/PROJECT.md + +## Source Files +@src/Modules/Settings/AllegroApiClient.php +@src/Modules/Settings/AllegroOAuthClient.php +@src/Core/Application.php +@.env.example + + + + +## AC-1: SSL weryfikowany w każdym cURL wywołaniu +```gherkin +Given 4 klasy ApiClient (AllegroApiClient, AllegroOAuthClient, ShopproApiClient, ApaczkaApiClient) wykonują cURL bez CURLOPT_SSL_VERIFYPEER +When dowolny ApiClient wykonuje zapytanie HTTP +Then CURLOPT_SSL_VERIFYPEER jest ustawione na true + AND CURLOPT_CAINFO wskazuje na ścieżkę z .env (lub systemowy bundle jako fallback) + AND .env.example zawiera CURL_CA_BUNDLE_PATH z komentarzem +``` + +## AC-2: Web cron throttle oparty na DB, nie sesji +```gherkin +Given Application::isWebCronThrottled() zapisuje/czyta timestamp z $_SESSION['cron_web_last_run_at'] +When isWebCronThrottled() jest wywołane +Then timestamp jest czytany z app_settings (klucz: cron_web_last_run_at) zamiast $_SESSION + AND zapis po uruchomieniu crona idzie do app_settings (UPDATE lub INSERT ON DUPLICATE KEY) + AND $_SESSION['cron_web_last_run_at'] nie jest już używany +``` + +## AC-3: Migracja 000014 zdeduplikowana +```gherkin +Given dwa pliki mają sekwencję 000014 w nazwie +When sprawdzasz listę migracji +Then jeden z nich jest przemianowany na 000014b + AND plik zawiera idempotentny INSERT (ON DUPLICATE KEY UPDATE lub INSERT IGNORE) + żeby powtórne uruchomienie na istniejącej bazie nie powodowało błędu +``` + + + + + + + Task 1: Dodaj SSL verification do 4 ApiClient klas + + src/Modules/Settings/AllegroApiClient.php, + src/Modules/Settings/AllegroOAuthClient.php, + src/Modules/Settings/ShopproApiClient.php, + src/Modules/Settings/ApaczkaApiClient.php, + .env.example + + + Dla każdej z 4 klas: + 1. Odczytaj plik — zidentyfikuj wszystkie `curl_setopt_array($ch, [...])` wywołania + 2. Do każdego array dodaj (jeśli nie ma): + ```php + CURLOPT_SSL_VERIFYPEER => true, + CURLOPT_SSL_VERIFYHOST => 2, + CURLOPT_CAINFO => $this->getCaBundlePath(), + ``` + 3. Dodaj prywatną metodę pomocniczą do każdej klasy: + ```php + private function getCaBundlePath(): string + { + $envPath = (string) ($_ENV['CURL_CA_BUNDLE_PATH'] ?? ''); + if ($envPath !== '' && is_file($envPath)) { + return $envPath; + } + // Windows XAMPP fallback + $xamppCa = 'C:/xampp/php/extras/ssl/cacert.pem'; + if (is_file($xamppCa)) { + return $xamppCa; + } + // System default (Linux/macOS) + return '/etc/ssl/certs/ca-certificates.crt'; + } + ``` + Jeśli wszystkie 4 klasy mają wspólny trait lub klasę bazową — wydziel metodę tam. + Jeśli nie — dodaj do każdej osobno (duplikacja jest OK, unikamy przedwczesnej abstrakcji). + + 4. W `.env.example` dodaj sekcję: + ``` + # SSL/TLS — ścieżka do CA bundle dla cURL + # Windows XAMPP: C:/xampp/php/extras/ssl/cacert.pem + # Linux: /etc/ssl/certs/ca-certificates.crt + CURL_CA_BUNDLE_PATH= + ``` + + WAŻNE: Nie dodawaj CURLOPT_SSL_VERIFYPEER => false nigdzie. Jeśli CA bundle nie istnieje, + metoda getCaBundlePath() zwraca ścieżkę — PHP zgłosi błąd cURL jeśli plik nie istnieje. + To jest pożądane zachowanie (fail-fast, nie silent MITM). + + + php -l src/Modules/Settings/AllegroApiClient.php + php -l src/Modules/Settings/AllegroOAuthClient.php + php -l src/Modules/Settings/ShopproApiClient.php + php -l src/Modules/Settings/ApaczkaApiClient.php + grep -n "CURLOPT_SSL_VERIFYPEER" src/Modules/Settings/AllegroApiClient.php + grep -c "CURLOPT_SSL_VERIFYPEER" src/Modules/Settings/ShopproApiClient.php + + AC-1 satisfied: wszystkie 4 ApiClienty mają CURLOPT_SSL_VERIFYPEER => true + + + + Task 2: Cron throttle z $_SESSION → app_settings DB + + src/Core/Application.php, + database/migrations/20260313_000049_add_cron_last_run_at_setting.sql + + + W `Application.php`, znajdź metodę `isWebCronThrottled()` (linie ~268-285). + + Obecna logika: + ```php + $lastRunAt = isset($_SESSION['cron_web_last_run_at']) ? (int) $_SESSION['cron_web_last_run_at'] : 0; + // ... + $_SESSION['cron_web_last_run_at'] = $now; + ``` + + Zastąp odczyt sesji odczytem z DB: + ```php + $lastRunAt = $this->getWebCronLastRunAt(); + ``` + + I zapis sesji zapisem do DB: + ```php + $this->setWebCronLastRunAt($now); + ``` + + Dodaj prywatne metody pomocnicze: + ```php + private function getWebCronLastRunAt(): int + { + try { + $stmt = $this->db->pdo()->prepare( + "SELECT setting_value FROM app_settings WHERE setting_key = 'cron_web_last_run_at'" + ); + $stmt->execute(); + $value = $stmt->fetchColumn(); + return $value !== false ? (int) $value : 0; + } catch (Throwable) { + return 0; + } + } + + private function setWebCronLastRunAt(int $timestamp): void + { + try { + $this->db->pdo()->prepare( + "INSERT INTO app_settings (setting_key, setting_value, created_at, updated_at) + VALUES ('cron_web_last_run_at', :ts, NOW(), NOW()) + ON DUPLICATE KEY UPDATE setting_value = :ts, updated_at = NOW()" + )->execute(['ts' => (string) $timestamp]); + } catch (Throwable) { + // throttle failure is non-critical — GET_LOCK is the real guard + } + } + ``` + + Sprawdź jak Application uzyskuje dostęp do PDO — jeśli przez `$this->db->pdo()` lub + `$this->db->query()` — dostosuj. Sprawdź jak CronHandlerFactory i inne miejsca używają DB. + + Usuń odwołania do `$_SESSION['cron_web_last_run_at']` całkowicie. + + Stwórz migrację `20260313_000049_add_cron_last_run_at_setting.sql`: + ```sql + INSERT INTO app_settings (setting_key, setting_value, created_at, updated_at) + VALUES ('cron_web_last_run_at', '0', NOW(), NOW()) + ON DUPLICATE KEY UPDATE updated_at = updated_at; + ``` + + + php -l src/Core/Application.php + grep -n "SESSION.*cron_web\|cron_web.*SESSION" src/Core/Application.php + # Powinno zwrócić 0 wyników + grep -n "getWebCronLastRunAt\|setWebCronLastRunAt" src/Core/Application.php + + AC-2 satisfied: isWebCronThrottled() nie używa $_SESSION, czyta/zapisuje app_settings + + + + Task 3: Rename duplikatu migracji 000014 + + database/migrations/20260301_000014b_add_products_sku_format_setting.sql + + + Migracja `20260301_000014_add_products_sku_format_setting.sql` ma ten sam numer co + `20260227_000014_create_product_integration_translations.sql`. + + Ponieważ migrator śledzi migracje po **pełnej nazwie pliku** (nie numerze sekwencji), + rename jest bezpieczny dla nowych instalacji. Dla istniejących instalacji plik pod nową + nazwą będzie wyglądał jako nowy — dlatego zawartość musi być idempotentna. + + Kroki: + 1. Odczytaj zawartość `20260301_000014_add_products_sku_format_setting.sql` + 2. Sprawdź czy INSERT używa `ON DUPLICATE KEY UPDATE` lub `INSERT IGNORE` + - Jeśli nie: zmień INSERT na idempotentny (INSERT IGNORE lub ON DUPLICATE KEY) + 3. Utwórz nowy plik `20260301_000014b_add_products_sku_format_setting.sql` z zaktualizowaną treścią + 4. Usuń stary plik `20260301_000014_add_products_sku_format_setting.sql` + + UWAGA: Użyj `git mv` (przez bash) żeby git śledził rename: + ```bash + git mv "database/migrations/20260301_000014_add_products_sku_format_setting.sql" \ + "database/migrations/20260301_000014b_add_products_sku_format_setting.sql" + ``` + Potem edytuj plik (jeśli INSERT wymaga korekty na idempotentny). + + + ls database/migrations/ | grep "000014" + # Powinno pokazać: 20260227_000014_create... i 20260301_000014b_add... + # NIE powinno być: 20260301_000014_add... (stary plik) + + AC-3 satisfied: brak duplikatu, jeden plik przemianowany na 000014b + + + + + + +## DO NOT CHANGE +- Logika biznesowa żadnego ApiClienta — tylko dodanie SSL options do istniejących curl_setopt_array +- GET_LOCK logika w Application.php — nie ruszamy mutex, tylko session→DB dla timestamp +- Inne klucze w app_settings +- Istniejące migracje poza rename 000014 + +## SCOPE LIMITS +- Nie implementuj rotacji CSRF tokenów (osobny deferred concern) +- Nie dodawaj path traversal check dla label files (osobny deferred concern) +- CA bundle path — środowisko aplikacyjne, nie certyfikat self-signed + + + + +Przed zamknięciem planu: +- [ ] php -l na wszystkich 4 ApiClient plikach +- [ ] php -l src/Core/Application.php +- [ ] grep "SSL_VERIFYPEER" — każdy ApiClient ma ten wpis +- [ ] grep "SESSION.*cron_web" Application.php — 0 wyników +- [ ] ls database/migrations/ | grep "000014" — tylko 000014 i 000014b +- [ ] Plik migracji 000049 istnieje + + + +- 4 ApiClienty z SSL verification +- .env.example z CURL_CA_BUNDLE_PATH +- Application::isWebCronThrottled() bez $_SESSION +- Migracja 000049 (cron_web_last_run_at seed) +- Rename 000014 → 000014b +- Zero błędów składniowych PHP + + + +Po zakończeniu utwórz `.paul/phases/07-pre-expansion-fixes/07-02-SUMMARY.md` + diff --git a/.paul/phases/07-pre-expansion-fixes/07-03-PLAN.md b/.paul/phases/07-pre-expansion-fixes/07-03-PLAN.md new file mode 100644 index 0000000..603347f --- /dev/null +++ b/.paul/phases/07-pre-expansion-fixes/07-03-PLAN.md @@ -0,0 +1,286 @@ +--- +phase: 07-pre-expansion-fixes +plan: 03 +type: execute +wave: 1 +depends_on: [] +files_modified: + - src/Modules/Settings/AllegroStatusSyncService.php + - resources/views/settings/allegro.php + - resources/views/orders/list.php + - resources/scss/modules/_orders.scss +autonomous: false +--- + + +## Goal +Naprawić 5 problemów UX/behawioralnych widocznych dla użytkownika: cichy no-op synchronizacji statusów, kolejność source/ID w liście zamówień, brak kolorowania statusów, błędna etykieta źródła, zbyt jasne obramowanie pól formularza. + +## Purpose +Użytkownik konfiguruje synchronizację "orderPRO → Allegro" i aplikacja loguje "sukces" mimo że nic nie robi. To aktywnie wprowadza w błąd. Pozostałe 4 to widoczne błędy UX (todo.md items 14-17) które obniżają czytelność głównego widoku listy zamówień — ekranu który użytkownik widzi najczęściej. + +## Output +- AllegroStatusSyncService: `ok: false` dla nieopracowanego kierunku + UI option disabled +- Lista zamówień: source przed ID, integracja zamiast "shopPRO", "ID:" prefix +- Lista zamówień: statusy kolorowane zgodnie z ustawieniami (jeśli mapping istnieje) +- SCSS: ciemniejsze obramowanie inputów/select/textarea + + + +## Project Context +@.paul/PROJECT.md + +## Source Files +@src/Modules/Settings/AllegroStatusSyncService.php +@resources/views/settings/allegro.php +@resources/views/orders/list.php +@resources/scss/modules/_orders.scss + + + + +## AC-1: orderpro_to_allegro nie daje false-positive sukcesu +```gherkin +Given użytkownik ustawił kierunek sync "orderPRO → Allegro" w ustawieniach +When cron uruchamia synchronizację statusów +Then serwis zwraca ['ok' => false, 'message' => 'Kierunek orderPRO -> Allegro nie jest jeszcze wdrożony.'] + AND log crona odróżnia to od sukcesów (ok: false) +``` + +## AC-2: Opcja sync direction "orderPRO → Allegro" oznaczona jako niedostępna +```gherkin +Given w formularzu ustawień Allegro jest select z opcjami kierunku synchronizacji +When użytkownik otwiera formularz ustawień +Then opcja "orderPRO → Allegro" jest widoczna ale wyraźnie oznaczona jako "(wkrótce)" lub disabled + AND formularz nie pozwala zapisać tej opcji bez ostrzeżenia +``` + +## AC-3: W liście zamówień source wyświetla się przed ID, z etykietą "ID:" +```gherkin +Given wiersz zamówienia w div.orders-ref__meta pokazuje kolejność: [ID][source] +When renderowana jest lista zamówień +Then kolejność to: [source][ID], a ID ma prefix "ID:" + AND dla zamówień z integracji shopPRO wyświetlana jest konkretna nazwa integracji (nie "shopPRO") +``` + +## AC-4: Statusy zamówień kolorowane na liście +```gherkin +Given administrator skonfigurował kolory statusów w ustawieniach (tabela allegro_order_status_mappings lub odpowiednik) +When renderowana jest lista zamówień z zamówieniami mającymi zmapowane statusy +Then komórka/badge statusu ma inline styl lub klasę CSS odpowiadającą skonfigurowanemu kolorowi + AND dla niemapowanych statusów wyświetlany jest tekst bez koloru (neutralny fallback) +``` + +## AC-5: Ciemniejsze obramowanie pól formularza +```gherkin +Given input, select, textarea mają jasne obramowanie (zbyt słabo widoczne) +When użytkownik wyświetla dowolny formularz w aplikacji +Then obramowanie pól jest zauważalnie ciemniejsze (wyższy kontrast z tłem) +``` + + + + + + + Task 1: AllegroStatusSyncService — ok:false + UI disabled dla orderpro_to_allegro + + src/Modules/Settings/AllegroStatusSyncService.php, + resources/views/settings/allegro.php + + + **W AllegroStatusSyncService.php (linie ~39-45):** + Zmień: + ```php + if ($direction === self::DIRECTION_ORDERPRO_TO_ALLEGRO) { + return [ + 'ok' => true, // ← BUG: powinno być false + ... + ]; + } + ``` + Na: + ```php + if ($direction === self::DIRECTION_ORDERPRO_TO_ALLEGRO) { + return [ + 'ok' => false, + 'direction' => $direction, + 'processed' => 0, + 'message' => 'Kierunek orderPRO -> Allegro nie jest jeszcze wdrożony.', + ]; + } + ``` + + **W resources/views/settings/allegro.php (linie ~260-267):** + Znajdź select `status_sync_direction`. Zmień opcję `orderpro_to_allegro`: + ```php + + ``` + Dodaj `disabled` do atrybutu opcji. Jeśli aktualnie wybrana wartość to `orderpro_to_allegro` + (istniejące ustawienie w DB), disabled option z selected nadal wyświetla się poprawnie — + użytkownik widzi co było wybrane, ale nie może ponownie wybrać. + + NIE zmieniaj logiki AllegroStatusSyncService poza tym early return. + + + php -l src/Modules/Settings/AllegroStatusSyncService.php + grep -A5 "DIRECTION_ORDERPRO_TO_ALLEGRO" src/Modules/Settings/AllegroStatusSyncService.php + # Powinno pokazać ok: false + grep -A3 "orderpro_to_allegro" resources/views/settings/allegro.php + # Powinno pokazać disabled + + AC-1 i AC-2 satisfied: ok:false + opcja UI disabled + + + + Task 2: Lista zamówień — source przed ID, etykieta integracji, prefix "ID:" + resources/views/orders/list.php + + Przeczytaj plik `resources/views/orders/list.php` dokładnie. + Znajdź fragment renderujący `orders-ref__meta` (lub analogiczny div z source i order ID). + + Obecna kolejność (wg todo.md item 15): + ```html +
+ [ID zamówienia] + [source: "allegro"/"shopPRO"] +
+ ``` + + **Zmiana 1 (item 15): Odwróć kolejność** — source przed ID: + ```html +
+ [source] + ID: [ID zamówienia] +
+ ``` + + **Zmiana 2 (item 17): Zamiast "shopPRO" pokaż konkretną nazwę integracji.** + Sprawdź jakie pole w danych zamówienia zawiera nazwę integracji. + Odczytaj co jest dostępne w `$order` array w widoku — szukaj pól: `integration_name`, + `source`, `source_name`, `integration_id`. + + Jeśli `$order['source']` zawiera np. "shoppro" a konkretna nazwa integracji jest dostępna + jako osobne pole — użyj tej nazwy dla shopPRO, zachowaj "allegro" dla Allegro. + + Jeśli nie ma pola z konkretną nazwą integracji w danych zwracanych przez OrdersRepository, + sprawdź co jest dostępne i użyj dostępnego pola. Nie dodawaj nowych DB queries w widoku. + + Dodaj prefix "ID: " przed source_order_id lub external_order_id (którego używa widok). +
+ + php -l resources/views/orders/list.php + grep -n "orders-ref__meta\|source_order_id\|source.*span\|ID:" resources/views/orders/list.php + + AC-3 satisfied: source przed ID, prefix "ID:", integracja zamiast "shopPRO" +
+ + + + 1. AllegroStatusSyncService zwraca ok:false dla orderpro_to_allegro + 2. UI opcja "orderPRO → Allegro" oznaczona jako disabled/(wkrótce) + 3. Lista zamówień: source przed ID, prefix "ID:", etykieta integracji + + + 1. Uruchom aplikację (XAMPP) + 2. Przejdź do Ustawień → Integracje → Allegro → zakładka synchronizacji + - Sprawdź że opcja "orderPRO → Allegro" jest wyświetlona jako disabled/(wkrótce) + 3. Przejdź do listy zamówień (/orders) + - Sprawdź kolejność: source powinien być przed ID + - Sprawdź prefix "ID:" + - Sprawdź że shopPRO pokazuje konkretną nazwę integracji + 4. Wpisz "approved" lub opisz co nie gra + + Wpisz "approved" aby kontynuować do kolorowania statusów i SCSS, lub opisz problemy + + + + Task 3: Statusy kolorowane na liście + ciemniejsze obramowanie formularzy + + resources/views/orders/list.php, + resources/scss/modules/_orders.scss + + + **Item 16: Kolorowanie statusów.** + + Przeczytaj `resources/views/orders/list.php` — znajdź gdzie renderowany jest status zamówienia. + Sprawdź co jest w `$order['effective_status_id']` lub analogicznym polu. + + W OrdersRepository::buildListSql() jest już JOIN do `allegro_order_status_mappings` — + sprawdź jakie kolumny są pobierane z tego JOINu (np. `asm.color` czy `asm.label_color`). + + Jeśli kolor jest już dostępny w danych widoku jako pole `status_color` lub `color` — + dodaj do elementu statusu inline style: + ```php + + > + + + ``` + + Jeśli kolor nie jest dostępny w danych widoku — sprawdź OrdersRepository::transformOrderRow() + i buildListSql() co jest pobierane z asm (allegro_order_status_mappings JOIN). Jeśli kolumna + `color` istnieje w tabeli — dodaj ją do SELECT i transformOrderRow(). Jeśli tabela nie ma + kolumny `color` — nie dodawaj, zaznacz w SUMMARY że wymaga migracji. + + **Item 14: Ciemniejsze obramowanie inputów.** + Znajdź w `resources/scss/modules/_orders.scss` lub `resources/scss/_global.scss` + lub analogicznym pliku SCSS definicję borderów dla `input, select, textarea`. + Zmień kolor border na ciemniejszy o ok. 30-40% (np. z `#ddd` na `#aaa` lub z `#ccc` na `#999`). + + Sprawdź najpierw które pliki SCSS definiują style formularzy — może to być inny plik niż _orders.scss. + Szukaj: `grep -rn "border.*input\|input.*border\|\.form-control" resources/scss/` + + + php -l resources/views/orders/list.php + # Znajdź plik SCSS z border input i zweryfikuj zmianę: + grep -rn "form-control\|input.*border\|border.*input" resources/scss/ | head -10 + + AC-4 i AC-5 satisfied: statusy kolorowane, obramowania ciemniejsze + + +
+ + + +## DO NOT CHANGE +- Logika synchronizacji statusów poza early return (nie implementuj orderpro_to_allegro) +- Inne widoki poza orders/list.php i settings/allegro.php +- Publiczne API controllera AllegroStatusSyncService +- Routing i nazwy tras + +## SCOPE LIMITS +- Nie implementuj synchronizacji orderPRO→Allegro — tylko disable UI + ok:false +- Kolorowanie statusów tylko jeśli kolor jest dostępny bez dodatkowych DB queries w widoku +- Border color — tylko zmiana istniejącej wartości, nie nowy system designu +- Nie ruszaj resources/views/settings/shoppro.php (ma analogiczną opcję — osobny scope) + + + + +Przed zamknięciem planu: +- [ ] php -l AllegroStatusSyncService.php — brak błędów +- [ ] grep ok.*false AllegroStatusSyncService — widoczny w early return +- [ ] grep disabled resources/views/settings/allegro.php — opcja UI +- [ ] php -l resources/views/orders/list.php — brak błędów +- [ ] Checkpoint human-verify zaliczony + + + +- AllegroStatusSyncService: ok:false dla orderpro_to_allegro +- UI: opcja disabled +- Lista zamówień: source | "ID: [id]" w poprawnej kolejności +- Lista zamówień: integracja zamiast "shopPRO" +- Statusy: kolorowane gdy kolor dostępny +- Formularze: ciemniejsze obramowanie + + + +Po zakończeniu utwórz `.paul/phases/07-pre-expansion-fixes/07-03-SUMMARY.md` + diff --git a/.paul/phases/07-pre-expansion-fixes/07-04-PLAN.md b/.paul/phases/07-pre-expansion-fixes/07-04-PLAN.md new file mode 100644 index 0000000..a2e5ec8 --- /dev/null +++ b/.paul/phases/07-pre-expansion-fixes/07-04-PLAN.md @@ -0,0 +1,179 @@ +--- +phase: 07-pre-expansion-fixes +plan: 04 +type: tdd +wave: 2 +depends_on: [] +files_modified: + - tests/Unit/AllegroTokenManagerTest.php + - tests/Unit/AllegroOrderImportServiceTest.php +autonomous: true +--- + + +## Goal +Dodać testy jednostkowe dla dwóch krytycznych ścieżek: logiki odświeżania tokenów OAuth (AllegroTokenManager) i happy path importu zamówień Allegro (AllegroOrderImportService). PHPUnit jest już skonfigurowany — testy muszą przejść `vendor/bin/phpunit`. + +## Purpose +AllegroTokenManager zawiera złożone edge case'y (token expired, refresh token empty, write-then-re-read) które są krytyczne dla działania wszystkich integracji Allegro. Bez testów każda zmiana w okolicach token managementu jest ryzykowna. AllegroOrderImportService importSingleOrder() ma catch(Throwable) w kilku miejscach — bez testów błędy mogą być swallowane po cichu. + +## Output +- `tests/Unit/AllegroTokenManagerTest.php` — 5+ testów pokrywających logikę tokenów +- `tests/Unit/AllegroOrderImportServiceTest.php` — 3+ testy happy path + jedna ścieżka błędu + + + +## Project Context +@.paul/PROJECT.md + +## Source Files +@src/Modules/Settings/AllegroTokenManager.php +@src/Modules/Settings/AllegroOrderImportService.php +@tests/bootstrap.php +@phpunit.xml + + + + +## AC-1: AllegroTokenManager — logika refresh pokryta testami +```gherkin +Given AllegroTokenManager zarządza tokenami OAuth z logiką: odśwież jeśli wygaśnie w ciągu 5 min +When testy jednostkowe są uruchamiane przez PHPUnit +Then istnieje test dla: token świeży (brak refresh) + AND test dla: token wygasły lub wygaśnie za < 5 min (refresh triggered) + AND test dla: brak refresh token (oczekiwany wyjątek lub ok:false) + AND wszystkie testy przechodzą: vendor/bin/phpunit tests/Unit/AllegroTokenManagerTest.php +``` + +## AC-2: AllegroOrderImportService — import happy path pokryty +```gherkin +Given AllegroOrderImportService::importSingleOrder() pobiera zamówienie i zapisuje je do DB +When testy jednostkowe są uruchamiane +Then istnieje test dla: import sukces (pełne zamówienie ze wszystkimi polami) + AND test dla: import zwraca dane zamówienia (assert na kluczowe pola odpowiedzi) + AND testy nie uderzają w prawdziwą bazę ani API (mocki/stubs) + AND testy przechodzą: vendor/bin/phpunit tests/Unit/AllegroOrderImportServiceTest.php +``` + +## AC-3: Wszystkie nowe testy przechodzą +```gherkin +Given nowe pliki testowe istnieją w tests/Unit/ +When uruchamiasz vendor/bin/phpunit +Then zero FAILURES, zero ERRORS dla nowych test files +``` + + + + + + + Task 1: Testy dla AllegroTokenManager + + tests/Unit/AllegroTokenManagerTest.php, + src/Modules/Settings/AllegroTokenManager.php + + + Przeczytaj `src/Modules/Settings/AllegroTokenManager.php` dokładnie. + Zrozum: konstruktor, zależności, logika `resolveToken()`, kiedy refresh jest wywoływany. + + Stwórz `tests/Unit/AllegroTokenManagerTest.php`: + ```php + now + 5min → resolveToken() zwraca token bez refresh + 2. **Token wygaśnie za < 5 min** — expires_at < now + 300s → resolveToken() wywołuje refresh + 3. **Token już wygasły** — expires_at < now → resolveToken() wywołuje refresh + 4. **Brak refresh token** — token wygasły, refresh_token pusty → oczekiwany wyjątek lub failure signal + 5. **Write-then-re-read** — po refresh, token jest odczytany z repo (nie z odpowiedzi API) + + Używaj PHPUnit Mock Objects dla zależności (AllegroOAuthClient, repository). + Sprawdź które dependency injection AllegroTokenManager przyjmuje w konstruktorze. + Mockuj zewnętrzne zależności — testy NIE mogą uderzać w Allegro API ani DB. + + Stosuj `setUp()` żeby nie powtarzać kodu inicjalizacyjnego. + + + php -l tests/Unit/AllegroTokenManagerTest.php + vendor/bin/phpunit tests/Unit/AllegroTokenManagerTest.php --testdox + + AC-1 i AC-3 satisfied: 5+ testów AllegroTokenManager, wszystkie zielone + + + + Task 2: Testy dla AllegroOrderImportService + + tests/Unit/AllegroOrderImportServiceTest.php, + src/Modules/Settings/AllegroOrderImportService.php + + + Przeczytaj `src/Modules/Settings/AllegroOrderImportService.php` dokładnie. + Zidentyfikuj: zależności konstruktora, `importSingleOrder()` flow, co zwraca. + + Stwórz `tests/Unit/AllegroOrderImportServiceTest.php`. + + Wymagane scenariusze (co najmniej): + 1. **Happy path** — API zwraca poprawne zamówienie → importSingleOrder() zwraca sukces + - Mock AllegroApiClient który zwraca fixture z polami zamówienia + - Assert że wynik zawiera ['ok' => true] lub odpowiednik sukcesu + - Assert że OrdersRepository::upsert (lub odpowiednia metoda) była wywołana + 2. **401 retry** — jeśli importSingleOrder() ma logikę retry przy 401 → test że retry jest wywoływany + 3. **API error** — AllegroApiClient rzuca wyjątek → importSingleOrder() zwraca ['ok' => false] + lub propaguje wyjątek (sprawdź aktualną semantykę) + + Uwaga na catch(Throwable) bloki — sprawdź czy są testowane i czy swallują w sposób + widoczny (logowanie) czy całkowicie cichy. Jeśli całkowicie cichy — zanotuj w SUMMARY. + + Używaj fixture danych (tablica PHP) dla response API — nie potrzebujesz realnej struktury + API, wystarczy minimum wymagane przez metodę mapującą. + + + php -l tests/Unit/AllegroOrderImportServiceTest.php + vendor/bin/phpunit tests/Unit/AllegroOrderImportServiceTest.php --testdox + + AC-2 i AC-3 satisfied: 3+ testów AllegroOrderImportService, wszystkie zielone + + + + + + +## DO NOT CHANGE +- Logika produkcyjna AllegroTokenManager i AllegroOrderImportService — tylko testy +- Istniejące pliki w tests/ — bootstrap.php, inne pliki Unit (jeśli istnieją) +- phpunit.xml — nie modyfikuj konfiguracji, tylko dodaj nowe pliki testowe + +## SCOPE LIMITS +- Tylko testy jednostkowe z mockami — bez testów integracyjnych z realnym DB/API +- Nie dodawaj testów dla innych klas poza AllegroTokenManager i AllegroOrderImportService +- Jeśli zależności są trudne do mockowania — użyj minimalnego zestawu testów (happy path + 1 error path) +- ShopproOrdersSyncService — nie w tym planie + + + + +Przed zamknięciem planu: +- [ ] php -l tests/Unit/AllegroTokenManagerTest.php +- [ ] php -l tests/Unit/AllegroOrderImportServiceTest.php +- [ ] vendor/bin/phpunit tests/Unit/ — zero FAILURES, zero ERRORS +- [ ] AllegroTokenManagerTest.php: min. 4 metody testowe +- [ ] AllegroOrderImportServiceTest.php: min. 3 metody testowe + + + +- tests/Unit/AllegroTokenManagerTest.php: 5+ testów, zielone +- tests/Unit/AllegroOrderImportServiceTest.php: 3+ testów, zielone +- vendor/bin/phpunit --testdox: czyste wyjście dla obu plików + + + +Po zakończeniu utwórz `.paul/phases/07-pre-expansion-fixes/07-04-SUMMARY.md` + diff --git a/.paul/phases/07-pre-expansion-fixes/07-05-PLAN.md b/.paul/phases/07-pre-expansion-fixes/07-05-PLAN.md new file mode 100644 index 0000000..fe6b5ab --- /dev/null +++ b/.paul/phases/07-pre-expansion-fixes/07-05-PLAN.md @@ -0,0 +1,220 @@ +--- +phase: 07-pre-expansion-fixes +plan: 05 +type: execute +wave: 2 +depends_on: [] +files_modified: + - src/Modules/Settings/InpostShipmentService.php + - src/Modules/Shipments/ShipmentController.php + - src/Modules/Shipments/ShipmentProviderRegistry.php + - src/Core/Application.php +autonomous: false +--- + + +## Goal +Zastąpić workaround `inpost → allegro_wza` prawdziwym `InpostShipmentService implements ShipmentProviderInterface`, korzystającym z credentiali z `InpostIntegrationRepository`. + +## Purpose +Obecnie InPost shipments są tworzone przez Allegro WZA API, co oznacza że użytkownik potrzebuje aktywnej integracji Allegro żeby nadawać przez InPost. To jest cichy bloker dla użytkowników z InPost-only. Każdy nowy przewoźnik dodany bez własnego providera będzie powielał ten pattern. + +## Output +- `src/Modules/Settings/InpostShipmentService.php` — implementacja ShipmentProviderInterface +- `ShipmentController.php` — usunięty workaround remap `inpost → allegro_wza` +- Wiring w Application.php / ShipmentProviderRegistry + + + +## Project Context +@.paul/PROJECT.md + +## Source Files +@src/Modules/Shipments/ShipmentProviderInterface.php +@src/Modules/Shipments/ShipmentController.php +@src/Modules/Settings/InpostIntegrationRepository.php +@src/Modules/Settings/AllegroShipmentService.php + + + + +## AC-1: InpostShipmentService implementuje ShipmentProviderInterface +```gherkin +Given ShipmentProviderInterface definiuje kontrakt dla providerów przesyłek +When tworzysz InpostShipmentService +Then klasa implementuje ShipmentProviderInterface + AND php -l nie zgłasza błędów + AND klasa poprawnie typuje wszystkie wymagane metody interfejsu +``` + +## AC-2: InPost shipments tworzone przez InPost API (nie Allegro WZA) +```gherkin +Given użytkownik ma skonfigurowane credentiale InPost (token w InpostIntegrationRepository) + AND nie ma skonfigurowanej integracji Allegro +When tworzy przesyłkę InPost przez ShipmentController +Then ShipmentController używa InpostShipmentService (nie AllegroShipmentService) + AND workaround "if (inpost) { providerCode = allegro_wza }" jest usunięty +``` + +## AC-3: Brak regresji dla istniejących Allegro WZA shipments +```gherkin +Given użytkownik tworzy przesyłkę przez Allegro WZA (provider_code = allegro_wza) +When ShipmentController przetwarza żądanie +Then flow Allegro WZA działa dokładnie jak przed zmianami + AND InPost nie jest mylony z Allegro WZA +``` + + + + + + + Który endpoint InPost API do tworzenia paczek? + + InPost ma dwa API: + A) Allegro WZA (paczkomaty przez Allegro) — obecny workaround, wymaga Allegro auth + B) InPost ShipX API (natywne API InPost) — bezpośrednie, wymaga tokenu InPost + + InpostIntegrationRepository przechowuje credentiale — sprawdź jakie pola. + Odczytaj: src/Modules/Settings/InpostIntegrationRepository.php + + Opcja A: Zaimplementuj natywny InPost ShipX API (wymaga dokumentacji i tokenu) + Opcja B: Wydziel logikę Allegro WZA do oddzielnego providera bez remapu, + a InPost nadal przez Allegro WZA ale bez tajnego remapu (jawny config) + + + + + + Wybierz: "shipx" lub "allegro-wza-explicit", lub opisz inne podejście + + + + Task 1: Implementuj InpostShipmentService zgodnie z wybraną opcją + + src/Modules/Settings/InpostShipmentService.php, + src/Modules/Settings/InpostIntegrationRepository.php + + + Przeczytaj `src/Modules/Shipments/ShipmentProviderInterface.php` — zrozum wymagany kontrakt. + Przeczytaj `src/Modules/Settings/AllegroShipmentService.php` — jako wzorzec implementacji. + Przeczytaj `src/Modules/Settings/InpostIntegrationRepository.php` — jakie credentiale są dostępne. + + **Jeśli wybrano "shipx" (natywny InPost ShipX API):** + - Stwórz `InpostShipmentService implements ShipmentProviderInterface` + - Konstruktor przyjmuje `InpostIntegrationRepository` (dla tokenu) + - Metoda create(): wywołuje ShipX API endpoint tworzenia przesyłki + - Metoda getLabel(): pobiera etykietę przez ShipX API + - Używaj cURL (jak inne ApiClienty) z SSL verification + - Endpointy ShipX: `https://api-shipx-pl.easypack24.net/v1/` + + **Jeśli wybrano "allegro-wza-explicit":** + - Stwórz `InpostShipmentService` który wrapuje `AllegroShipmentService` + - Zamiast tajnego remapu — jawna delegacja + - Konstruktor przyjmuje `AllegroShipmentService $allegroService` + - Każda metoda deleguje do $allegroService + + W obu przypadkach: namespace App\Modules\Settings, php -l musi przejść. + + + php -l src/Modules/Settings/InpostShipmentService.php + grep "implements ShipmentProviderInterface" src/Modules/Settings/InpostShipmentService.php + + AC-1 satisfied: InpostShipmentService implementuje ShipmentProviderInterface + + + + Task 2: Usuń workaround z ShipmentController, podłącz InpostShipmentService + + src/Modules/Shipments/ShipmentController.php, + src/Core/Application.php + + + **W ShipmentController.php:** + Znajdź linie ~164-166: + ```php + if ($providerCode === 'inpost') { + $providerCode = 'allegro_wza'; + } + ``` + Usuń ten blok. ShipmentProviderRegistry powinien teraz zawierać 'inpost' jako zarejestrowany provider. + + Sprawdź czy jest podobny remap w innych miejscach ShipmentController (linia ~239, ~286) — usuń wszystkie. + + **W Application.php (lub ShipmentProviderRegistry):** + Znajdź gdzie rejestrowane są shipment providers. + Dodaj rejestrację InpostShipmentService pod kluczem 'inpost': + ```php + $inpostService = new InpostShipmentService(...); // odpowiednie zależności + $providerRegistry->register('inpost', $inpostService); + ``` + Sprawdź jak AllegroShipmentService jest rejestrowany — użyj tego samego wzorca. + + NIE usuwaj rejestracji allegro_wza — nadal musi działać. + + + php -l src/Modules/Shipments/ShipmentController.php + php -l src/Core/Application.php + grep -n "inpost.*allegro_wza\|allegro_wza.*inpost" src/Modules/Shipments/ShipmentController.php + # Powinno zwrócić 0 — remap usunięty + + AC-2 i AC-3 satisfied: workaround usunięty, InpostShipmentService zarejestrowany + + + + + InpostShipmentService zaimplementowany i podłączony. + Workaround remap inpost→allegro_wza usunięty. + + + 1. Uruchom aplikację (XAMPP) + 2. Przejdź do tworzenia przesyłki InPost + 3. Sprawdź że przesyłka InPost jest tworzona (lub zwraca czytelny błąd bez PHP errors) + 4. Sprawdź że tworzenie przesyłki Allegro WZA nadal działa + 5. Sprawdź logi PHP — brak Fatal errors + + Wpisz "approved" jeśli działa, lub opisz błędy + + + + + + +## DO NOT CHANGE +- AllegroShipmentService — nie modyfikuj istniejącej logiki +- ShipmentProviderInterface — nie zmieniaj kontraktu +- Routing (routes/web.php) — nie zmieniaj URL endpointów + +## SCOPE LIMITS +- Nie implementuj InPost tracking/status sync — tylko create shipment + label +- Nie zmieniaj UI wyboru przewoźnika — tylko backend provider +- Apaczka, InPost paczkomat przez Allegro WZA nadal działa niezależnie + + + + +Przed zamknięciem planu: +- [ ] php -l InpostShipmentService.php +- [ ] php -l ShipmentController.php +- [ ] grep inpost.*allegro_wza ShipmentController.php — 0 wyników +- [ ] Checkpoint human-verify zaliczony + + + +- InpostShipmentService.php istnieje i implementuje ShipmentProviderInterface +- Remap usunięty z ShipmentController +- Allegro WZA flow bez regresji +- Checkpoint human-verify: approved + + + +Po zakończeniu utwórz `.paul/phases/07-pre-expansion-fixes/07-05-SUMMARY.md` +