wip(06-sonarqube-quality): 6 planów SonarQube Quality utworzonych

Plany dla php:S112, S1142, S1192, S3776, S1448, S138 — gotowe do APPLY.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 10:54:57 +01:00
parent 2d4b52adfc
commit 3a9cfcd4a2
9 changed files with 1545 additions and 11 deletions

View File

@@ -0,0 +1,98 @@
# PAUL Handoff
**Date:** 2026-03-13
**Status:** paused
---
## READ THIS FIRST
You have no prior context. This document tells you everything.
**Project:** orderPRO — aplikacja do zarządzania zamówieniami z wielu kanałów sprzedaży (Allegro, Erli, własne sklepy). Generowanie etykiet kurierskich.
**Core value:** Sprzedawca obsługuje wszystkie kanały i nadaje przesyłki bez przełączania platform.
---
## Current State
**Version:** v0.1.0 (In Progress)
**Phase:** 6 of TBD — 06-sonarqube-quality
**Plan:** 06-01..06-06 — CREATED, awaiting approval
**Loop Position:**
```
PLAN ──▶ APPLY ──▶ UNIFY
✓ ○ ○ [6 planów gotowych, żaden nie wykonany]
```
---
## What Was Done
- Przeanalizowano 327 issues SonarQube z CONCERNS.md i DOCS/todo.md
- Zbadano kluczowe pliki (AllegroIntegrationController 923L, ShopproIntegrationsController 901L, ShopproOrdersSyncService 1192L, OrdersRepository 785L)
- Utworzono 6 planów dla fazy 06-sonarqube-quality:
- `06-01-PLAN.md` — php:S112 (95x) — typowane wyjątki zamiast RuntimeException
- `06-02-PLAN.md` — php:S1142 (57x) — redukcja return statements (save() z 5→≤3)
- `06-03-PLAN.md` — php:S1192 (40x) — ekstrakcja literałów do stałych (IntegrationSources, RedirectPaths)
- `06-04-PLAN.md` — php:S3776 (31x) — redukcja złożoności kognitywnej (extract method)
- `06-05-PLAN.md` — php:S1448 (6x) — podział god classes (ShopproOrdersSyncService 39→≤20 metod, AllegroIntegrationController 30→≤15)
- `06-06-PLAN.md` — php:S138 (4x) — skrócenie długich metod (sync 195L, paginate 183L)
- Zaktualizowano ROADMAP.md (Phase 6 dodana)
- Zaktualizowano STATE.md
---
## What's In Progress
- Żaden plan nie jest w trakcie — wszystkie 6 czeka na zatwierdzenie i APPLY
---
## What's Next
**Immediate:** Wybierz plan do wykonania i uruchom `/paul:apply .paul/phases/06-sonarqube-quality/06-XX-PLAN.md`
**Rekomendowana kolejność:**
1. `06-01` (php:S112) — najprostsza zmiana, nowe klasy + podmiana throw
2. `06-03` (php:S1192) — quick win, stałe zamiast literałów
3. `06-02` (php:S1142) — refaktoryzacja metod w 2 kontrolerach
4. `06-06` (php:S138) — skrócenie długich metod
5. `06-04` (php:S3776) — redukcja złożoności (extract method w god classes)
6. `06-05` (php:S1448) — największa zmiana: podział klas (ma checkpoint:human-verify)
**Zależności między planami:**
- `06-01, 06-02, 06-03` — niezależne, wave 1
- `06-04` — niezależny, wave 2 (lepiej przed 06-05)
- `06-05``depends_on: ["06-04"]`, wave 3, nie jest autonomous (ma human-verify)
- `06-06` — wave 2, uważaj na interferencję z 06-05 w ShopproOrdersSyncService
---
## Key Files
| File | Purpose |
|------|---------|
| `.paul/STATE.md` | Live project state |
| `.paul/ROADMAP.md` | Phase overview |
| `.paul/phases/06-sonarqube-quality/06-01-PLAN.md` | S112: Typowane wyjątki |
| `.paul/phases/06-sonarqube-quality/06-02-PLAN.md` | S1142: Redukcja return |
| `.paul/phases/06-sonarqube-quality/06-03-PLAN.md` | S1192: Stałe dla literałów |
| `.paul/phases/06-sonarqube-quality/06-04-PLAN.md` | S3776: Złożoność kognitywna |
| `.paul/phases/06-sonarqube-quality/06-05-PLAN.md` | S1448: Podział god classes |
| `.paul/phases/06-sonarqube-quality/06-06-PLAN.md` | S138: Długie metody |
| `.paul/codebase/CONCERNS.md` | Pełna lista concerns (tech debt, bugs, performance) |
| `DOCS/todo.md` | Lista TODO z SonarQube issues |
---
## Resume Instructions
1. Przeczytaj `.paul/STATE.md` — potwierdź pozycję w loop
2. Wybierz plan (rekomendowane: 06-01 lub 06-03)
3. Uruchom `/paul:apply .paul/phases/06-sonarqube-quality/06-01-PLAN.md`
---
*Handoff created: 2026-03-13*

View File

@@ -8,7 +8,7 @@ orderPRO to narzędzie do wielokanałowego zarządzania sprzedażą. Projekt prz
**v0.1 Initial Release** (v0.1.0)
Status: In progress
Phases: 3 complete, Phase 4 planning
Phases: 5 complete, Phase 6 planning
## Phases
@@ -19,6 +19,7 @@ Phases: 3 complete, Phase 4 planning
| 3 | Tech Debt 2 | 1/1 | ✅ Complete | 2026-03-13 |
| 4 | Schema Docs | 1/1 | ✅ Complete | 2026-03-13 |
| 5 | Tech Debt 3 | 1/1 | ✅ Complete | 2026-03-13 |
| 6 | SonarQube Quality | 0/6 | 🔄 Planning | — |
## Phase Details
@@ -51,6 +52,16 @@ Migracja flash messages z bezpośrednich zapisów `$_SESSION` do abstrakcji `Fla
- **Plan 05-01** — Migrate $_SESSION flash writes to Flash class in OrdersController and ShipmentController — *Complete*
### Phase 6 — SonarQube Quality
Eliminacja 327 issues SonarQube (php:S112, S1142, S1192, S3776, S1448, S138) przez typowane wyjątki, ekstrakcję stałych, redukcję złożoności i podział god classes.
- **Plan 06-01** — php:S112 (95x) — Typowane klasy wyjątków zamiast RuntimeException — *Not started*
- **Plan 06-02** — php:S1142 (57x) — Redukcja liczby return w metodach — *Not started*
- **Plan 06-03** — php:S1192 (40x) — Ekstrakcja powtarzających się literałów do stałych — *Not started*
- **Plan 06-04** — php:S3776 (31x) — Redukcja złożoności kognitywnej metod — *Not started*
- **Plan 06-05** — php:S1448 (6x) — Podział god classes (ShopproOrdersSyncService, AllegroIntegrationController) — *Not started*
- **Plan 06-06** — php:S138 (4x) — Skrócenie zbyt długich metod — *Not started*
---
*Roadmap created: 2026-03-12*
*Last updated: 2026-03-13 after Phase 5 (Tech Debt 3 — Flash migration)*
*Last updated: 2026-03-13 after Phase 6 plans created (SonarQube Quality — 6 plans)*

View File

@@ -10,10 +10,10 @@ See: .paul/PROJECT.md (updated 2026-03-12)
## Current Position
Milestone: v0.1 Initial Release
Phase: 5 of TBD (05-tech-debt-3) — Complete
Plan: 05-01 complete
Status: Phase 5 complete — ready for next phase
Last activity: 2026-03-13 — Phase 05 complete (05-01-SUMMARY.md)
Phase: 6 of TBD (06-sonarqube-quality) — Planning
Plan: 06-01..06-06 created, awaiting approval
Status: PLAN created (6 plans), ready for APPLY
Last activity: 2026-03-13 — Created .paul/phases/06-sonarqube-quality/06-01..06-PLAN.md
Progress:
- Milestone: [███████░░░] ~65%
@@ -22,13 +22,14 @@ Progress:
- Phase 3: [██████████] 100% (1/1 plans complete)
- Phase 4: [██████████] 100% (1/1 plans complete)
- Phase 5: [██████████] 100% (1/1 plans complete)
- Phase 6: [░░░░░░░░░░] 0% (0/6 plans complete)
## Loop Position
Current loop state:
```
PLAN ──▶ APPLY ──▶ UNIFY
[Phase 05 complete — ready for Phase 6 PLAN]
[6 planów utworzonych, oczekują zatwierdzenia]
```
## Accumulated Context
@@ -75,7 +76,7 @@ PLAN ──▶ APPLY ──▶ UNIFY
- **code-review** — wywołać /code-review przed kolejnym UNIFY (pominięto w obydwu planach fazy 01).
### Git State
Last commit: 880ab59
Last commit: 2d4b52a (Phase 04+05 — schema docs + Flash migration)
Branch: main
Feature branches merged: none
@@ -85,9 +86,13 @@ Brak.
## Session Continuity
Last session: 2026-03-13
Stopped at: Phase 05 complete
Next action: /paul:plan dla Phase 6
Resume file: .paul/phases/05-tech-debt-3/05-01-SUMMARY.md
Stopped at: Phase 06 — 6 planów SonarQube Quality utworzonych, żaden nie wykonany
Next action: /paul:apply .paul/phases/06-sonarqube-quality/06-01-PLAN.md (lub 06-03 jako quick win)
Resume file: .paul/HANDOFF-2026-03-13.md
Resume context:
- Faza 06 ma 6 planów (06-01..06-06) wszystkie gotowe w .paul/phases/06-sonarqube-quality/
- Rekomendowana kolejność: 06-01 → 06-03 → 06-02 → 06-06 → 06-04 → 06-05
- 06-05 (god classes) zależy od 06-04 i ma checkpoint:human-verify (nie autonomous)
---
*STATE.md — Updated after every significant action*

View File

@@ -0,0 +1,251 @@
---
phase: 06-sonarqube-quality
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- src/Core/Exceptions/OrderProException.php
- src/Core/Exceptions/AllegroApiException.php
- src/Core/Exceptions/AllegroOAuthException.php
- src/Core/Exceptions/ApaczkaApiException.php
- src/Core/Exceptions/ShipmentException.php
- src/Core/Exceptions/IntegrationConfigException.php
- src/Modules/Settings/AllegroApiClient.php
- src/Modules/Settings/AllegroOAuthClient.php
- src/Modules/Settings/AllegroTokenManager.php
- src/Modules/Settings/AllegroIntegrationRepository.php
- src/Modules/Settings/AllegroIntegrationController.php
- src/Modules/Settings/AllegroOrderImportService.php
- src/Modules/Settings/AllegroOrdersSyncService.php
- src/Modules/Settings/ApaczkaApiClient.php
- src/Modules/Settings/ApaczkaIntegrationRepository.php
- src/Modules/Shipments/ApaczkaShipmentService.php
- src/Modules/Shipments/AllegroShipmentService.php
- src/Modules/Shipments/ShipmentController.php
- src/Modules/Settings/IntegrationSecretCipher.php
- src/Modules/Settings/InpostIntegrationRepository.php
- src/Modules/Settings/ShopproIntegrationsRepository.php
- src/Modules/Settings/ShopproOrdersSyncService.php
- src/Modules/Settings/ShopproPaymentStatusSyncService.php
autonomous: true
---
<objective>
## Goal
Zastąpić 86+ wywołań `throw new RuntimeException()` dedykowanymi klasami wyjątków per moduł — eliminacja naruszeń SonarQube php:S112.
## Purpose
SonarQube flaguje `RuntimeException` jako zbyt ogólny — łapanie `catch (RuntimeException)` nie mówi nic o tym, jaki błąd nastąpił. Typowane wyjątki umożliwiają precyzyjny catch w wywołującym kodzie i poprawiają czytelność stacktrace.
## Output
Hierarchia klas wyjątków w `src/Core/Exceptions/` + podmienione throw w 13 plikach. Liczba S112 w SonarQube spada z 95 do ~5 (pozostałości w Core).
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/STATE.md
## Source Files
@src/Modules/Settings/AllegroApiClient.php
@src/Modules/Settings/AllegroOAuthClient.php
@src/Modules/Settings/AllegroTokenManager.php
@src/Modules/Shipments/ApaczkaShipmentService.php
@src/Modules/Shipments/AllegroShipmentService.php
@src/Modules/Settings/ApaczkaApiClient.php
@src/Modules/Settings/IntegrationSecretCipher.php
</context>
<skills>
## 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 (CLI w katalogu projektu)
</skills>
<acceptance_criteria>
## AC-1: Hierarchia wyjątków istnieje
```gherkin
Given brak folderu src/Core/Exceptions/
When plan zostaje wykonany
Then folder src/Core/Exceptions/ istnieje z klasami: OrderProException, AllegroApiException, AllegroOAuthException, ApaczkaApiException, ShipmentException, IntegrationConfigException
```
## AC-2: Allegro-specific throws podmienione
```gherkin
Given AllegroApiClient, AllegroOAuthClient, AllegroTokenManager rzucają RuntimeException
When plan zostaje wykonany
Then wszystkie throw w tych plikach używają AllegroApiException lub AllegroOAuthException
```
## AC-3: Apaczka-specific throws podmienione
```gherkin
Given ApaczkaApiClient, ApaczkaShipmentService, ApaczkaIntegrationRepository rzucają RuntimeException
When plan zostaje wykonany
Then wszystkie throw w tych plikach używają ApaczkaApiException
```
## AC-4: Shipment throws podmienione
```gherkin
Given AllegroShipmentService, ShipmentController rzucają RuntimeException
When plan zostaje wykonany
Then wszystkie throw używają ShipmentException
```
## AC-5: Brak regresji
```gherkin
Given aplikacja działa przed zmianą
When klasy wyjątków rozszerzają RuntimeException (łańcuch dziedziczenia zachowany)
Then istniejące catch (RuntimeException) w kodzie wywołującym nadal działają
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Utwórz hierarchię klas wyjątków</name>
<files>
src/Core/Exceptions/OrderProException.php,
src/Core/Exceptions/AllegroApiException.php,
src/Core/Exceptions/AllegroOAuthException.php,
src/Core/Exceptions/ApaczkaApiException.php,
src/Core/Exceptions/ShipmentException.php,
src/Core/Exceptions/IntegrationConfigException.php
</files>
<action>
Utwórz folder src/Core/Exceptions/ i pliki klas z namespace App\Core\Exceptions.
Hierarchia:
- OrderProException extends \RuntimeException — baza dla wszystkich własnych wyjątków
- AllegroApiException extends OrderProException — błędy HTTP/JSON Allegro API i OAuth
- AllegroOAuthException extends AllegroApiException — specyficznie błędy OAuth (token refresh, brak tokenów)
- ApaczkaApiException extends OrderProException — błędy API Apaczka (HTTP, curl, payload)
- ShipmentException extends OrderProException — błędy tworzenia/pobierania przesyłek (brak zamówienia, brak providera, brak paczki)
- IntegrationConfigException extends OrderProException — błędy konfiguracji integracji (brak rekordu w DB, brak klucza API, brak sekretu)
Każda klasa: minimalna (tylko class declaration + extends). Brak dodatkowych metod — nie powielać logiki.
Nie dodawaj konstruktorów ani innych metod — klasy wyjątków mają być tylko markerami.
</action>
<verify>php -l src/Core/Exceptions/OrderProException.php (i pozostałe 5 plików) — każdy zwraca "No syntax errors"</verify>
<done>AC-1 satisfied: folder src/Core/Exceptions/ z 6 klasami istnieje i jest poprawny składniowo</done>
</task>
<task type="auto">
<name>Task 2: Podmień RuntimeException w plikach Allegro</name>
<files>
src/Modules/Settings/AllegroApiClient.php,
src/Modules/Settings/AllegroOAuthClient.php,
src/Modules/Settings/AllegroTokenManager.php,
src/Modules/Settings/AllegroIntegrationRepository.php,
src/Modules/Settings/AllegroIntegrationController.php,
src/Modules/Settings/AllegroOrderImportService.php,
src/Modules/Settings/AllegroOrdersSyncService.php
</files>
<action>
W każdym pliku:
1. Dodaj use App\Core\Exceptions\AllegroApiException; (i AllegroOAuthException tam gdzie dotyczy OAuth)
2. Zastąp throw new RuntimeException(...) odpowiednią klasą:
- AllegroApiClient.php (18x): HTTP/JSON/curl błędy → AllegroApiException; błędy OAuth (ALLEGRO_HTTP_401) → AllegroApiException
- AllegroOAuthClient.php (6x): wszystkie → AllegroOAuthException
- AllegroTokenManager.php (3x): brak połączenia OAuth, brak danych, niepowodzenie refresh → AllegroOAuthException
- AllegroIntegrationRepository.php (1x): brak rekordu → IntegrationConfigException (użyj use App\Core\Exceptions\IntegrationConfigException)
- AllegroIntegrationController.php (1x): brak credentials → IntegrationConfigException
- AllegroOrderImportService.php (2x): błędy importu → AllegroApiException
- AllegroOrdersSyncService.php (1x): brak aktywnej integracji → IntegrationConfigException
Uwaga: NIE zmieniaj logiki catch bloków — tylko throw. NIE zmieniaj komunikatów błędów.
Uwaga: \RuntimeException w AllegroIntegrationController (jeśli catch) — NIE ruszać.
</action>
<verify>
php -l src/Modules/Settings/AllegroApiClient.php
php -l src/Modules/Settings/AllegroOAuthClient.php
php -l src/Modules/Settings/AllegroTokenManager.php
grep -r "new RuntimeException" src/Modules/Settings/Allegro*.php — powinno zwrócić 0 wyników
</verify>
<done>AC-2 satisfied: pliki Allegro nie zawierają throw new RuntimeException</done>
</task>
<task type="auto">
<name>Task 3: Podmień RuntimeException w plikach Apaczka, Shipment i pozostałych</name>
<files>
src/Modules/Settings/ApaczkaApiClient.php,
src/Modules/Settings/ApaczkaIntegrationRepository.php,
src/Modules/Shipments/ApaczkaShipmentService.php,
src/Modules/Shipments/AllegroShipmentService.php,
src/Modules/Shipments/ShipmentController.php,
src/Modules/Settings/IntegrationSecretCipher.php,
src/Modules/Settings/InpostIntegrationRepository.php,
src/Modules/Settings/ShopproIntegrationsRepository.php,
src/Modules/Settings/ShopproOrdersSyncService.php,
src/Modules/Settings/ShopproPaymentStatusSyncService.php
</files>
<action>
Podmień RuntimeException:
- ApaczkaApiClient.php (9x): curl/HTTP/JSON błędy → ApaczkaApiException
- ApaczkaIntegrationRepository.php (4x): brak rekordu/klucza → IntegrationConfigException
- ApaczkaShipmentService.php (15x): brak zamówienia/paczki/etykiety → ShipmentException; brak konfiguracji (brak app_id) → IntegrationConfigException; brak danych nadawcy → IntegrationConfigException
- AllegroShipmentService.php (8x): brak zamówienia/paczki/przesyłki → ShipmentException; brak danych nadawcy → IntegrationConfigException
- ShipmentController.php (3x): nieznany provider, brak paczki, brak providera → ShipmentException
- IntegrationSecretCipher.php (3x): brak sekretu, błąd szyfrowania → IntegrationConfigException
- InpostIntegrationRepository.php (1x): brak rekordu → IntegrationConfigException
- ShopproIntegrationsRepository.php (1x): INTEGRATION_NOT_FOUND → IntegrationConfigException
- ShopproOrdersSyncService.php (2x): brak danych API, błąd pobierania zamówień → \RuntimeException (zostawić — są wewnątrz try-catch i message pochodzi z zewnętrznego API)
- ShopproPaymentStatusSyncService.php (1x): zostawić jako \RuntimeException (message z zewnętrznego API)
W każdym pliku dodaj odpowiednie use statements na górze.
NIE zmieniaj logiki catch — tylko throw.
</action>
<verify>
php -l src/Modules/Shipments/ApaczkaShipmentService.php
php -l src/Modules/Shipments/AllegroShipmentService.php
php -l src/Modules/Settings/ApaczkaApiClient.php
grep -rn "new RuntimeException" src/ — powinno zwrócić maks. 5 wyników (ShopproOrdersSyncService, ShopproPaymentStatusSyncService, CronRunner, AllegroTokenRefreshHandler — intentional pozostałości)
</verify>
<done>AC-3, AC-4, AC-5 satisfied: Apaczka i Shipment pliki używają typowanych wyjątków; łańcuch dziedziczenia od RuntimeException zachowany</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Bloki catch (RuntimeException) w controllers/handlers — łańcuch dziedziczenia zachowuje kompatybilność, ale NIE ruszaj istniejących catch
- Komunikaty błędów w throw — tylko typ wyjątku się zmienia
- src/Core/Exceptions/ — nie tworzyć tu nic poza 6 klasami z tego planu
- routes/web.php
- Pliki widoków (resources/views/)
## SCOPE LIMITS
- Nie refaktoryzuj logiki poza podmianą throw
- Nie dodawaj konstruktorów ani metod do klas wyjątków
- Nie zmieniaj sposobu obsługi błędów w UI (flash messages, HTTP redirecty)
- Core wyjątki (Router.php, Template.php, Translator.php, ConnectionFactory.php, Migrator.php) — zostawić jako RuntimeException (mają sens jako generyczne)
</boundaries>
<verification>
Przed zamknięciem planu:
- [ ] php -l na wszystkich zmodyfikowanych plikach PHP — zero błędów składniowych
- [ ] grep -rn "new RuntimeException" src/ — max 5 wyników (tylko Core + 2 Shoppro intentional)
- [ ] ls src/Core/Exceptions/ — 6 plików: OrderProException, AllegroApiException, AllegroOAuthException, ApaczkaApiException, ShipmentException, IntegrationConfigException
- [ ] Aplikacja startuje bez błędów (wejście na stronę główną, brak PHP fatal errors w logach)
- [ ] sonar-scanner uruchomiony — sprawdź czy S112 violations zmalały
</verification>
<success_criteria>
- Wszystkie 3 taski ukończone
- Zero błędów składniowych PHP
- grep "new RuntimeException" src/ zwraca ≤5 wyników
- SonarQube S112 spada z 95 do ≤10
</success_criteria>
<output>
Po zakończeniu utwórz `.paul/phases/06-sonarqube-quality/06-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,190 @@
---
phase: 06-sonarqube-quality
plan: 02
type: execute
wave: 1
depends_on: []
files_modified:
- src/Modules/Settings/AllegroIntegrationController.php
- src/Modules/Settings/ShopproIntegrationsController.php
autonomous: true
---
<objective>
## Goal
Zredukować liczbę `return` w metodach do maksymalnie 3 — eliminacja naruszeń SonarQube php:S1142 (57 wystąpień).
## Purpose
Metody z 4+ returnami (np. `save()` z 5 returnami) są trudne do śledzenia — nie wiadomo jaką ścieżką wróciła wartość. Wydzielenie walidacji do osobnych metod upraszcza główną ścieżkę logiki.
## Output
`AllegroIntegrationController` i `ShopproIntegrationsController` z metodami o max 3 return statements. SonarQube S1142 spada o ~15-20 violations.
</objective>
<context>
## Project Context
@.paul/PROJECT.md
## Source Files
@src/Modules/Settings/AllegroIntegrationController.php
@src/Modules/Settings/ShopproIntegrationsController.php
</context>
<skills>
## 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
</skills>
<acceptance_criteria>
## AC-1: AllegroIntegrationController::save() — max 3 return
```gherkin
Given save() ma 5 return statements (walidacja CSRF, URL, credentials, dates, etc.)
When refaktoryzujesz przez wydzielenie walidacji do private validateSaveInput(): ?string
Then save() ma 3 return statements; zachowanie identyczne
```
## AC-2: AllegroIntegrationController::saveImportSettings() — max 3 return
```gherkin
Given saveImportSettings() ma 4 return statements
When wydzielasz walidację do private validateImportSettingsInput(): ?string
Then saveImportSettings() ma 3 return statements
```
## AC-3: AllegroIntegrationController::oauthCallback() — max 3 return
```gherkin
Given oauthCallback() ma 4 return statements (walidacja state, code, brak credentials, etc.)
When wydzielasz walidację parametrów do private validateOAuthCallbackParams(): ?string
Then oauthCallback() ma 3 return statements
```
## AC-4: ShopproIntegrationsController::save() — max 3 return
```gherkin
Given save() ma 5 return statements
When wydzielasz walidację do private validateSaveInput(): ?string
Then save() ma 3 return statements
```
## AC-5: Brak regresji funkcjonalnej
```gherkin
Given formularze integracji Allegro i Shoppro działają przed zmianą
When refaktoryzacja jest czysto strukturalna (ta sama logika, inne rozłożenie)
Then wszystkie walidacje nadal działają, komunikaty błędów identyczne
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Refaktoryzacja AllegroIntegrationController — redukcja return w save(), saveImportSettings(), oauthCallback()</name>
<files>src/Modules/Settings/AllegroIntegrationController.php</files>
<action>
Przeczytaj dokładnie trzy metody: save(), saveImportSettings(), oauthCallback().
**Wzorzec refaktoryzacji (stosuj w każdej metodzie):**
- Wydziel bloki walidacji (if + return z błędem) do private helper: validateXxxInput(array $input): ?string
- Helper zwraca komunikat błędu lub null (gdy wszystko OK)
- W głównej metodzie: $error = $this->validateXxxInput($data); if ($error !== null) { [flash + redirect z $error]; return; }
- Dzięki temu główna metoda ma: 1 return po walidacji + 1 return po logice = 2 return zamiast 5
**save() (linia ~115):**
- Wydziel do private validateSaveInput(array $data): ?string
- Walidacje do wydzielenia: CSRF check, URL validation, credentials check, date validations
- Zwraca pierwszy napotkany błąd jako string, lub null
**saveImportSettings() (linia ~169):**
- Wydziel do private validateImportSettingsInput(array $data): ?string
- Walidacje: CSRF, date range checks, inne
**oauthCallback() (linia ~403):**
- Wydziel do private validateOAuthCallbackParams(array $params): ?string
- Walidacje: state parameter, code parameter, CSRF state match
Zasady:
- NIE zmieniaj logiki biznesowej — tylko strukturę
- NIE zmieniaj komunikatów błędów ani flash keys
- NIE zmieniaj redirect paths
- Nowe private metody dodaj na końcu klasy, przed ostatnim }
</action>
<verify>
php -l src/Modules/Settings/AllegroIntegrationController.php
grep -c "return" w obrębie save(), saveImportSettings(), oauthCallback() — każda ≤3
</verify>
<done>AC-1, AC-2, AC-3 satisfied: trzy metody mają ≤3 return statements; składnia poprawna</done>
</task>
<task type="auto">
<name>Task 2: Refaktoryzacja ShopproIntegrationsController — redukcja return w save() i innych metodach</name>
<files>src/Modules/Settings/ShopproIntegrationsController.php</files>
<action>
Przeczytaj dokładnie: save(), test(), saveStatusMappings(), syncStatuses().
**save() (linia ~112):**
- Wydziel do private validateSaveInput(array $data): ?string
- Walidacje: CSRF, URL, credentials, data range checks
- Docelowo: ≤3 return w save()
**test() (linia ~202):**
- 3 return — sprawdź czy można zredukować do 2 przez wydzielenie warunków wejściowych
- Jeśli nie da się bez utraty czytelności, zostaw (SonarQube limit to 3 — akceptowalne)
**saveStatusMappings() (linia ~238):**
- Jeśli ma 4+ return: wydziel wstępną walidację
**syncStatuses() (linia ~301):**
- Jeśli ma 4+ return: wydziel walidację
Stosuj ten sam wzorzec co Task 1: private validateXxxInput(): ?string.
Nowe private metody dodaj na końcu klasy.
NIE zmieniaj logiki, komunikatów, redirect paths.
</action>
<verify>
php -l src/Modules/Settings/ShopproIntegrationsController.php
Ręczne przeliczenie: save(), saveStatusMappings(), syncStatuses() — każda ≤3 return
</verify>
<done>AC-4 satisfied: ShopproIntegrationsController::save() i inne metody mają ≤3 return statements</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Logika biznesowa (warunki, komunikaty błędów, redirect paths, flash keys)
- Inne metody poza wymienionymi — nie ruszaj
- Pliki widoków, routes/web.php, inne kontrolery
## SCOPE LIMITS
- Refaktoryzacja strukturalna tylko w 2 plikach
- Nowe private helper methods — tylko walidacja (nie wydzielaj logiki zapisu do bazy)
- Nie wydzielaj metod do osobnych klas (to jest zakres planu 06-05)
- Nie zmieniaj visibility metod (public/private)
</boundaries>
<verification>
Przed zamknięciem planu:
- [ ] php -l src/Modules/Settings/AllegroIntegrationController.php — brak błędów
- [ ] php -l src/Modules/Settings/ShopproIntegrationsController.php — brak błędów
- [ ] Ręczne sprawdzenie: save() i oauthCallback() w AllegroIntegrationController — ≤3 return każda
- [ ] Ręczne sprawdzenie: save() w ShopproIntegrationsController — ≤3 return
- [ ] sonar-scanner uruchomiony — S1142 violations zmalały
</verification>
<success_criteria>
- Oba pliki bez błędów składniowych
- save(), saveImportSettings(), oauthCallback() (Allegro) — ≤3 return każda
- save() (Shoppro) — ≤3 return
- SonarQube S1142 spada o min. 10 violations
</success_criteria>
<output>
Po zakończeniu utwórz `.paul/phases/06-sonarqube-quality/06-02-SUMMARY.md`
</output>

View File

@@ -0,0 +1,231 @@
---
phase: 06-sonarqube-quality
plan: 03
type: execute
wave: 1
depends_on: []
files_modified:
- src/Core/Constants/IntegrationSources.php
- src/Core/Constants/RedirectPaths.php
- src/Modules/Settings/AllegroIntegrationController.php
- src/Modules/Settings/ShopproIntegrationsController.php
- src/Modules/Orders/OrdersRepository.php
- src/Modules/Settings/AllegroOrdersSyncService.php
- src/Modules/Settings/AllegroStatusSyncService.php
- src/Modules/Settings/AllegroOrderImportService.php
- src/Modules/Settings/ShopproOrdersSyncService.php
- src/Modules/Settings/ShopproStatusSyncService.php
- src/Modules/Settings/ShopproPaymentStatusSyncService.php
autonomous: true
---
<objective>
## Goal
Wyciągnąć 40+ powtarzających się literałów string do stałych — eliminacja naruszeń SonarQube php:S1192.
## Purpose
Ciągi jak `'allegro'` (23x), `'shoppro'` (15x), redirect paths (5-6x) i identyfikatory statusów rozsiane po całym kodzie to pułapki na literówki i utrudnienie przy refaktoryzacji. Stałe zapewniają jedno miejsce zmiany i wykrycie błędu przez IDE.
## Output
Dwie klasy stałych w `src/Core/Constants/`, wszystkie powtórzenia zastąpione — S1192 spada z 40 do ~5.
</objective>
<context>
## Project Context
@.paul/PROJECT.md
## Source Files
@src/Modules/Orders/OrdersRepository.php
@src/Modules/Settings/AllegroIntegrationController.php
@src/Modules/Settings/ShopproIntegrationsController.php
</context>
<skills>
## 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
</skills>
<acceptance_criteria>
## AC-1: Klasy stałych istnieją
```gherkin
Given brak src/Core/Constants/
When plan zostaje wykonany
Then src/Core/Constants/IntegrationSources.php zawiera ALLEGRO, SHOPPRO, APACZKA, INPOST
src/Core/Constants/RedirectPaths.php zawiera stałe dla ścieżek integracji
```
## AC-2: Identyfikatory źródeł podmienione
```gherkin
Given 'allegro' (23x), 'shoppro' (15x), 'apaczka' (10x), 'inpost' (7x) rozsiane w src/
When plan zostaje wykonany
Then wszystkie wystąpienia w plikach PHP (poza SQL query strings) używają IntegrationSources::ALLEGRO etc.
```
## AC-3: Redirect paths podmienione w kontrolerach
```gherkin
Given '/settings/integrations/allegro' (5x), '/settings/integrations/shoppro' (6x) w kontrolerach
When plan zostaje wykonany
Then AllegroIntegrationController i ShopproIntegrationsController używają RedirectPaths::*
```
## AC-4: Brak regresji
```gherkin
Given aplikacja działa z twardymi stringami
When stałe mają identyczne wartości co podmienianie literały
Then zachowanie aplikacji identyczne routing, source matching, redirect paths
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Utwórz klasy stałych IntegrationSources i RedirectPaths</name>
<files>
src/Core/Constants/IntegrationSources.php,
src/Core/Constants/RedirectPaths.php
</files>
<action>
Utwórz folder src/Core/Constants/ i dwa pliki:
**IntegrationSources.php** (namespace App\Core\Constants):
```php
final class IntegrationSources {
public const ALLEGRO = 'allegro';
public const SHOPPRO = 'shoppro';
public const APACZKA = 'apaczka';
public const INPOST = 'inpost';
}
```
**RedirectPaths.php** (namespace App\Core\Constants):
Przejrzyj AllegroIntegrationController i ShopproIntegrationsController i wypisz wszystkie unikalne redirect paths (np. '/settings/integrations/allegro', '/settings/integrations/allegro?tab=settings', '/settings/integrations/shoppro', etc.).
Utwórz stałe dla każdego unikalnego path:
```php
final class RedirectPaths {
public const ALLEGRO_INTEGRATION = '/settings/integrations/allegro';
public const ALLEGRO_SETTINGS_TAB = '/settings/integrations/allegro?tab=settings';
public const ALLEGRO_STATUS_MAPPING_TAB = '/settings/integrations/allegro?tab=status-mapping';
public const ALLEGRO_DELIVERY_TAB = '/settings/integrations/allegro?tab=delivery-mapping';
public const SHOPPRO_INTEGRATION = '/settings/integrations/shoppro';
public const SHOPPRO_SETTINGS_TAB = '/settings/integrations/shoppro?tab=settings';
// ... dodaj wszystkie które znajdziesz w plikach kontrolerów
}
```
Klasy final, bez konstruktora, bez metod — tylko stałe.
</action>
<verify>
php -l src/Core/Constants/IntegrationSources.php
php -l src/Core/Constants/RedirectPaths.php
</verify>
<done>AC-1 satisfied: oba pliki istnieją, poprawna składnia PHP</done>
</task>
<task type="auto">
<name>Task 2: Podmień identyfikatory źródeł w Services i Repositories</name>
<files>
src/Modules/Settings/AllegroOrdersSyncService.php,
src/Modules/Settings/AllegroStatusSyncService.php,
src/Modules/Settings/AllegroOrderImportService.php,
src/Modules/Settings/ShopproOrdersSyncService.php,
src/Modules/Settings/ShopproStatusSyncService.php,
src/Modules/Settings/ShopproPaymentStatusSyncService.php,
src/Modules/Orders/OrdersRepository.php
</files>
<action>
W każdym pliku:
1. Dodaj use App\Core\Constants\IntegrationSources; na górze
2. Podmień literały:
- 'allegro' (gdy to identyfikator źródła/integracji, NIE gdy nazwa tabeli SQL) → IntegrationSources::ALLEGRO
- 'shoppro' → IntegrationSources::SHOPPRO
- 'apaczka' → IntegrationSources::APACZKA
- 'inpost' → IntegrationSources::INPOST
WAŻNE — co NIE podmieniać:
- String w zapytaniach SQL (np. WHERE source = 'allegro' w OrdersRepository) — zostaw literal (SonarQube wyjątek dla SQL)
- Nazwy tabel, kolumn — zostaw
- Klucze tłumaczeń (np. 'settings.allegro.*') — zostaw
- Redirect URL paths — podmieniane w Task 3
Sprawdź kontekst każdego wystąpienia przed podmianą.
</action>
<verify>
php -l na każdym zmodyfikowanym pliku
grep -n "'allegro'" src/Modules/Settings/AllegroOrdersSyncService.php — 0 wyników (lub tylko w SQL/komentarzach)
grep -n "'shoppro'" src/Modules/Settings/ShopproOrdersSyncService.php — 0 wyników (lub tylko w SQL/komentarzach)
</verify>
<done>AC-2 satisfied: identyfikatory źródeł w services/repositories używają IntegrationSources::*</done>
</task>
<task type="auto">
<name>Task 3: Podmień redirect paths i source literals w kontrolerach</name>
<files>
src/Modules/Settings/AllegroIntegrationController.php,
src/Modules/Settings/ShopproIntegrationsController.php
</files>
<action>
W obu plikach:
1. Dodaj use App\Core\Constants\IntegrationSources;
2. Dodaj use App\Core\Constants\RedirectPaths;
3. Podmień redirect path strings → odpowiednie RedirectPaths::*
4. Podmień identyfikatory źródeł 'allegro', 'shoppro' → IntegrationSources::*
Szczególna uwaga:
- Header redirect strings (np. header('Location: /settings/integrations/allegro')) → RedirectPaths::ALLEGRO_INTEGRATION
- URL z tabami → odpowiednie stałe z RedirectPaths
- Nie podmieniaj kluczy tłumaczeń (settings.allegro.*)
- Nie podmieniaj niczego w widokach (resources/views/)
</action>
<verify>
php -l src/Modules/Settings/AllegroIntegrationController.php
php -l src/Modules/Settings/ShopproIntegrationsController.php
grep -c "'/settings/integrations/allegro'" src/Modules/Settings/AllegroIntegrationController.php — 0
grep -c "'/settings/integrations/shoppro'" src/Modules/Settings/ShopproIntegrationsController.php — 0
</verify>
<done>AC-3, AC-4 satisfied: redirect paths podmienione; aplikacja zachowuje identyczne zachowanie</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Zapytania SQL z literałami (np. 'allegro' w WHERE clauses) — SonarQube nie flaguje string w SQL
- Klucze tłumaczeń (settings.allegro.*, settings.integrations.*)
- Pliki widoków (resources/views/)
- routes/web.php
- Nazwy tabel i kolumn w SQL
## SCOPE LIMITS
- Tylko pliki wymienione w files_modified
- Nie tworzyć stałych dla string które pojawiają się tylko 1-2 razy (SonarQube S1192 flaguje 3+ powtórzenia)
- Nie przenosić stałych do modułów — zostają w Core/Constants (globalne identyfikatory)
</boundaries>
<verification>
Przed zamknięciem planu:
- [ ] php -l na wszystkich zmodyfikowanych plikach — zero błędów
- [ ] ls src/Core/Constants/ — IntegrationSources.php, RedirectPaths.php
- [ ] grep -rn "'allegro'" src/ (bez SQL context) — drastycznie zredukowane
- [ ] grep -rn "'shoppro'" src/ (bez SQL context) — drastycznie zredukowane
- [ ] sonar-scanner — S1192 violations zmalały
</verification>
<success_criteria>
- Oba pliki Constants istnieją i są poprawne składniowo
- Wszystkie zmodyfikowane pliki bez błędów PHP
- SonarQube S1192 spada z 40 do ≤10
</success_criteria>
<output>
Po zakończeniu utwórz `.paul/phases/06-sonarqube-quality/06-03-SUMMARY.md`
</output>

View File

@@ -0,0 +1,246 @@
---
phase: 06-sonarqube-quality
plan: 04
type: execute
wave: 2
depends_on: []
files_modified:
- src/Modules/Settings/AllegroIntegrationController.php
- src/Modules/Settings/ShopproIntegrationsController.php
- src/Modules/Settings/ShopproOrdersSyncService.php
autonomous: true
---
<objective>
## Goal
Obniżyć złożoność kognitywną (cognitive complexity) w 5 najbardziej skomplikowanych metodach — eliminacja naruszeń SonarQube php:S3776 (31 wystąpień).
## Purpose
Metody z zagnieżdżonymi if/foreach/switch na 4+ poziomach są trudne do testowania i modyfikacji. Wydzielenie fragmentów do private helper methods upraszcza główny przepływ i obniża wynik SonarQube poniżej progu 15.
## Output
Zmniejszona złożoność w 5 kluczowych metodach w 3 plikach. SonarQube S3776 spada z 31 do ~15.
</objective>
<context>
## Project Context
@.paul/PROJECT.md
## Source Files
@src/Modules/Settings/AllegroIntegrationController.php
@src/Modules/Settings/ShopproIntegrationsController.php
@src/Modules/Settings/ShopproOrdersSyncService.php
</context>
<skills>
## 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
</skills>
<acceptance_criteria>
## AC-1: AllegroIntegrationController::oauthCallback() uproszczone
```gherkin
Given oauthCallback() ma 6 zagnieżdżonych if (validation chain)
When wydzielasz każdy blok walidacji do osobnej private metody
Then oauthCallback() ma max 2 poziomy zagnieżdżenia
```
## AC-2: AllegroIntegrationController::loadDeliveryServices() uproszczone
```gherkin
Given loadDeliveryServices() ma try-catch z 4 poziomami zagnieżdżenia
When wydzielasz ładowanie Allegro services i Apaczka services do osobnych private metod
Then loadDeliveryServices() ma max 2 poziomy zagnieżdżenia
```
## AC-3: ShopproIntegrationsController::saveStatusMappings() uproszczone
```gherkin
Given saveStatusMappings() ma 3 zagnieżdżone pętle (foreach w foreach z if)
When wydzielasz przetwarzanie pojedynczego mappingu do private metody
Then saveStatusMappings() ma max 2 poziomy zagnieżdżenia
```
## AC-4: ShopproOrdersSyncService::sync() uproszczone
```gherkin
Given sync() (195 linii) ma triple nested loop (for pages > foreach candidates > try-catch > if)
When wydzielasz przetwarzanie pojedynczej strony do private processPage() i pojedynczego zamówienia do processCandidate()
Then sync() ma max 3 poziomy zagnieżdżenia; główna pętla jest czytelna
```
## AC-5: Brak regresji
```gherkin
Given funkcjonalność działa przed refaktoryzacją
When zmiany są czysto strukturalne (extract method, nie zmiana logiki)
Then zachowanie identyczne OAuth flow, status mappings, sync działają
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Uproszczenie AllegroIntegrationController — oauthCallback() i loadDeliveryServices()</name>
<files>src/Modules/Settings/AllegroIntegrationController.php</files>
<action>
Przeczytaj oauthCallback() i loadDeliveryServices() dokładnie.
**oauthCallback() — wzorzec uproszczenia:**
Każdy blok `if (warunek) { flash error; return; }` to kandydat do wydzielenia.
Stwórz private validateOAuthState(array $params): bool — sprawdza state parameter
Stwórz private validateOAuthCode(array $params): bool — sprawdza code parameter
Główna metoda: series of guard checks bez zagnieżdżenia.
Docelowy kształt:
```php
public function oauthCallback(): void {
if (!$this->validateOAuthState($params)) { [flash + redirect]; return; }
if (!$this->validateOAuthCode($params)) { [flash + redirect]; return; }
// logika bez zagnieżdżenia
}
```
**loadDeliveryServices() — wzorzec uproszczenia:**
Wydziel ładowanie Allegro delivery services do private loadAllegroDeliveryServices(): array
Wydziel ładowanie Apaczka services do private loadApaczkaServices(): array
Główna metoda wywołuje oba i buduje odpowiedź — bez zagnieżdżonych try-catch.
Każda z wydzielonych metod ma swój własny try-catch wewnętrznie.
Zasady:
- NIE zmieniaj logiki — tylko gdzie jest kod
- NIE zmieniaj komunikatów, flash keys, redirect paths
- Nowe private metody na końcu klasy
</action>
<verify>
php -l src/Modules/Settings/AllegroIntegrationController.php
Ręczne sprawdzenie oauthCallback(): max 2 poziomy zagnieżdżenia w ciele metody
Ręczne sprawdzenie loadDeliveryServices(): max 2 poziomy zagnieżdżenia
</verify>
<done>AC-1, AC-2 satisfied: obie metody uproszczone, składnia poprawna</done>
</task>
<task type="auto">
<name>Task 2: Uproszczenie ShopproIntegrationsController — saveStatusMappings()</name>
<files>src/Modules/Settings/ShopproIntegrationsController.php</files>
<action>
Przeczytaj saveStatusMappings() i loadDeliveryServices().
**saveStatusMappings() — wzorzec uproszczenia:**
Jeśli metoda zawiera zagnieżdżone foreach z logiką wewnątrz:
```php
foreach ($mappings as $mapping) {
foreach ($mapping['items'] as $item) {
if (...) {
// logika
}
}
}
```
Wydziel wewnętrzny foreach+if do private processMappingItem(array $item, ...): void
lub private processSingleStatusMapping(array $mapping): void
**loadDeliveryServices() (jeśli jest w Shoppro):**
Ten sam wzorzec co Allegro — wydziel ładowanie per-provider.
Główna zasada: metoda po refaktoryzacji powinna być "czytalna jak lista kroków" — bez zagłębiania się w szczegóły.
NIE zmieniaj logiki biznesowej.
Nowe private metody na końcu klasy.
</action>
<verify>
php -l src/Modules/Settings/ShopproIntegrationsController.php
Ręczne sprawdzenie saveStatusMappings(): max 2 poziomy zagnieżdżenia
</verify>
<done>AC-3 satisfied: saveStatusMappings() uproszczone</done>
</task>
<task type="auto">
<name>Task 3: Uproszczenie ShopproOrdersSyncService::sync()</name>
<files>src/Modules/Settings/ShopproOrdersSyncService.php</files>
<action>
Przeczytaj metodę sync() (ok. 195 linii).
**sync() — wzorzec uproszczenia:**
Obecna struktura (schemat):
```
foreach ($integrations) {
for ($page = 1; ...) {
foreach ($candidates) {
try {
if (...) {
// przetwarzanie zamówienia
}
} catch (...) { ... }
}
}
}
```
Wydziel:
1. private processOrderCandidate(array $candidate, array $integration, array $statusMap, ...): void
- zawiera: try-catch + logikę przetwarzania jednego zamówienia
2. private processPageCandidates(array $candidates, array $integration, array $statusMap, array &$results): void
- zawiera: foreach ($candidates) + wywołanie processOrderCandidate()
Po wydzieleniu sync() powinna wyglądać jak:
```
foreach ($integrations) {
for ($page = 1; ...) {
$candidates = $this->buildCandidates($orders);
$this->processPageCandidates($candidates, $integration, $statusMap, $results);
if (brak kolejnej strony) break;
}
}
```
Zachowaj wszystkie parametry które są potrzebne (przekaż przez argumenty lub &$results).
NIE zmieniaj logiki przetwarzania — tylko gdzie jest kod.
</action>
<verify>
php -l src/Modules/Settings/ShopproOrdersSyncService.php
Ręczne sprawdzenie sync(): max 3 poziomy zagnieżdżenia (foreach integrations > for pages > processPageCandidates)
</verify>
<done>AC-4, AC-5 satisfied: sync() uproszczone, ShopproOrdersSyncService poprawna składniowo</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Logika biznesowa — warunkowa, obliczeniowa, API calls
- Komunikaty błędów, flash keys, redirect paths
- Publiczne sygnatury metod (public function names + parameters)
- Inne metody poza wymienionymi w AC
- Pliki poza files_modified
## SCOPE LIMITS
- Wydzielanie metod tylko wewnątrz istniejącej klasy (nie tworzyć nowych klas — to zakres 06-05)
- Nowe metody: private, na końcu klasy, jasna nazwa opisująca co robią
- Nie optymalizuj algorytmicznie — tylko strukturalnie
</boundaries>
<verification>
Przed zamknięciem planu:
- [ ] php -l na wszystkich 3 plikach — zero błędów
- [ ] oauthCallback() — max 2 poziomy zagnieżdżenia (ręczne sprawdzenie)
- [ ] loadDeliveryServices() (Allegro) — max 2 poziomy zagnieżdżenia
- [ ] saveStatusMappings() (Shoppro) — max 2 poziomy zagnieżdżenia
- [ ] sync() (ShopproOrdersSyncService) — max 3 poziomy zagnieżdżenia
- [ ] sonar-scanner — S3776 violations zmalały
</verification>
<success_criteria>
- Wszystkie 3 pliki bez błędów składniowych
- 5 wskazanych metod z zredukowanym zagnieżdżeniem
- SonarQube S3776 spada z 31 do ≤18
</success_criteria>
<output>
Po zakończeniu utwórz `.paul/phases/06-sonarqube-quality/06-04-SUMMARY.md`
</output>

View File

@@ -0,0 +1,298 @@
---
phase: 06-sonarqube-quality
plan: 05
type: execute
wave: 3
depends_on: ["06-04"]
files_modified:
- src/Modules/Settings/ShopproOrdersSyncService.php
- src/Modules/Settings/ShopproOrderMapper.php
- src/Modules/Settings/ShopproProductImageResolver.php
- src/Modules/Settings/AllegroIntegrationController.php
- src/Modules/Settings/AllegroStatusMappingController.php
- src/Modules/Settings/AllegroDeliveryMappingController.php
autonomous: false
---
<objective>
## Goal
Rozdzielić 2 z 3 god classes przekraczające 20 metod — eliminacja naruszeń SonarQube php:S1448.
- `ShopproOrdersSyncService` (39 metod) → 3 klasy
- `AllegroIntegrationController` (30 metod) → 3 kontrolery
## Purpose
God classes naruszają SRP i S1448. `ShopproOrdersSyncService` z 39 metodami i 1192 liniami jest najtrudniejszą do testowania klasą w projekcie. Podział ułatwia izolowane modyfikacje integracji Shoppro bez ryzyka regresi w całym module.
## Output
5 nowych klas/kontrolerów, `ShopproOrdersSyncService` spada do ~15 metod, `AllegroIntegrationController` do ~12. S1448 spada z 6x do 1x (ShopproIntegrationsController pozostaje na następny plan).
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
## Source Files
@src/Modules/Settings/ShopproOrdersSyncService.php
@src/Modules/Settings/AllegroIntegrationController.php
@routes/web.php
</context>
<skills>
## Required Skills (from SPECIAL-FLOWS.md)
| Skill | Priority | When to Invoke | Loaded? |
|-------|----------|----------------|---------|
| sonar-scanner | required | Po APPLY, przed UNIFY | ○ |
| /code-review | optional | Po implementacji, przed UNIFY | ○ |
## Skill Invocation Checklist
- [ ] sonar-scanner uruchomiony po zakończeniu APPLY
- [ ] /code-review rozważyć (duże zmiany strukturalne)
</skills>
<acceptance_criteria>
## AC-1: ShopproOrderMapper wydzielony
```gherkin
Given ShopproOrdersSyncService zawiera 15+ metod mapujących (mapOrderAggregate, mapAddresses, mapItems, etc.)
When wydzielasz je do src/Modules/Settings/ShopproOrderMapper.php
Then ShopproOrderMapper zawiera wszystkie metody mapowania; ShopproOrdersSyncService wstrzykuje go przez konstruktor
```
## AC-2: ShopproProductImageResolver wydzielony
```gherkin
Given ShopproOrdersSyncService zawiera resolveProductImagesForOrder() i fetchPrimaryProductImageUrl()
When wydzielasz je do src/Modules/Settings/ShopproProductImageResolver.php
Then ShopproProductImageResolver obsługuje image fetching; ShopproOrdersSyncService używa go przez konstruktor
```
## AC-3: ShopproOrdersSyncService ma ≤20 metod
```gherkin
Given ShopproOrdersSyncService ma 39 metod
When wydzielono ShopproOrderMapper i ShopproProductImageResolver
Then ShopproOrdersSyncService ma 20 metod (sync + orchestration + state management)
```
## AC-4: AllegroIntegrationController podzielony
```gherkin
Given AllegroIntegrationController ma 30 metod (status mapping, delivery mapping, main settings)
When wydzielasz AllegroStatusMappingController i AllegroDeliveryMappingController
Then każdy kontroler ma 15 metod; routes/web.php zaktualizowane
```
## AC-5: Brak regresji
```gherkin
Given Shoppro sync i Allegro integration działają przed podziałem
When podział jest czysto strukturalny (move method, nie zmiana logiki)
Then sync Shoppro importuje zamówienia; mapowania Allegro zapisują się; OAuth działa
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Wydziel ShopproOrderMapper z ShopproOrdersSyncService</name>
<files>
src/Modules/Settings/ShopproOrderMapper.php,
src/Modules/Settings/ShopproOrdersSyncService.php
</files>
<action>
Przeczytaj ShopproOrdersSyncService dokładnie. Zidentyfikuj metody mapujące dane zamówień.
**Metody do przeniesienia do ShopproOrderMapper:**
Przenieś wszystkie private metody których jedynym zadaniem jest konwersja danych API → struktury wewnętrzne:
- mapOrderAggregate() — główna metoda mapowania
- mapAddresses() — mapowanie adresów
- mapItems() — mapowanie pozycji
- mapPayments() — mapowanie płatności
- mapShipments() — mapowanie wysyłek
- mapNotes() — mapowanie notatek
- buildInvoiceAddress() — budowanie adresu faktury
- normalizeOrderId(), normalizePaidFlag(), mapPaymentStatus(), sanitizePlainText()
- buildDeliveryMethodLabel(), formatMoneyCompact(), normalizeMediaUrl()
- toFloatOrNull(), toFloatOrDefault(), readPath(), readSinglePath(), composeName()
- hasAddressData(), resolveInvoiceRequested(), parsePickupPoint()
**ShopproOrderMapper (nowy plik):**
- namespace App\Modules\Settings
- class ShopproOrderMapper
- Konstruktor przyjmuje zależności potrzebne metodom (np. ShopproOrderSyncStateRepository jeśli potrzebne)
- Przenieś metody bez zmiany logiki (tylko zmień z private na public te, które sync() wywołuje bezpośrednio)
**ShopproOrdersSyncService (aktualizacja):**
- Dodaj private ShopproOrderMapper $mapper; w konstruktorze
- CronHandlerFactory tworzy ShopproOrdersSyncService — sprawdź src/Modules/Cron/CronHandlerFactory.php i zaktualizuj kompozycję
- Zastąp wywołania $this->mapOrderAggregate() → $this->mapper->mapOrderAggregate()
- Usuń przeniesione metody z ShopproOrdersSyncService
UWAGA: Sprawdź CronHandlerFactory.php — musi przekazać nowy ShopproOrderMapper do konstruktora.
</action>
<verify>
php -l src/Modules/Settings/ShopproOrderMapper.php
php -l src/Modules/Settings/ShopproOrdersSyncService.php
php -l src/Modules/Cron/CronHandlerFactory.php
grep -c "function " src/Modules/Settings/ShopproOrdersSyncService.php — powinno być ≤25 (po Task 1, przed Task 2)
</verify>
<done>AC-1 satisfied: ShopproOrderMapper istnieje z metodami mapowania; ShopproOrdersSyncService go używa</done>
</task>
<task type="auto">
<name>Task 2: Wydziel ShopproProductImageResolver i finalizuj ShopproOrdersSyncService</name>
<files>
src/Modules/Settings/ShopproProductImageResolver.php,
src/Modules/Settings/ShopproOrdersSyncService.php,
src/Modules/Cron/CronHandlerFactory.php
</files>
<action>
**ShopproProductImageResolver (nowy plik):**
- Przenieś z ShopproOrdersSyncService:
- resolveProductImagesForOrder()
- fetchPrimaryProductImageUrl()
- Konstruktor przyjmuje ShopproApiClient (potrzebny do fetch)
- namespace App\Modules\Settings
**ShopproOrdersSyncService (finalizacja):**
- Dodaj private ShopproProductImageResolver $imageResolver;
- Zastąp $this->resolveProductImagesForOrder() → $this->imageResolver->resolveProductImagesForOrder()
- Po Task 1 + Task 2 ShopproOrdersSyncService powinien mieć ≤20 metod
**CronHandlerFactory.php (aktualizacja):**
- Sprawdź gdzie tworzona jest instancja ShopproOrdersSyncService
- Dodaj nowe zależności: new ShopproOrderMapper(...), new ShopproProductImageResolver($shopproApiClient)
- Przekaż do konstruktora ShopproOrdersSyncService
Sprawdź czy ShopproOrderMapper też potrzebuje ShopproApiClient lub innych zależności — jeśli tak, przekaż z CronHandlerFactory.
</action>
<verify>
php -l src/Modules/Settings/ShopproProductImageResolver.php
php -l src/Modules/Settings/ShopproOrdersSyncService.php
php -l src/Modules/Cron/CronHandlerFactory.php
grep -c "function " src/Modules/Settings/ShopproOrdersSyncService.php — ≤20
</verify>
<done>AC-2, AC-3 satisfied: ShopproProductImageResolver wydzielony; ShopproOrdersSyncService ≤20 metod</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<what-built>
ShopproOrdersSyncService podzielony na 3 klasy:
- ShopproOrderMapper (metody mapowania danych)
- ShopproProductImageResolver (pobieranie zdjęć produktów)
- ShopproOrdersSyncService (orchestrator, ≤20 metod)
CronHandlerFactory zaktualizowany z nowymi zależnościami.
</what-built>
<how-to-verify>
1. Uruchom aplikację (XAMPP)
2. Przejdź do Ustawień → Integracje → ShopPRO
3. Uruchom ręczny import zamówień (jeśli dostępny) lub uruchom cron
4. Sprawdź logi crona: importy Shoppro działają bez błędów
5. Sprawdź że zamówienia pojawiają się / nie ma regresji
</how-to-verify>
<resume-signal>Wpisz "approved" aby kontynuować do podziału AllegroIntegrationController, lub opisz błędy</resume-signal>
</task>
<task type="auto">
<name>Task 3: Podziel AllegroIntegrationController na 3 kontrolery</name>
<files>
src/Modules/Settings/AllegroIntegrationController.php,
src/Modules/Settings/AllegroStatusMappingController.php,
src/Modules/Settings/AllegroDeliveryMappingController.php,
routes/web.php
</files>
<action>
Przeczytaj AllegroIntegrationController i routes/web.php dokładnie.
**Podział metod:**
AllegroStatusMappingController (nowy):
- saveStatusMapping()
- saveStatusMappingsBulk()
- deleteStatusMapping()
- syncStatusesFromAllegro()
- buildImportImageWarningMessage() (private helper)
- reasonLabel() (private helper)
AllegroDeliveryMappingController (nowy):
- saveDeliveryMappings()
- loadDeliveryServices()
- (prywatne helpery potrzebne tym metodom)
AllegroIntegrationController (zostaje z):
- index()
- save()
- saveImportSettings()
- startOAuth()
- oauthCallback()
- refreshOAuthToken()
- importSingleOrder()
- (prywatne walidatory i helpery potrzebne tym metodom)
**Nowe pliki kontrolerów:**
- Skopiuj konstruktor i zależności z AllegroIntegrationController
- namespace App\Modules\Settings
- Przenieś metody bez zmiany logiki
**routes/web.php:**
- Utwórz instancje nowych kontrolerów (ten sam wzorzec co istniejące)
- Przepnij route definicje dla:
- status mapping routes → AllegroStatusMappingController
- delivery mapping routes → AllegroDeliveryMappingController
- AllegroIntegrationController obsługuje resztę
Sprawdź które zależności ($this->repository, $this->translator etc.) są potrzebne każdemu kontrolerowi.
</action>
<verify>
php -l src/Modules/Settings/AllegroIntegrationController.php
php -l src/Modules/Settings/AllegroStatusMappingController.php
php -l src/Modules/Settings/AllegroDeliveryMappingController.php
php -l routes/web.php
grep -c "function " src/Modules/Settings/AllegroIntegrationController.php — ≤15
</verify>
<done>AC-4, AC-5 satisfied: AllegroIntegrationController ≤15 metod; nowe kontrolery poprawne; routes zaktualizowane</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Logika biznesowa w żadnej z przenoszonych metod
- Publiczne sygnatury metod (nazwy, parametry)
- Pliki widoków i SCSS
- ShopproIntegrationsController — zakres następnego planu (06-06 jeśli powstanie)
- Inne moduły (Orders, Shipments, Auth, etc.)
## SCOPE LIMITS
- Podział 2 klas (ShopproOrdersSyncService + AllegroIntegrationController)
- ShopproIntegrationsController pomijamy w tym planie (30 metod — podobny refactoring)
- Nie zmieniaj publicznych URL routes — tylko który controller je obsługuje
- CronHandlerFactory — tylko minimalne zmiany potrzebne dla nowych dependencies
</boundaries>
<verification>
Przed zamknięciem planu:
- [ ] php -l na wszystkich nowych i zmodyfikowanych plikach
- [ ] ShopproOrdersSyncService: grep -c "function " ≤20
- [ ] AllegroIntegrationController: grep -c "function " ≤15
- [ ] ShopproOrderMapper.php istnieje z metodami mapowania
- [ ] ShopproProductImageResolver.php istnieje
- [ ] AllegroStatusMappingController.php i AllegroDeliveryMappingController.php istnieją
- [ ] routes/web.php poprawny składniowo
- [ ] Checkpoint human-verify zaliczony (Shoppro sync działa)
- [ ] sonar-scanner — S1448 violations zmalały
</verification>
<success_criteria>
- 4 nowe pliki PHP
- ShopproOrdersSyncService ≤20 metod
- AllegroIntegrationController ≤15 metod
- Zero błędów składniowych
- Shoppro import działa (human-verify passed)
- SonarQube S1448 spada z 6 do ≤2
</success_criteria>
<output>
Po zakończeniu utwórz `.paul/phases/06-sonarqube-quality/06-05-SUMMARY.md`
</output>

View File

@@ -0,0 +1,204 @@
---
phase: 06-sonarqube-quality
plan: 06
type: execute
wave: 2
depends_on: []
files_modified:
- src/Modules/Settings/ShopproOrdersSyncService.php
- src/Modules/Orders/OrdersRepository.php
autonomous: true
---
<objective>
## Goal
Skrócić 4 metody przekraczające limit linii — eliminacja naruszeń SonarQube php:S138. Dodatkowo: `OrdersRepository::paginate()` (183 linie) i `findDetails()` (101 linii) jako najdłuższe metody w module Orders.
## Purpose
Metody o 87-195 liniach są niereviewowalne w MR i trudne do lokalnego rozumowania. Wydzielenie sub-metod ogranicza zakres zmian przy przyszłych modyfikacjach — edytujesz 30-liniową metodę, nie 200-liniowy monolit.
## Output
6 metod skróconych do ≤50 linii każda przez ekstrakcję sub-metod. SonarQube S138 spada z 4 do 0.
**Uwaga na kolejność z planem 06-05:**
Ten plan (06-06) modyfikuje ShopproOrdersSyncService. Plan 06-05 podnosi zależność `depends_on: ["06-04"]`. Jeśli 06-05 jest w trakcie, odłóż zmiany w ShopproOrdersSyncService do po zakończeniu 06-05.
</objective>
<context>
## Project Context
@.paul/PROJECT.md
## Source Files
@src/Modules/Settings/ShopproOrdersSyncService.php
@src/Modules/Orders/OrdersRepository.php
</context>
<skills>
## 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
</skills>
<acceptance_criteria>
## AC-1: ShopproOrdersSyncService — sync() skrócone
```gherkin
Given sync() ma 195 linii
When wydzielasz procesPage() i processOrderCandidate() (jeśli nie zrobione w 06-04)
Then sync() ma 60 linii; wydzielone metody mają 50 linii każda
```
## AC-2: ShopproOrdersSyncService — mapAddresses() skrócone
```gherkin
Given mapAddresses() ma 166 linii (budowanie customer + delivery address)
When wydzielasz buildCustomerAddress(): array i buildDeliveryAddress(): array
Then mapAddresses() ma 50 linii; każda sub-metoda 60 linii
```
## AC-3: OrdersRepository::paginate() skrócone
```gherkin
Given paginate() ma 183 linie (dynamic SQL building + data transformation)
When wydzielasz buildFilters(): array i transformRow(array $row): array
Then paginate() ma 80 linii; sub-metody mają 50 linii każda
```
## AC-4: OrdersRepository::findDetails() skrócone
```gherkin
Given findDetails() ma 101 linii (8 kolejnych zapytań DB)
When wydzielasz osobne private metody per query group (loadOrderItems, loadOrderShipments, etc.)
Then findDetails() ma 50 linii; każdy loader 30 linii
```
## AC-5: Brak regresji
```gherkin
Given lista zamówień i szczegóły zamówienia działają przed zmianą
When zmiany są czysto strukturalne (extract method)
Then paginate() i findDetails() zwracają identyczne dane
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Skróć ShopproOrdersSyncService — sync() i mapAddresses()</name>
<files>src/Modules/Settings/ShopproOrdersSyncService.php</files>
<action>
Przeczytaj ShopproOrdersSyncService dokładnie.
**Sprawdź najpierw:** czy 06-04 już wydzielił processPageCandidates() z sync(). Jeśli tak, sprawdź czy sync() jest już ≤60 linii. Jeśli nie — wydziel.
**sync() (195 linii) — jeśli jeszcze nie zrobione w 06-04:**
Wydziel:
- private processPageCandidates(array $candidates, array $integration, array $statusMap, array &$results): void
→ zawiera foreach ($candidates) + try-catch + logikę przetwarzania 1 zamówienia
- private fetchOrdersPage(array $integration, int $page, string $startDate): array
→ zawiera wywołanie API + obsługę błędów odpowiedzi
**mapAddresses() (166 linii):**
Wydziel:
- private buildCustomerAddress(array $payload): array
→ adres klienta (billing address)
- private buildDeliveryAddress(array $payload): array
→ adres dostawy (shipping address)
- private buildPickupPointData(array $payload): array (jeśli dotyczy)
mapAddresses() staje się kompozytorem: wywołuje buildCustomerAddress() i buildDeliveryAddress().
NIE zmieniaj logiki — tylko gdzie jest kod.
Nowe private metody na końcu klasy (ale przed zamknięciem `}`).
</action>
<verify>
php -l src/Modules/Settings/ShopproOrdersSyncService.php
Ręcznie: sync() ≤60 linii, mapAddresses() ≤50 linii
</verify>
<done>AC-1, AC-2 satisfied: sync() i mapAddresses() skrócone</done>
</task>
<task type="auto">
<name>Task 2: Skróć OrdersRepository — paginate() i findDetails()</name>
<files>src/Modules/Orders/OrdersRepository.php</files>
<action>
Przeczytaj OrdersRepository dokładnie — paginate() i findDetails().
**paginate() (183 linie):**
Ma dwie odrębne odpowiedzialności: budowanie SQL z filtrami + transformacja wierszy.
Wydziel:
- private buildPaginateFilters(array $filters): array
→ zwraca ['conditions' => [], 'bindings' => []]
→ zawiera bloki if ($filter['source']), if ($filter['status']), if ($filter['date_from']), etc.
- private transformOrderRow(array $row): array
→ transformuje jeden wiersz z DB → format gotowy dla widoku
→ jeśli nie ma transformacji per-row, wydziel transformOrderRows(array $rows): array
paginate() staje się: build SQL structure → execute → transform → return.
WAŻNE: Medoo query syntax — zachowaj dokładnie ten sam format przekazywania warunków do Medoo. NIE zmieniaj sposobu budowania prepared statements.
**findDetails() (101 linii):**
Metoda wykonuje 8 kolejnych zapytań DB. Wydziel każde lub grupuj logicznie:
- private loadOrderAddresses(int $orderId): array
- private loadOrderItems(int $orderId): array
- private loadOrderShipments(int $orderId): array
- private loadOrderHistory(int $orderId): array
- (i inne grupy które znajdziesz w metodzie)
findDetails() staje się: załaduj dane orderu + złóż w całość + return.
NIE zmieniaj logiki SQL, kolumn, tabel, sposobu bindingu.
NIE zmieniaj zwracanej struktury danych.
</action>
<verify>
php -l src/Modules/Orders/OrdersRepository.php
Ręcznie: paginate() ≤80 linii, findDetails() ≤50 linii
</verify>
<done>AC-3, AC-4, AC-5 satisfied: paginate() i findDetails() skrócone; brak zmiany logiki SQL</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Logika SQL w OrdersRepository — prepared statements, WHERE conditions, column names
- Zwracana struktura danych z paginate() i findDetails() — widoki zależą od tych kluczy
- Publiczne sygnatury metod
- Inne pliki poza ShopproOrdersSyncService.php i OrdersRepository.php
## SCOPE LIMITS
- Tylko extract method refactoring — nie zmieniaj algorytmów
- Nie przenoś metod do innych klas (to zakres 06-05)
- Nie zmieniaj logiki filtrowania w paginate() — tylko gdzie jest kod
- ShopproOrdersSyncService: uważaj na interferencję z 06-05 (jeśli 06-05 już zmienił tę klasę — dostosuj)
</boundaries>
<verification>
Przed zamknięciem planu:
- [ ] php -l src/Modules/Settings/ShopproOrdersSyncService.php — brak błędów
- [ ] php -l src/Modules/Orders/OrdersRepository.php — brak błędów
- [ ] sync() w ShopproOrdersSyncService: ≤60 linii (ręczne sprawdzenie)
- [ ] mapAddresses(): ≤50 linii
- [ ] paginate(): ≤80 linii
- [ ] findDetails(): ≤50 linii
- [ ] Lista zamówień ładuje się (brak PHP fatal errors)
- [ ] sonar-scanner — S138 violations zmalały
</verification>
<success_criteria>
- Oba pliki bez błędów składniowych
- 4 metody skrócone poniżej limitu SonarQube
- Nowe private helper metody ≤50 linii każda
- SonarQube S138 spada z 4 do 0
</success_criteria>
<output>
Po zakończeniu utwórz `.paul/phases/06-sonarqube-quality/06-06-SUMMARY.md`
</output>