This commit is contained in:
2026-04-03 22:35:49 +02:00
parent 0e7ee957cb
commit e95c4967d2
52 changed files with 7430 additions and 631 deletions

View File

@@ -73,6 +73,7 @@ Sprzedawca moĹĽe obsĹugiwać zamĂłwienia ze wszystkich kanaĹĂłw
- [x] Ochrona danych lokalnych przy re-imporcie + rozroznienie import/aktualizacja w activity log shopPRO — Phase 62
- [x] Import i wyswietlanie personalizacji produktow z shopPRO (custom_fields) + naprawa daty zamowienia — Phase 63
- [x] Data wystawienia paragonu z dokladnoscia do godziny i minuty (DATE -> DATETIME) — Phase 64
- [ ] Eliminacja zduplikowanego kodu: SslCertificateResolver, ToggleableRepositoryTrait, RedirectPathResolver, ReceiptService — Phase 68
### Active (In Progress)

View File

@@ -25,6 +25,10 @@ Wersja mobilna aplikacji, modul po module. Cel: pelna uzywalnosc orderPRO na tel
| 62 | Import Re-import Safety | 1/1 | Complete |
| 63 | Order Item Personalization | 1/1 | Complete |
| 64 | Receipt Datetime Precision | 1/1 | Complete |
| 65 | PAUL Delegated Apply | 1/1 | Complete |
| 66 | Allegro Delivery Tracking | 2/2 | Complete |
| 67 | PAUL Codex Executor | 1/1 | Complete |
| 68 | Code Deduplication Refactor | 0/2 | Planning |
| TBD | Mobile Orders List | - | Not started |
| TBD | Mobile Order Details | - | Not started |
| TBD | Mobile Settings | - | Not started |

View File

@@ -5,19 +5,19 @@
See: .paul/PROJECT.md (updated 2026-04-03)
**Core value:** Sprzedawca moze obslugiwac zamowienia ze wszystkich kanalow sprzedazy i nadawac przesylki bez przelaczania sie miedzy platformami.
**Current focus:** Milestone v3.0 — Phase 64 complete, ready for next PLAN
**Current focus:** Milestone v3.0 — Phase 68 plan 01 complete, ready for next PLAN
## Current Position
Milestone: v3.0 Mobile Responsive — In progress
Phase: 13 of N (64 - Receipt Datetime Precision) — Complete
Plan: 64-01 complete
Status: Loop complete — phase 64 done, ready for next PLAN
Last activity: 2026-04-03 — UNIFY closed for 64-01
Phase: 68 (Code Deduplication Refactor) — In progress
Plan: 68-01 complete
Status: Loop complete — plan 68-01 done, ready for next PLAN
Last activity: 2026-04-03 — UNIFY closed for 68-01
Progress:
- Milestone: [######░░░░] ~64%
- Phase 64: [##########] 100%
- Milestone: [######░░░░] ~68%
- Phase 68: [#####░░░░░] 50%
## Loop Position
@@ -30,12 +30,11 @@ PLAN ──▶ APPLY ──▶ UNIFY
## Session Continuity
Last session: 2026-04-03
Stopped at: Phase 64 complete
Next action: /paul:plan dla kolejnego modulu
Resume file: .paul/phases/64-receipt-datetime-precision/64-01-SUMMARY.md
Stopped at: Phase 68 plan 01 complete
Next action: /paul:plan for 68-02 (remaining duplications: validateCsrf, isActive filter)
Resume file: .paul/phases/68-code-deduplication-refactor/68-01-SUMMARY.md
## Git State
Last commit: 39c3183
Last commit: 0e7ee95
Branch: main
Feature branches merged: none

View File

@@ -0,0 +1,302 @@
---
phase: 65-paul-delegated-apply
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- ~/.claude/paul-framework/workflows/apply-phase.md
- ~/.claude/paul-framework/references/delegated-apply.md
- ~/.claude/paul-framework/references/subagent-criteria.md
- ~/.claude/paul-framework/templates/PLAN.md
autonomous: false
---
<objective>
## Goal
Rozszerzyć workflow `/paul:apply` o tryb delegowany (delegated-apply), w którym orkiestrator w głównym kontekście rozbija taski z PLAN.md na mikro-zadania i zleca je sub-agentom przez Agent tool. Orkiestrator ocenia wyniki i decyduje: accept / retry / escalate.
## Purpose
Główny kontekst zapełnia się zbyt szybko podczas APPLY — czytanie plików, implementacja, weryfikacja zużywają 60-80% okna kontekstowego na jedną fazę. Delegacja do sub-agentów pozwoli:
- Zachować główny kontekst na poziomie ~15-25% zużycia (orkiestracja + ocena)
- Wykonywać więcej faz w jednej sesji bez handoff
- Poprawić jakość — mniejsze, fokusowe zadania dla sub-agentów
- Umożliwić równoległe wykonywanie niezależnych tasków
## Output
1. Zmodyfikowany `apply-phase.md` z trybem delegowanym
2. Nowy reference `delegated-apply.md` z pełną specyfikacją wzorca
3. Zaktualizowany `subagent-criteria.md` o kontekst PAUL delegacji
4. Zaktualizowany template `PLAN.md` o pole `delegation`
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/STATE.md
## Source Files (do edycji)
@~/.claude/paul-framework/workflows/apply-phase.md
@~/.claude/paul-framework/references/subagent-criteria.md
@~/.claude/paul-framework/references/delegated-apply.md (nowy)
@~/.claude/paul-framework/templates/PLAN.md
</context>
<acceptance_criteria>
## AC-1: Tryb delegowany aktywuje się automatycznie lub ręcznie
```gherkin
Given plan z polem `delegation: auto` w frontmatter
When /paul:apply wykonuje plan
Then orkiestrator rozbija każdy task type="auto" na mikro-prompt i zleca Agent tool
And checkpointy (human-verify, decision) pozostają w głównym kontekście
```
## AC-2: Mikro-prompt zawiera pełny kontekst dla sub-agenta
```gherkin
Given task z PLAN.md z polami files, action, verify, done, boundaries
When orkiestrator buduje mikro-prompt
Then prompt zawiera: cel zadania, ścieżki plików, dokładne instrukcje, co NIE zmieniać, jak zweryfikować
And sub-agent nie musi czytać PLAN.md ani STATE.md (zero dodatkowego kontekstu)
```
## AC-3: Orkiestrator ocenia wynik i podejmuje decyzję
```gherkin
Given sub-agent zwrócił wynik mikro-zadania
When orkiestrator analizuje zwrócony summary
Then podejmuje jedną z decyzji: accept (kontynuuj) / retry (ponów z feedbackiem) / escalate (poproś użytkownika)
And loguje decyzję do progress trackera
```
## AC-4: Fallback do trybu inline
```gherkin
Given plan z polem `delegation: off` lub brak pola delegation
When /paul:apply wykonuje plan
Then workflow działa identycznie jak dotychczas (bez zmian w zachowaniu)
```
## AC-5: Równoległe wykonywanie niezależnych tasków
```gherkin
Given plan z 2+ taskami type="auto" które nie mają zależności między sobą
When orkiestrator jest w trybie delegowanym
Then może zlecić niezależne taski równolegle (multiple Agent calls w jednym kroku)
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Utworzenie reference delegated-apply.md</name>
<files>~/.claude/paul-framework/references/delegated-apply.md</files>
<action>
Utwórz nowy plik referencyjny opisujący wzorzec delegated-apply:
1. **Architektura orkiestratora:**
- Główny kontekst = lekki koordynator (czyta plan, buduje prompty, ocenia wyniki)
- Sub-agent = wykonawca jednego mikro-zadania (czyta pliki, implementuje, weryfikuje)
- Przepływ: parse plan → dla każdego taska → build micro-prompt → spawn Agent → evaluate result → accept/retry/escalate
2. **Budowanie mikro-promptu:**
Każdy mikro-prompt dla sub-agenta MUSI zawierać:
```
## Cel
[task name z PLAN.md]
## Pliki do modyfikacji
[files z PLAN.md]
## Instrukcje
[action z PLAN.md — pełna treść, nie skrócona]
## Granice — NIE ZMIENIAJ
[boundaries z PLAN.md]
## Weryfikacja
[verify z PLAN.md]
## Kryteria ukończenia
[done z PLAN.md]
## Kontekst projektu (minimum)
- Stack: [z PROJECT.md — 1-2 linijki]
- Konwencje: [kluczowe konwencje z CONVENTIONS/CLAUDE.md — max 5 linijek]
```
3. **Ocena wyników (evaluation protocol):**
- Sub-agent zwraca summary ~100-300 słów
- Orkiestrator sprawdza:
a) Czy pliki zostały zmodyfikowane (git diff --stat)
b) Czy done criteria z PLAN.md są spełnione
c) Czy boundaries nie zostały naruszone (git diff na chronionych plikach)
- Decyzja:
- **accept**: wynik OK → przejdź do następnego taska
- **retry** (max 3x): wynik niekompletny → spawn Agent z feedbackiem co poprawić
- **escalate**: 3 retry failed lub problem wymaga decyzji → pytaj użytkownika
4. **Tryby delegacji:**
- `delegation: auto` — orkiestrator automatycznie deleguje taski type="auto"
- `delegation: parallel` — jak auto, ale niezależne taski lecą równolegle
- `delegation: off` — klasyczny inline (domyślne, backward compatible)
5. **Ograniczenia:**
- Checkpointy (human-verify, decision, human-action) ZAWSZE w głównym kontekście
- Sub-agent NIE aktualizuje STATE.md ani PLAN.md — to robi orkiestrator
- Max 1 task per sub-agent (atomowość)
- Sub-agent type: `general-purpose` (domyślny)
6. **Szacunek oszczędności kontekstu:**
| Element | Inline (teraz) | Delegated |
|---------|----------------|-----------|
| Czytanie plików źródłowych | ~3-5k tokens/plik | 0 (w sub-agencie) |
| Implementacja kodu | ~10-20k tokens | 0 (w sub-agencie) |
| Wynik weryfikacji | ~2-5k tokens | ~0.5k (summary) |
| Orkiestracja per task | 0 | ~1-2k tokens |
| **Total per task** | **~15-30k** | **~2-3k** |
</action>
<verify>Plik istnieje w ~/.claude/paul-framework/references/delegated-apply.md i zawiera sekcje: Architektura, Mikro-prompt, Evaluation Protocol, Tryby, Ograniczenia</verify>
<done>AC-2 satisfied: specyfikacja mikro-promptu zdefiniowana. AC-3 satisfied: evaluation protocol zdefiniowany.</done>
</task>
<task type="auto">
<name>Task 2: Modyfikacja apply-phase.md — dodanie trybu delegowanego</name>
<files>~/.claude/paul-framework/workflows/apply-phase.md</files>
<action>
Dodaj nowy krok `determine_execution_mode` PRZED `execute_tasks` oraz zmodyfikuj `execute_tasks`:
1. **Nowy krok `determine_execution_mode`** (po `verify_required_skills`):
```
1. Sprawdź frontmatter pola `delegation`:
- `auto` lub `parallel` → tryb delegowany
- `off` lub brak pola → tryb inline (bez zmian)
2. Jeśli tryb delegowany:
- Załaduj @references/delegated-apply.md
- Przygotuj kontekst projektu dla mikro-promptów (stack, konwencje — max 10 linijek)
- Zidentyfikuj taski niezależne (do równoległego wykonania jeśli `parallel`)
3. Log: "Execution mode: delegated|inline"
```
2. **Rozszerzenie `execute_tasks`** — dodaj alternatywną ścieżkę dla trybu delegowanego:
**Jeśli tryb delegowany i task type="auto":**
```
1. Zbuduj mikro-prompt wg szablonu z delegated-apply.md
2. Wywołaj Agent tool:
- subagent_type: "general-purpose"
- prompt: [mikro-prompt]
- description: "PAUL task: [task name]" (3-5 słów)
3. Odbierz wynik (summary od sub-agenta)
4. Oceń wg evaluation protocol:
- Sprawdź git diff --stat
- Sprawdź done criteria
- Sprawdź boundaries
5. Decyzja: accept / retry / escalate
6. Przy retry: spawn ponownie z feedbackiem (max 3 razy)
```
**Jeśli `delegation: parallel` i multiple niezależne taski:**
```
1. Zidentyfikuj klastry niezależnych tasków
2. Wywołaj multiple Agent tool w jednym kroku
3. Oceń wyniki każdego
4. Kontynuuj sekwencyjnie z taskami zależnymi
```
**Checkpointy — BEZ ZMIAN:**
Taski checkpoint:* zawsze wykonywane inline w głównym kontekście.
3. **Rozszerzenie `finalize`** — dodaj podsumowanie trybu:
```
Delegated execution summary:
- Tasks delegated: N
- Accepted on first try: N
- Retried: N
- Escalated: N
```
WAŻNE: Nie usuwaj i nie modyfikuj istniejącego zachowania inline. Tryb delegowany to DODATKOWA ścieżka.
</action>
<verify>apply-phase.md zawiera kroki: determine_execution_mode, execute_tasks z alternatywną ścieżką delegowaną, finalize z podsumowaniem delegacji</verify>
<done>AC-1 satisfied: tryb delegowany aktywuje się na podstawie pola delegation. AC-4 satisfied: brak pola = inline bez zmian.</done>
</task>
<task type="auto">
<name>Task 3: Aktualizacja PLAN.md template i subagent-criteria.md</name>
<files>~/.claude/paul-framework/templates/PLAN.md, ~/.claude/paul-framework/references/subagent-criteria.md</files>
<action>
1. **PLAN.md template** — dodaj pole `delegation` do frontmatter:
```yaml
delegation: off # off | auto | parallel
```
- Dodaj pole po `autonomous` z komentarzem
- Dodaj do tabeli Frontmatter Fields: `delegation | No | Execution mode: off (inline, default), auto (delegated), parallel (delegated + parallel)`
- Dodaj krótką sekcję "## Delegation Mode" w dokumencie opisującą kiedy użyć jakiego trybu
2. **subagent-criteria.md** — dodaj sekcję o kontekście PAUL:
- Nowa sekcja "## PAUL Delegated Apply Context"
- Wyjaśnij że w kontekście /paul:apply, sub-agenty służą do:
a) Wykonywania tasków z PLAN.md (well-defined, files+action+verify+done)
b) Ochrony głównego kontekstu orkiestratora
c) Umożliwienia wielu faz w jednej sesji
- Podkreśl że kryteria z istniejącej sekcji nadal obowiązują
- Dodaj notę: "W PAUL delegated-apply, każdy task z PLAN.md naturalnie spełnia kryteria: Task Independence (self-contained), Clear Scope (files+action+verify+done), Complexity Sweet Spot (2-3 taski per plan = 1 task per agent)"
</action>
<verify>PLAN.md template zawiera pole delegation w frontmatter i tabeli. subagent-criteria.md zawiera sekcję PAUL Delegated Apply Context.</verify>
<done>AC-1 partially satisfied: template wspiera pole delegation. AC-5 satisfied: parallel mode zdefiniowany.</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<what-built>
Kompletny wzorzec delegated-apply dla PAUL:
1. Reference doc z architekturą, mikro-promptem, evaluation protocol
2. Zmodyfikowany apply-phase.md z trybem delegowanym
3. Zaktualizowany template i subagent-criteria
</what-built>
<how-to-verify>
1. Przejrzyj ~/.claude/paul-framework/references/delegated-apply.md
2. Przejrzyj zmiany w ~/.claude/paul-framework/workflows/apply-phase.md
3. Sprawdź czy PLAN.md template ma pole delegation
4. Przetestuj na następnej fazie orderPRO z `delegation: auto`
</how-to-verify>
<resume-signal>Type "approved" to continue, or describe issues to fix</resume-signal>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- ~/.claude/paul-framework/workflows/plan-phase.md (planning workflow untouched)
- ~/.claude/paul-framework/workflows/unify-phase.md (unify workflow untouched)
- ~/.claude/paul-framework/rules/* (framework rules untouched)
- .paul/PROJECT.md, .paul/ROADMAP.md (project state untouched)
## SCOPE LIMITS
- Nie zmieniamy /paul:plan ani /paul:unify — tylko /paul:apply
- Nie tworzymy nowych komend — rozszerzamy istniejący workflow
- Nie przerabiamy istniejącego trybu inline — dodajemy alternatywną ścieżkę
- Nie implementujemy auto-detection rozmiaru (proste pole w frontmatter wystarczy)
</boundaries>
<verification>
Before declaring plan complete:
- [ ] delegated-apply.md istnieje i zawiera: architekturę, mikro-prompt template, evaluation protocol, tryby, ograniczenia
- [ ] apply-phase.md zawiera krok determine_execution_mode i alternatywną ścieżkę delegowaną
- [ ] PLAN.md template zawiera pole delegation w frontmatter
- [ ] subagent-criteria.md zawiera sekcję PAUL context
- [ ] Tryb inline (delegation: off / brak pola) działa bez zmian
- [ ] Wszystkie checkpointy nadal działają w głównym kontekście
</verification>
<success_criteria>
- Wszystkie 3 taski auto completed + checkpoint approved
- Framework PAUL wspiera tryb delegated-apply
- Backward compatible — istniejące plany bez pola delegation działają jak dotychczas
- Gotowy do przetestowania na następnej fazie orderPRO
</success_criteria>
<output>
After completion, create `.paul/phases/65-paul-delegated-apply/65-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,124 @@
---
phase: 65-paul-delegated-apply
plan: 01
subsystem: infra
tags: [paul-framework, subagents, delegation, context-management]
requires:
- phase: none
provides: standalone enhancement
provides:
- Delegated-apply execution mode for /paul:apply
- Micro-prompt template for sub-agent task delegation
- Evaluation protocol (accept/retry/escalate)
- delegation field in PLAN.md frontmatter
affects: [all future phases using /paul:apply]
tech-stack:
added: []
patterns: [orchestrator-subagent delegation, micro-prompt composition, evaluation protocol]
key-files:
created:
- ~/.claude/paul-framework/references/delegated-apply.md
modified:
- ~/.claude/paul-framework/workflows/apply-phase.md
- ~/.claude/paul-framework/templates/PLAN.md
- ~/.claude/paul-framework/references/subagent-criteria.md
key-decisions:
- "delegation: auto as default for all new plans"
- "Max 3 retries before escalation to user"
- "Checkpoints always inline, never delegated"
patterns-established:
- "Orchestrator builds micro-prompt from PLAN.md task fields, delegates via Agent tool"
- "Evaluation: git diff + done criteria + boundary check → accept/retry/escalate"
duration: ~15min
started: 2026-04-03T20:10:00Z
completed: 2026-04-03T20:25:00Z
---
# Phase 65 Plan 01: PAUL Delegated Apply Summary
**Rozszerzenie /paul:apply o tryb delegowany — orkiestrator w głównym kontekście zleca mikro-zadania sub-agentom, ocenia wyniki i decyduje accept/retry/escalate. Szacowana oszczędność: ~80% kontekstu per faza.**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~15min |
| Started | 2026-04-03T20:10:00Z |
| Completed | 2026-04-03T20:25:00Z |
| Tasks | 3 auto + 1 checkpoint completed |
| Files modified | 4 |
| Execution mode | Delegated (parallel) — 3 sub-agents |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Tryb delegowany aktywuje się automatycznie | Pass | Pole `delegation: auto` (domyślne), workflow sprawdza frontmatter |
| AC-2: Mikro-prompt zawiera pełny kontekst | Pass | Template w delegated-apply.md: cel, pliki, instrukcje, granice, weryfikacja, kontekst |
| AC-3: Orkiestrator ocenia wynik | Pass | Evaluation protocol: git diff + done criteria + boundary check → accept/retry/escalate |
| AC-4: Fallback do trybu inline | Pass | `delegation: off` = inline bez zmian; brak pola = auto (zmiana z oryginalnego planu) |
| AC-5: Równoległe wykonywanie tasków | Pass | `delegation: parallel` mode zdefiniowany w workflow i reference |
## Accomplishments
- Utworzono kompletny reference `delegated-apply.md` z architekturą, mikro-prompt template, evaluation protocol, trybami i ograniczeniami
- Rozszerzono `apply-phase.md` o krok `determine_execution_mode` i alternatywną ścieżkę delegowaną (inline path bez zmian)
- Zaktualizowano template PLAN.md o pole `delegation` (domyślnie `auto`) i sekcję Delegation Mode
- Rozszerzono `subagent-criteria.md` o kontekst PAUL delegated-apply
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `~/.claude/paul-framework/references/delegated-apply.md` | Created | Pełna specyfikacja wzorca delegated-apply |
| `~/.claude/paul-framework/workflows/apply-phase.md` | Modified | Nowy krok determine_execution_mode + delegated path + delegation summary w finalize |
| `~/.claude/paul-framework/templates/PLAN.md` | Modified | Pole delegation w frontmatter (default: auto) + sekcja Delegation Mode |
| `~/.claude/paul-framework/references/subagent-criteria.md` | Modified | Sekcja PAUL Delegated Apply Context |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| `delegation: auto` jako default | Użytkownik chce delegacji jako standardowego zachowania, nie opt-in | Wszystkie nowe plany automatycznie delegują taski |
| Max 3 retries (zmiana z 2) | Użytkownik poprosił o zwiększenie — daje więcej szans na sukces | Mniej escalacji do użytkownika |
| Checkpointy zawsze inline | Wymagają interakcji z użytkownikiem — nie da się delegować | Bezpieczeństwo workflow |
## Deviations from Plan
### Summary
| Type | Count | Impact |
|------|-------|--------|
| Scope additions | 1 | Zmiana default z `off` na `auto` — poproszona przez użytkownika |
**Total impact:** Pozytywna zmiana — lepsze UX, delegacja out-of-the-box.
## Issues Encountered
None
## Next Phase Readiness
**Ready:**
- Framework PAUL wspiera tryb delegated-apply we wszystkich projektach
- Każdy nowy plan domyślnie używa `delegation: auto`
- Gotowe do przetestowania na następnej fazie orderPRO
**Concerns:**
- Wymaga przetestowania w praktyce — pierwszy real-world test na następnej fazie orderPRO pokaże czy mikro-prompty są wystarczająco dobre
- Sub-agenty mogą potrzebować dostrojenia kontekstu projektu (stack + konwencje)
**Blockers:**
- None
---
*Phase: 65-paul-delegated-apply, Plan: 01*
*Completed: 2026-04-03*

View File

@@ -0,0 +1,312 @@
---
phase: 66-allegro-delivery-tracking
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- src/Modules/Shipments/AllegroTrackingService.php
- src/Modules/Shipments/DeliveryStatus.php
- src/Modules/Cron/ShipmentTrackingHandler.php
autonomous: true
delegation: auto
---
<objective>
## Goal
Zaimplementować śledzenie statusu przesyłek Allegro Delivery (numery A-*) przez publiczne edge API Allegro. Aktualnie `AllegroTrackingService` zwraca `null` dla przesyłek nie-InPost. Po zmianie — pobiera statusy z `https://edge.allegro.pl/ad/tracking?packageNo={nr}` i normalizuje je do istniejącego systemu `DeliveryStatus`.
## Purpose
Przesyłki Allegro Delivery (One Kurier, DPD via Allegro, itp.) nie mają śledzenia w orderPRO. Użytkownicy muszą ręcznie sprawdzać status na allegro.pl. Ta zmiana automatyzuje ten proces.
## Output
- `AllegroTrackingService` pobiera statusy z edge API dla przesyłek non-InPost
- `DeliveryStatus` ma rozszerzony mapping Allegro o statusy z edge API (opisy PL)
- Rate limiting: max 1 request na minutę per przesyłka (w cron handler)
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/STATE.md
## Source Files
@src/Modules/Shipments/AllegroTrackingService.php
@src/Modules/Shipments/DeliveryStatus.php
@src/Modules/Cron/ShipmentTrackingHandler.php
## API Research
Edge API endpoint (publiczny, bez autoryzacji):
- URL: `https://edge.allegro.pl/ad/tracking?packageNo={trackingNumber}`
- Header: `Accept: application/vnd.allegro.internal.v1+json`
- Response: `{"status": [{"eventTimestamp": "ISO8601", "description": "Opis PL"}]}`
- Ostatni element tablicy = aktualny status
- Opisy są po polsku, np.:
- "Przesyłka została przygotowana przez nadawcę"
- "Przesyłka została nadana"
- "Przesyłka została podjęta z punktu przez kuriera"
- "Przesyłka została odebrana przez kuriera"
- "Kurier przekazał przesyłkę do magazynu"
</context>
<acceptance_criteria>
## AC-1: Allegro Delivery tracking zwraca status
```gherkin
Given przesyłka z provider=allegro_wza i tracking_number zaczynający się od "A"
When cron tracking handler odpytuje AllegroTrackingService
Then serwis pobiera status z edge.allegro.pl/ad/tracking
And zwraca znormalizowany status + opis po polsku
```
## AC-2: Mapowanie opisów na znormalizowane statusy
```gherkin
Given odpowiedź z edge API zawiera description np. "Przesyłka została nadana"
When AllegroTrackingService przetwarza odpowiedź
Then mapuje opis na raw status (np. "shipped") i normalizuje przez DeliveryStatus
And status_raw zawiera oryginalny opis z API
```
## AC-3: Rate limiting — max 1 request/min
```gherkin
Given cron handler przetwarza wiele przesyłek allegro_wza
When odpytuje edge API
Then między kolejnymi requestami do edge.allegro.pl czeka minimum 60 sekund
And inne providery (InPost, Apaczka) nie są objęte tym limitem
```
## AC-4: Fallback InPost nadal działa
```gherkin
Given przesyłka z provider=allegro_wza i carrier_id zawierający "inpost"
When cron tracking handler odpytuje AllegroTrackingService
Then serwis nadal używa InPost API (nie edge API)
And zachowanie jest identyczne jak przed zmianą
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Rozszerzenie DeliveryStatus o mapowanie Allegro edge API</name>
<files>src/Modules/Shipments/DeliveryStatus.php</files>
<action>
Dodaj nową mapę `ALLEGRO_EDGE_MAP` i `ALLEGRO_EDGE_DESCRIPTIONS` do DeliveryStatus.
Edge API zwraca opisy po polsku (nie kody). Trzeba mapować opisy na wewnętrzne klucze, a potem na znormalizowane statusy.
Nowa mapa `ALLEGRO_EDGE_MAP` (klucz = slug z opisu, wartość = normalized status):
```php
private const ALLEGRO_EDGE_MAP = [
'przygotowana_przez_nadawce' => self::CREATED,
'nadana' => self::CONFIRMED,
'podjeta_z_punktu' => self::IN_TRANSIT,
'odebrana_przez_kuriera' => self::IN_TRANSIT,
'przekazana_do_magazynu' => self::IN_TRANSIT,
'w_sortowni' => self::IN_TRANSIT,
'w_doreceniu' => self::OUT_FOR_DELIVERY,
'gotowa_do_odbioru' => self::READY_FOR_PICKUP,
'dostarczona' => self::DELIVERED,
'doreczona' => self::DELIVERED,
'zwrocona' => self::RETURNED,
'anulowana' => self::CANCELLED,
'problem' => self::PROBLEM,
];
```
Nowa mapa `ALLEGRO_EDGE_DESCRIPTIONS` — klucz = slug, wartość = oryginalny opis PL (identyczny jak z API).
Dodaj nowy provider `allegro_edge` do:
- `PROVIDER_MAPS` array
- `PROVIDER_DESCRIPTIONS` array
- match w `normalize()` i `description()`
Dodaj statyczną metodę `slugifyAllegroDescription(string $description): string` — konwertuje opis PL na slug:
1. Usuwa prefiks "Przesyłka została " / "Kurier "
2. Bierze główne słowo kluczowe
3. Zamienia polskie znaki na ASCII
4. Zamienia spacje na podkreślenia
5. Zwraca lowercase slug
WAŻNE: Metoda musi obsługiwać nieznane opisy — jeśli slug nie istnieje w mapie, zwróć sam slug jako raw_status i self::UNKNOWN jako normalized.
</action>
<verify>Klasa DeliveryStatus kompiluje się bez błędów. Nowe stałe ALLEGRO_EDGE_MAP i ALLEGRO_EDGE_DESCRIPTIONS istnieją. Provider 'allegro_edge' jest obsługiwany w normalize() i description().</verify>
<done>AC-2 satisfied: mapowanie opisów na znormalizowane statusy zdefiniowane.</done>
</task>
<task type="auto">
<name>Task 2: Implementacja fetchAllegroEdgeStatus w AllegroTrackingService</name>
<files>src/Modules/Shipments/AllegroTrackingService.php</files>
<action>
Zmodyfikuj `AllegroTrackingService::getDeliveryStatus()` aby obsługiwał przesyłki Allegro Delivery (non-InPost):
1. W metodzie `getDeliveryStatus()`, po bloku `if (str_contains(... 'inpost'))`, zamiast `return null` dodaj:
```php
return $this->fetchAllegroEdgeStatus($trackingNumber);
```
2. Dodaj nową prywatną metodę `fetchAllegroEdgeStatus(string $trackingNumber): ?array`:
```php
private function fetchAllegroEdgeStatus(string $trackingNumber): ?array
{
try {
$url = 'https://edge.allegro.pl/ad/tracking?packageNo=' . rawurlencode($trackingNumber);
$response = $this->edgeApiRequest($url);
$statuses = $response['status'] ?? [];
if (!is_array($statuses) || $statuses === []) {
return null;
}
// Ostatni element = najnowszy status
$latest = end($statuses);
$description = trim((string) ($latest['description'] ?? ''));
if ($description === '') {
return null;
}
$slug = DeliveryStatus::slugifyAllegroDescription($description);
return [
'status' => DeliveryStatus::normalize('allegro_edge', $slug),
'status_raw' => $description,
'description' => $description,
];
} catch (Throwable) {
return null;
}
}
```
3. Dodaj nową prywatną metodę `edgeApiRequest(string $url): array` — osobna od istniejącej `apiRequest()` bo nie wymaga autoryzacji:
```php
private function edgeApiRequest(string $url): array
{
$ch = curl_init($url);
if ($ch === false) {
return [];
}
$opts = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_HTTPHEADER => [
'Accept: application/vnd.allegro.internal.v1+json',
'Content-Type: application/vnd.allegro.internal.v1+json',
],
];
$caPath = $this->getCaBundlePath();
if ($caPath !== null) {
$opts[CURLOPT_CAINFO] = $caPath;
}
curl_setopt_array($ch, $opts);
$body = curl_exec($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
$ch = null;
if ($body === false || $httpCode < 200 || $httpCode >= 300) {
return [];
}
$json = json_decode((string) $body, true);
return is_array($json) ? $json : [];
}
```
NIE zmieniaj istniejącej metody `fetchInpostStatus()` ani `apiRequest()` — to osobne ścieżki.
</action>
<verify>AllegroTrackingService kompiluje się. Metoda getDeliveryStatus nie zwraca null dla non-InPost przesyłek (wywołuje fetchAllegroEdgeStatus). Metoda edgeApiRequest wysyła Accept: application/vnd.allegro.internal.v1+json.</verify>
<done>AC-1 satisfied: Allegro Delivery tracking pobiera status z edge API. AC-4 satisfied: InPost path bez zmian.</done>
</task>
<task type="auto">
<name>Task 3: Rate limiting w ShipmentTrackingHandler dla Allegro edge API</name>
<files>src/Modules/Cron/ShipmentTrackingHandler.php</files>
<action>
Dodaj rate limiting do `ShipmentTrackingHandler::handle()` — max 1 request na 60 sekund do edge.allegro.pl.
1. Dodaj stałą:
```php
private const ALLEGRO_EDGE_RATE_LIMIT_SECONDS = 60;
```
2. W metodzie `handle()`, przed pętlą `foreach`, dodaj zmienną śledzącą czas ostatniego requestu Allegro edge:
```php
$lastAllegroEdgeRequestTime = 0.0;
```
3. Wewnątrz pętli `foreach`, PO uzyskaniu `$service` a PRZED wywołaniem `$service->getDeliveryStatus()`, dodaj sprawdzenie:
```php
if ($provider === 'allegro_wza') {
$carrierId = strtolower(trim((string) ($package['carrier_id'] ?? '')));
$isInpost = str_contains($carrierId, 'inpost') || str_contains($carrierId, 'paczkomat');
if (!$isInpost) {
$elapsed = microtime(true) - $lastAllegroEdgeRequestTime;
if ($elapsed < self::ALLEGRO_EDGE_RATE_LIMIT_SECONDS) {
$sleepTime = (int) ceil(self::ALLEGRO_EDGE_RATE_LIMIT_SECONDS - $elapsed);
sleep($sleepTime);
}
// Zaraz po sleep (lub bez), przed wywołaniem getDeliveryStatus:
$lastAllegroEdgeRequestTime = microtime(true);
}
}
```
WAŻNE:
- Rate limit dotyczy TYLKO requestów do edge.allegro.pl (non-InPost allegro_wza)
- InPost i Apaczka requestów NIE ograniczaj
- sleep() w cronie jest OK — to background process
</action>
<verify>ShipmentTrackingHandler kompiluje się. Stała ALLEGRO_EDGE_RATE_LIMIT_SECONDS = 60 istnieje. Kod rate limitingu jest w pętli foreach przed getDeliveryStatus dla allegro_wza non-inpost.</verify>
<done>AC-3 satisfied: max 1 request/min do edge.allegro.pl.</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- src/Modules/Shipments/InpostTrackingService.php
- src/Modules/Shipments/ApaczkaTrackingService.php
- src/Modules/Shipments/ShipmentTrackingInterface.php
- src/Modules/Shipments/ShipmentTrackingRegistry.php
- src/Modules/Shipments/ShipmentPackageRepository.php
- src/Modules/Cron/CronHandlerFactory.php
- Istniejące mapy INPOST_MAP, APACZKA_MAP w DeliveryStatus
- Istniejąca metoda fetchInpostStatus() w AllegroTrackingService
## SCOPE LIMITS
- Nie tworzymy nowych klas — rozszerzamy istniejące
- Nie zmieniamy schematu DB — delivery_status_raw już obsługuje dowolne stringi
- Nie dodajemy UI — tracking UI już istnieje i działa z dowolnym statusem
- Nie tworzymy unit testów w tym planie
</boundaries>
<verification>
Before declaring plan complete:
- [ ] DeliveryStatus::normalize('allegro_edge', 'nadana') zwraca 'confirmed'
- [ ] DeliveryStatus::slugifyAllegroDescription('Przesyłka została nadana') zwraca slug mapowany na status
- [ ] AllegroTrackingService::getDeliveryStatus() dla non-InPost allegro_wza wywołuje edge API
- [ ] AllegroTrackingService::getDeliveryStatus() dla InPost allegro_wza nadal używa InPost API
- [ ] ShipmentTrackingHandler ma rate limit 60s między requestami edge.allegro.pl
- [ ] Żadne chronione pliki nie zostały zmodyfikowane
- [ ] PHP syntax check: `php -l` na każdym zmienionym pliku
</verification>
<success_criteria>
- Wszystkie 3 taski auto completed
- Przesyłki Allegro Delivery (A-numery) mają automatyczny tracking statusu
- Rate limit chroni przed blokadą przez Allegro
- Istniejący tracking InPost i Apaczka bez zmian
</success_criteria>
<output>
After completion, create `.paul/phases/66-allegro-delivery-tracking/66-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,96 @@
---
phase: 66-allegro-delivery-tracking
plan: 01
subsystem: shipments
tags: [allegro, tracking, edge-api, delivery-status, cron]
requires:
- phase: 27-shipment-tracking-backend
provides: ShipmentTrackingInterface, DeliveryStatus, ShipmentTrackingHandler
provides:
- Allegro Delivery tracking via edge.allegro.pl API
- Edge API integration (no auth, public endpoint)
- Rate limiting for edge API requests (60s)
affects: [allegro-tracking, delivery-status, cron-handler]
tech-stack:
added: []
patterns: [edge-api-integration, rate-limited-cron]
key-files:
created: []
modified:
- src/Modules/Shipments/AllegroTrackingService.php
- src/Modules/Shipments/DeliveryStatus.php
- src/Modules/Cron/ShipmentTrackingHandler.php
key-decisions:
- "Edge API (edge.allegro.pl/ad/tracking) zamiast Allegro REST API (nie daje tracking statusów)"
- "Accept: application/vnd.allegro.internal.v1+json — wymagany nagłówek"
- "Provider allegro_edge osobny od allegro_wza w DeliveryStatus"
- "Rate limit 60s między requestami do edge API"
patterns-established:
- "slugifyAllegroDescription() konwertuje opisy PL na slugi mapowalne na statusy"
- "Osobna metoda edgeApiRequest() bez Bearer token (publiczny endpoint)"
duration: ~15min
started: 2026-04-03T20:30:00Z
completed: 2026-04-03T20:45:00Z
---
# Phase 66 Plan 01: Allegro Delivery Tracking — Core Integration
**Integracja śledzenia przesyłek Allegro Delivery przez publiczne edge API — fetchAllegroEdgeStatus, mapowanie opisów PL na znormalizowane statusy, rate limiting 60s w cronie.**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~15min |
| Tasks | 3 completed (delegated) |
| Files modified | 3 |
| Execution mode | Delegated auto (3 sub-agents) |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Allegro Delivery tracking zwraca status | Pass | fetchAllegroEdgeStatus pobiera z edge API |
| AC-2: Mapowanie opisów na statusy | Pass | slugify + ALLEGRO_EDGE_MAP |
| AC-3: Rate limiting max 1 req/min | Pass | sleep() w ShipmentTrackingHandler |
| AC-4: Fallback InPost nadal działa | Pass | Warunek carrier_id inpost/paczkomat bez zmian |
## Accomplishments
- AllegroTrackingService pobiera statusy z edge.allegro.pl/ad/tracking dla non-InPost przesyłek
- DeliveryStatus ma provider 'allegro_edge' z mapą slugów i opisów PL
- ShipmentTrackingHandler throttluje requesty do edge API (60s)
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `src/Modules/Shipments/DeliveryStatus.php` | Modified | ALLEGRO_EDGE_MAP, ALLEGRO_EDGE_DESCRIPTIONS, slugifyAllegroDescription() |
| `src/Modules/Shipments/AllegroTrackingService.php` | Modified | fetchAllegroEdgeStatus(), edgeApiRequest() |
| `src/Modules/Cron/ShipmentTrackingHandler.php` | Modified | Rate limiting 60s dla allegro_wza non-inpost |
## Deviations from Plan
### Auto-fixed Issues
**1. Slug mismatch w mapie**
- **Found during:** Orkiestrator verification
- **Issue:** "Kurier przekazał przesyłkę do magazynu" → slug `przekazal_przesylke_do_magazynu` nie był w mapie (była `przekazana_do_magazynu`)
- **Fix:** Dodano wariant do ALLEGRO_EDGE_MAP
- **Verification:** Test 5/5 realnych opisów mapuje poprawnie
## Next Phase Readiness
**Ready:** Plan 66-02 rozszerza mapę i dodaje keyword fallback
---
*Phase: 66-allegro-delivery-tracking, Plan: 01*
*Completed: 2026-04-03*

View File

@@ -0,0 +1,228 @@
---
phase: 66-allegro-delivery-tracking
plan: 02
type: execute
wave: 2
depends_on: ["66-01"]
files_modified:
- src/Modules/Shipments/DeliveryStatus.php
- src/Modules/Shipments/AllegroTrackingService.php
autonomous: true
delegation: auto
---
<objective>
## Goal
1. Uzupełnić mapę ALLEGRO_EDGE_MAP o brakujące statusy z realnych przesyłek
2. Dodać mechanizm keyword-based fallback (guessStatusFromDescription) dla nieznanych opisów
3. Logować nowe nierozpoznane statusy do activity_log
## Purpose
Edge API Allegro zwraca opisy po polsku bez ustalonego słownika — mogą pojawić się nowe warianty. Hardcoded mapa nie wystarczy. Fallback + logowanie = system sam sobie radzi z nowymi opisami i informuje admina.
## Output
- Rozszerzona mapa ALLEGRO_EDGE_MAP o 5 nowych slugów
- Metoda guessStatusFromDescription() w DeliveryStatus jako keyword fallback
- Logowanie nieznanych statusów w AllegroTrackingService
</objective>
<context>
## Prior Work
@.paul/phases/66-allegro-delivery-tracking/66-01-PLAN.md
## Nowe statusy z realnego zamówienia AD0243IOG6
| Slug | Opis | Mapowanie |
|------|------|-----------|
| podjeta_z_maszyny_przez_kuriera | Przesyłka została podjęta z maszyny przez kuriera | in_transit |
| przesylka_wyjechala_w_droge_do_punktu_docelowego | Przesyłka wyjechała w drogę do punktu docelowego | in_transit |
| wyslana_z_sortowni | Wysłana z sortowni | in_transit |
| wydana_do_doreczenia | Przesyłka została wydana do doręczenia | out_for_delivery |
| przesylka_oczekuje_na_odbior | Przesyłka oczekuje na odbiór | ready_for_pickup |
</context>
<acceptance_criteria>
## AC-1: Nowe slugi w mapie
```gherkin
Given opis "Wysłana z sortowni" z edge API
When slugify + normalize
Then zwraca 'in_transit' (nie 'unknown')
```
## AC-2: Fallback keyword matching
```gherkin
Given nieznany opis np. "Paczka jest w drodze do odbiorcy"
When slug nie istnieje w ALLEGRO_EDGE_MAP
Then guessStatusFromDescription() dopasowuje na podstawie słów kluczowych
And zwraca odpowiedni znormalizowany status
```
## AC-3: Logowanie nieznanych statusów
```gherkin
Given opis z edge API którego slug NIE jest w mapie i fallback zwraca unknown
When AllegroTrackingService przetwarza taki status
Then loguje do error_log: "[AllegroTracking] Nowy niezmapowany status: {opis} (slug: {slug})"
And nadal zwraca wynik z status=unknown (nie null)
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Rozszerzenie mapy + guessStatusFromDescription</name>
<files>src/Modules/Shipments/DeliveryStatus.php</files>
<action>
1. Dodaj brakujące slugi do ALLEGRO_EDGE_MAP:
```php
'podjeta_z_maszyny_przez_kuriera' => self::IN_TRANSIT,
'przesylka_wyjechala_w_droge_do_punktu_docelowego' => self::IN_TRANSIT,
'wyjechala_w_droge_do_punktu_docelowego' => self::IN_TRANSIT,
'wyslana_z_sortowni' => self::IN_TRANSIT,
'wydana_do_doreczenia' => self::OUT_FOR_DELIVERY,
'przesylka_oczekuje_na_odbior' => self::READY_FOR_PICKUP,
```
2. Dodaj odpowiednie opisy do ALLEGRO_EDGE_DESCRIPTIONS:
```php
'podjeta_z_maszyny_przez_kuriera' => 'Podjęta z maszyny przez kuriera',
'przesylka_wyjechala_w_droge_do_punktu_docelowego' => 'Wyjechała w drogę do punktu docelowego',
'wyjechala_w_droge_do_punktu_docelowego' => 'Wyjechała w drogę do punktu docelowego',
'wyslana_z_sortowni' => 'Wysłana z sortowni',
'wydana_do_doreczenia' => 'Wydana do doręczenia',
'przesylka_oczekuje_na_odbior' => 'Oczekuje na odbiór',
```
3. Dodaj nową statyczną metodę `guessStatusFromDescription(string $description): string` — keyword-based fallback. Umieść ją po slugifyAllegroDescription():
```php
public static function guessStatusFromDescription(string $description): string
{
$lower = mb_strtolower($description, 'UTF-8');
// Terminal statuses first
if (str_contains($lower, 'doręczon') || str_contains($lower, 'dostarczono') || str_contains($lower, 'odebrana przez odbiorc')) {
return self::DELIVERED;
}
if (str_contains($lower, 'zwrócon') || str_contains($lower, 'zwrocona')) {
return self::RETURNED;
}
if (str_contains($lower, 'anulowan')) {
return self::CANCELLED;
}
// Active statuses
if (str_contains($lower, 'doręczeni') || str_contains($lower, 'doreczenia') || str_contains($lower, 'wydana do')) {
return self::OUT_FOR_DELIVERY;
}
if (str_contains($lower, 'odbiór') || str_contains($lower, 'odbior') || str_contains($lower, 'oczekuje na odb')) {
return self::READY_FOR_PICKUP;
}
if (str_contains($lower, 'sortowni') || str_contains($lower, 'magazyn') || str_contains($lower, 'w drodze') || str_contains($lower, 'tranzyt') || str_contains($lower, 'kurier') || str_contains($lower, 'podjęta') || str_contains($lower, 'podjeta') || str_contains($lower, 'wyjechał') || str_contains($lower, 'wyjechala')) {
return self::IN_TRANSIT;
}
if (str_contains($lower, 'nadana') || str_contains($lower, 'nadano')) {
return self::CONFIRMED;
}
if (str_contains($lower, 'przygotowan') || str_contains($lower, 'utworzon')) {
return self::CREATED;
}
if (str_contains($lower, 'uszkodzon') || str_contains($lower, 'problem') || str_contains($lower, 'zagubiła') || str_contains($lower, 'zagubion')) {
return self::PROBLEM;
}
return self::UNKNOWN;
}
```
WAŻNE: NIE zmieniaj istniejących metod normalize(), description(), slugifyAllegroDescription(). Tylko DODAWAJ nowe wpisy do map i nową metodę.
</action>
<verify>php -l przechodzi. Test: DeliveryStatus::normalize('allegro_edge', 'wyslana_z_sortowni') zwraca 'in_transit'. Test: DeliveryStatus::guessStatusFromDescription('Paczka jest w drodze') zwraca 'in_transit'.</verify>
<done>AC-1 satisfied: nowe slugi w mapie. AC-2 satisfied: fallback method istnieje.</done>
</task>
<task type="auto">
<name>Task 2: Integracja fallback + logowanie w AllegroTrackingService</name>
<files>src/Modules/Shipments/AllegroTrackingService.php</files>
<action>
Zmodyfikuj metodę `fetchAllegroEdgeStatus()` — dodaj fallback i logowanie.
Po linii:
```php
$slug = DeliveryStatus::slugifyAllegroDescription($description);
```
Zamień blok return na:
```php
$normalized = DeliveryStatus::normalize('allegro_edge', $slug);
// Fallback: jeśli slug nieznany, próbuj keyword matching
if ($normalized === DeliveryStatus::UNKNOWN) {
$normalized = DeliveryStatus::guessStatusFromDescription($description);
// Loguj niezmapowany status (do uzupełnienia mapy w przyszłości)
error_log(sprintf(
'[AllegroTracking] Niezmapowany status: "%s" (slug: %s, guessed: %s)',
$description,
$slug,
$normalized
));
}
return [
'status' => $normalized,
'status_raw' => $description,
'description' => $description,
];
```
To zastępuje istniejący blok:
```php
return [
'status' => DeliveryStatus::normalize('allegro_edge', $slug),
'status_raw' => $description,
'description' => $description,
];
```
WAŻNE: Nie zmieniaj nic innego w tym pliku. Tylko modyfikacja wewnątrz fetchAllegroEdgeStatus().
</action>
<verify>php -l przechodzi. Metoda fetchAllegroEdgeStatus zawiera fallback guessStatusFromDescription i error_log.</verify>
<done>AC-2 partially satisfied: fallback zintegrowany. AC-3 satisfied: logowanie nieznanych statusów.</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Istniejące mapy INPOST_MAP, APACZKA_MAP, ALLEGRO_MAP
- Metody: normalize(), description(), slugifyAllegroDescription(), fetchInpostStatus()
- src/Modules/Cron/ShipmentTrackingHandler.php (rate limit z 66-01 bez zmian)
- Wszystkie inne pliki
## SCOPE LIMITS
- Nie tworzymy UI do zarządzania mapowaniem (istniejący Delivery Status Mapping UI wystarczy)
- Nie tworzymy unit testów w tym planie
</boundaries>
<verification>
- [ ] php -l na obu plikach
- [ ] DeliveryStatus::normalize('allegro_edge', 'wyslana_z_sortowni') === 'in_transit'
- [ ] DeliveryStatus::normalize('allegro_edge', 'wydana_do_doreczenia') === 'out_for_delivery'
- [ ] DeliveryStatus::guessStatusFromDescription('Paczka jest w drodze do odbiorcy') === 'in_transit'
- [ ] DeliveryStatus::guessStatusFromDescription('Przesyłka odebrana w punkcie') !== 'unknown'
- [ ] fetchAllegroEdgeStatus fallback + error_log działa
</verification>
<success_criteria>
- Oba taski completed
- Wszystkie realne opisy z API (obu zamówień) mapują się na właściwe statusy
- Nieznane przyszłe opisy są obsługiwane przez keyword fallback
- Nieznane statusy logowane do error_log
</success_criteria>
<output>
After completion, create `.paul/phases/66-allegro-delivery-tracking/66-02-SUMMARY.md`
</output>

View File

@@ -0,0 +1,93 @@
---
phase: 66-allegro-delivery-tracking
plan: 02
subsystem: shipments
tags: [allegro, tracking, fallback, keyword-matching, logging]
requires:
- phase: 66-allegro-delivery-tracking
provides: AllegroTrackingService edge API, DeliveryStatus allegro_edge provider
provides:
- Keyword-based fallback for unknown Allegro edge descriptions
- Error logging for unmapped statuses
- Extended ALLEGRO_EDGE_MAP with 6 new real-world slugs
affects: [allegro-tracking, delivery-status]
tech-stack:
added: []
patterns: [keyword-fallback, graceful-degradation]
key-files:
modified:
- src/Modules/Shipments/DeliveryStatus.php
- src/Modules/Shipments/AllegroTrackingService.php
key-decisions:
- "Keyword fallback zamiast pytania użytkownika (cron nie ma interakcji)"
- "error_log dla niezmapowanych statusów (monitoring bez blokowania)"
- "IN_TRANSIT sprawdzany przed READY_FOR_PICKUP w fallback (uniknięcie fałszywego matchu 'odbior' w 'w drodze do odbiorcy')"
patterns-established:
- "guessStatusFromDescription() jako graceful degradation dla nieznanych opisów"
- "Kolejność keyword matching: terminal → active → transit → pickup (specyficzność malejąca)"
duration: ~10min
started: 2026-04-03T20:45:00Z
completed: 2026-04-03T20:55:00Z
---
# Phase 66 Plan 02: Allegro Tracking Fallback + Extended Map
**Rozszerzenie mapy o 6 nowych slugów z realnych przesyłek, keyword-based fallback (guessStatusFromDescription) dla nieznanych opisów, logowanie nowych statusów do error_log.**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~10min |
| Tasks | 2 completed (delegated) + 1 orkiestrator fix |
| Files modified | 2 |
| Execution mode | Delegated auto (2 sub-agents, sequential) |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Nowe slugi w mapie | Pass | 6 nowych slugów z zamówienia AD0243IOG6 |
| AC-2: Fallback keyword matching | Pass | guessStatusFromDescription() — 9 kategorii keywords |
| AC-3: Logowanie nieznanych statusów | Pass | error_log w fetchAllegroEdgeStatus |
## Accomplishments
- 6 nowych slugów w ALLEGRO_EDGE_MAP z realnego zamówienia AD0243IOG6
- guessStatusFromDescription() jako keyword fallback — 9 kategorii (delivered, returned, cancelled, out_for_delivery, ready_for_pickup, in_transit, confirmed, created, problem)
- Logowanie niezmapowanych statusów do error_log z pełnym kontekstem (opis, slug, guessed status)
- Fix kolejności keyword matching: IN_TRANSIT przed READY_FOR_PICKUP
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `src/Modules/Shipments/DeliveryStatus.php` | Modified | +6 slugów w mapie, +guessStatusFromDescription() |
| `src/Modules/Shipments/AllegroTrackingService.php` | Modified | Fallback + error_log w fetchAllegroEdgeStatus() |
## Deviations from Plan
### Auto-fixed Issues
**1. Kolejność keyword matching**
- **Found during:** Orkiestrator verification
- **Issue:** "w drodze do odbiorcy" matchował READY_FOR_PICKUP (bo `odbior`) zamiast IN_TRANSIT
- **Fix:** IN_TRANSIT sprawdzany przed READY_FOR_PICKUP, READY_FOR_PICKUP ograniczony do `oczekuje na odb`/`gotowa do odb`
## Next Phase Readiness
**Ready:** Phase 66 complete — Allegro Delivery tracking działa z mapą + fallback + logowaniem
**Blockers:** None
---
*Phase: 66-allegro-delivery-tracking, Plan: 02*
*Completed: 2026-04-03*

View File

@@ -0,0 +1,310 @@
---
phase: 67-paul-codex-executor
plan: 01
type: execute
wave: 1
depends_on: ["65-01"]
files_modified:
- ~/.claude/paul-framework/references/delegated-apply.md
- ~/.claude/paul-framework/workflows/apply-phase.md
- ~/.claude/paul-framework/templates/PLAN.md
autonomous: true
delegation: auto
---
<objective>
## Goal
Dodać Codex CLI (`codex exec`) jako alternatywny executor w PAUL delegated-apply. Reguła: jeśli plan ma >3 tasków auto, 1/3 z nich MUSI iść przez Codex. Plus: auto-fallback na Codex przy quota error Claude.
## Purpose
- Oszczędność 5h limitu Claude Code — Codex jest osobno opłacany
- Rotacja executorów — wymuszenie użycia Codex przy większych planach
- Fallback — gdy Claude limit się kończy, praca nie staje
## Output
- Zaktualizowany `delegated-apply.md` z sekcją Codex executor
- Zaktualizowany `apply-phase.md` z logiką wyboru executor (Claude vs Codex)
- Zaktualizowany `PLAN.md` template z nowym trybem delegation
</objective>
<context>
## Prior Work
@.paul/phases/65-paul-delegated-apply/65-01-SUMMARY.md
## Research: Codex CLI
- Komenda: `codex exec -c 'approval_mode="full-auto"' "prompt"`
- Model: gpt-5.3-codex
- Synchroniczny, non-interactive, zwraca diff + summary
- Timeout: ~30s na proste zadanie, max ~5min na złożone
- Uwagi: pilnować UTF-8 bez BOM, polskie znaki w prompcie
- Skill warnings (niekrytyczne): ignorować
</context>
<acceptance_criteria>
## AC-1: Reguła 1/3 Codex przy >3 taskach
```gherkin
Given plan z 6 taskami type="auto" i delegation: auto
When orkiestrator przydziela executory
Then minimum 2 taski (ceil(6/3)) idą przez Codex
And pozostałe 4 przez Claude sub-agent
And wybór tasków dla Codex to te najprostsze (najmniej plików)
```
## AC-2: Auto-fallback Claude → Codex
```gherkin
Given task delegowany do Claude sub-agent
When Agent tool zwróci błąd quota/rate-limit
Then orkiestrator automatycznie ponawia ten sam task przez codex exec
And loguje: "Task N: Claude quota fallback Codex"
```
## AC-3: Codex micro-prompt zawiera guard UTF-8
```gherkin
Given task przydzielony do Codex
When orkiestrator buduje mikro-prompt
Then prompt zawiera instrukcje: "IMPORTANT: All files must remain UTF-8 without BOM. Preserve Polish characters (ą,ć,ę,ł,ń,ó,ś,ź,ż)."
```
## AC-4: Backward compatible
```gherkin
Given plan z delegation: off lub delegation: auto z <=3 taskami
When orkiestrator wykonuje plan
Then zachowanie identyczne jak dotychczas (bez Codex)
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Rozszerzenie delegated-apply.md o Codex executor</name>
<files>~/.claude/paul-framework/references/delegated-apply.md</files>
<action>
Przeczytaj plik, potem dodaj nowe sekcje.
1. Dodaj nową sekcję "## Codex Executor" PO sekcji "## Evaluation Protocol" a PRZED "## Delegation Modes":
```markdown
## Codex Executor
Codex CLI (`codex exec`) służy jako alternatywny executor dla tasków z PLAN.md.
### Wywołanie
```bash
codex exec -c 'approval_mode="full-auto"' "MICRO_PROMPT"
```
- Synchroniczny, non-interactive
- Timeout Bash: 300000ms (5 min)
- Output zawiera diff + summary — parsowany przez orkiestratora
### Reguła 1/3
Jeśli plan ma **>3 tasków type="auto"**, minimum `ceil(count/3)` tasków MUSI iść przez Codex:
| Tasków auto | Min Codex | Min Claude |
|-------------|-----------|------------|
| 1-3 | 0 | all |
| 4-6 | 2 | reszta |
| 7-9 | 3 | reszta |
| 10+ | ceil(N/3) | reszta |
**Wybór tasków dla Codex:** Orkiestrator przydziela Codexowi taski o najmniejszej liczbie plików w `<files>` (najprostsze). Jeśli równe — pierwsze w kolejności.
**KRYTYCZNE: Brak współdzielenia plików.** Codex i Claude sub-agenty NIE MOGĄ edytować tych samych plików — działają na tym samym working directory. Orkiestrator MUSI sprawdzić `<files>` każdego taska i zapewnić że:
- Żaden plik z tasków Codex nie pokrywa się z plikami tasków Claude
- Jeśli jest konflikt plików → oba taski idą do tego samego executora
- Przy parallel mode: taski z tymi samymi plikami NIE lecą równolegle
### Auto-fallback
Jeśli Claude Agent tool zwróci błąd (quota, rate-limit, timeout):
1. Orkiestrator loguje: "Task N: Claude error → fallback Codex"
2. Ponawia TEN SAM mikro-prompt przez `codex exec`
3. Evaluation protocol identyczny jak dla Claude
### Micro-prompt guard
Każdy mikro-prompt wysyłany do Codex MUSI zawierać na końcu:
```
## IMPORTANT
- All files must remain UTF-8 without BOM encoding
- Preserve all Polish characters: ą,ć,ę,ł,ń,ó,ś,ź,ż,Ą,Ć,Ę,Ł,Ń,Ó,Ś,Ź,Ż
- Do NOT convert file encoding
- Do NOT add BOM markers
```
### Evaluation po Codex
Identyczny jak po Claude sub-agent:
1. git diff --stat
2. Sprawdź done criteria
3. Sprawdź boundaries
4. **Dodatkowe:** `file` command na zmodyfikowanych plikach — potwierdź UTF-8
5. Decyzja: accept / retry / escalate
```
2. W sekcji "## Delegation Modes", zaktualizuj tabelę:
Dodaj wiersz:
```
| codex | `delegation: codex` | Wszystkie taski auto przez Codex exec |
```
W "Kiedy użyć jakiego" dodaj:
```
- **`codex`** — wymuszone użycie Codex dla wszystkich tasków (np. przy niskim limicie Claude)
```
3. W sekcji "## Constraints", dodaj:
```
- Codex exec timeout: 300s (5 min) per task — jeśli task jest większy, rozbij
- Codex nie ma dostępu do MCP servers ani Agent tool — tylko filesystem + shell
```
</action>
<verify>Plik zawiera sekcję "Codex Executor" z regułą 1/3, auto-fallback, micro-prompt guard. Tabela Delegation Modes zawiera tryb codex.</verify>
<done>AC-1 spec: reguła 1/3 zdefiniowana. AC-2 spec: auto-fallback zdefiniowany. AC-3 spec: UTF-8 guard zdefiniowany.</done>
</task>
<task type="auto">
<name>Task 2: Logika executor selection w apply-phase.md</name>
<files>~/.claude/paul-framework/workflows/apply-phase.md</files>
<action>
Przeczytaj plik, potem zmodyfikuj.
1. W kroku `determine_execution_mode`, ROZSZERZ punkt 2 ("If delegated mode"):
Dodaj pod-punkt po identyfikacji task clusters:
```
- Determine executor assignment per task:
a. Count auto tasks in plan
b. If count > 3: assign ceil(count/3) tasks to Codex executor
- Select tasks with fewest files in <files> for Codex
- Remaining tasks go to Claude Agent tool
- CRITICAL: Verify no file overlap between Codex and Claude tasks
If overlap detected → move conflicting task to same executor as its peer
c. If delegation: codex → all auto tasks to Codex
d. If delegation: auto/parallel with <=3 tasks → all to Claude Agent tool
e. Log: "Executor assignment: N Claude, M Codex (no file conflicts)"
```
2. W kroku `execute_tasks`, w bloku "If delegated mode and task type=auto":
Dodaj rozgałęzienie na executor:
```
**If task assigned to Claude Agent tool:**
[existing Agent tool delegation — no changes]
**If task assigned to Codex executor:**
1. Build micro-prompt (same template as Claude)
2. Append UTF-8 guard block to prompt
3. Execute via Bash:
codex exec -c 'approval_mode="full-auto"' "MICRO_PROMPT"
Timeout: 300000ms
4. Parse output for diff and summary
5. Evaluate: git diff + done criteria + boundaries + UTF-8 check
6. Decision: accept / retry (max 3) / escalate
**Auto-fallback (Claude → Codex):**
If Agent tool returns error (quota/rate-limit/connection):
1. Log: "Task N: [name] → Claude error: [reason] → fallback Codex"
2. Re-execute same micro-prompt via codex exec
3. Continue evaluation as normal
```
3. W kroku `finalize`, rozszerz "Delegated execution summary":
```
- Tasks via Claude: N
- Tasks via Codex: N
- Tasks via Codex (fallback): N
```
4. W sekcji `error_handling`, dodaj:
```
**Codex exec timeout:**
- If codex exec exceeds 300s timeout: mark as failed
- Offer retry via Claude Agent tool (reverse fallback)
**Codex encoding issue:**
- If file check shows non-UTF-8 after Codex: auto-fix encoding
- Log warning and continue
```
WAŻNE: Nie usuwaj istniejącej logiki — rozszerzaj.
</action>
<verify>apply-phase.md zawiera: executor assignment w determine_execution_mode, rozgałęzienie Claude/Codex w execute_tasks, auto-fallback, Codex w finalize summary, Codex error handling.</verify>
<done>AC-1: logika 1/3 w orkiestratorze. AC-2: auto-fallback zaimplementowany. AC-4: <=3 tasków = bez Codex.</done>
</task>
<task type="auto">
<name>Task 3: Aktualizacja PLAN.md template</name>
<files>~/.claude/paul-framework/templates/PLAN.md</files>
<action>
Przeczytaj plik, potem zmodyfikuj.
1. W frontmatter YAML, zaktualizuj komentarz przy delegation:
Zmień:
```yaml
delegation: auto # auto (default) | parallel | off — execution mode for /paul:apply
```
na:
```yaml
delegation: auto # auto (default) | parallel | codex | off — execution mode for /paul:apply
```
2. W tabeli Frontmatter Fields, zaktualizuj opis delegation:
Zmień na:
```
| `delegation` | No | Execution mode: `auto` (delegated, 1/3 Codex if >3 tasks, default), `parallel` (delegated + parallel), `codex` (all tasks via Codex), `off` (inline, legacy) |
```
3. W sekcji "## Delegation Mode", dodaj `codex` do tabeli:
```
| `codex` | When Claude limit is low or you want to force Codex for all tasks |
```
WAŻNE: Nie zmieniaj nic innego.
</action>
<verify>PLAN.md template zawiera tryb codex w frontmatter komentarzu, tabeli i sekcji Delegation Mode.</verify>
<done>AC-4 partially: template zaktualizowany o nowy tryb.</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- ~/.claude/paul-framework/workflows/plan-phase.md
- ~/.claude/paul-framework/workflows/unify-phase.md
- ~/.claude/paul-framework/rules/*
- ~/.claude/paul-framework/references/subagent-criteria.md
- .paul/PROJECT.md, .paul/ROADMAP.md
## SCOPE LIMITS
- Nie tworzymy wrapper script dla codex — wywołanie bezpośrednio z Bash
- Nie modyfikujemy Codex config (~/.codex/) — używamy -c flag
- Nie implementujemy monitoring limitu Claude — reaktywny fallback wystarczy
</boundaries>
<verification>
- [ ] delegated-apply.md zawiera sekcję Codex Executor z regułą 1/3
- [ ] apply-phase.md zawiera executor assignment + rozgałęzienie Claude/Codex
- [ ] apply-phase.md zawiera auto-fallback Claude → Codex
- [ ] PLAN.md template zawiera tryb codex
- [ ] Reguła 1/3 dotyczy tylko planów >3 tasków auto
- [ ] UTF-8 guard jest w każdym Codex mikro-prompcie
</verification>
<success_criteria>
- Wszystkie 3 taski completed
- PAUL apply wspiera Codex jako executor
- Reguła 1/3 wymusza rotację przy większych planach
- Auto-fallback chroni przed przestojem przy quota error
- Backward compatible
</success_criteria>
<output>
After completion, create `.paul/phases/67-paul-codex-executor/67-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,97 @@
---
phase: 67-paul-codex-executor
plan: 01
subsystem: infra
tags: [paul-framework, codex, delegation, executor, fallback]
requires:
- phase: 65-paul-delegated-apply
provides: Delegated-apply workflow, micro-prompt template, evaluation protocol
provides:
- Codex CLI as alternative executor in PAUL delegated-apply
- Rule of thirds (1/3 Codex if >3 tasks)
- Auto-fallback Claude → Codex on quota error
- UTF-8 guard for Codex micro-prompts
- File overlap protection between executors
affects: [all future phases using /paul:apply with >3 tasks]
tech-stack:
added: [codex-cli]
patterns: [multi-executor-delegation, auto-fallback, file-conflict-detection]
key-files:
modified:
- ~/.claude/paul-framework/references/delegated-apply.md
- ~/.claude/paul-framework/workflows/apply-phase.md
- ~/.claude/paul-framework/templates/PLAN.md
key-decisions:
- "Reguła 1/3: ceil(count/3) tasków do Codex przy >3 taskach auto"
- "Codex dostaje najprostsze taski (fewest files)"
- "Codex i Claude NIE MOGĄ edytować tych samych plików"
- "Auto-fallback Claude→Codex przy quota error, reverse fallback przy Codex timeout"
- "UTF-8 guard obowiązkowy w każdym Codex micro-prompt"
patterns-established:
- "codex exec -c 'approval_mode=\"full-auto\"' jako non-interactive executor"
- "File overlap check przed przydziałem executorów"
- "delegation: codex jako tryb wymuszający Codex dla wszystkich tasków"
duration: ~10min
started: 2026-04-03T21:00:00Z
completed: 2026-04-03T21:10:00Z
---
# Phase 67 Plan 01: PAUL Codex Executor
**Integracja Codex CLI jako alternatywny executor w PAUL — reguła 1/3 przy >3 taskach, auto-fallback Claude→Codex, ochrona plików przed współdzieleniem executorów, UTF-8 guard.**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~10min |
| Tasks | 3 completed (delegated, parallel) |
| Files modified | 3 |
| Execution mode | Delegated auto (3 sub-agents parallel) |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Reguła 1/3 Codex przy >3 taskach | Pass | Zdefiniowana w delegated-apply.md + apply-phase.md |
| AC-2: Auto-fallback Claude → Codex | Pass | W execute_tasks + error_handling |
| AC-3: UTF-8 guard w Codex prompts | Pass | Micro-prompt guard w delegated-apply.md |
| AC-4: Backward compatible | Pass | <=3 tasków = bez Codex, delegation: off = inline |
## Accomplishments
- Sekcja "Codex Executor" w delegated-apply.md z regułą 1/3, auto-fallback, UTF-8 guard
- Executor assignment logic w apply-phase.md z file overlap protection
- Tryb `delegation: codex` w PLAN.md template
- Error handling: Codex timeout, encoding issues, file conflicts
- Anti-patterns: mixing executors on same files, Polish text without guard
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `~/.claude/paul-framework/references/delegated-apply.md` | Modified | +Codex Executor section, +codex mode, +constraints |
| `~/.claude/paul-framework/workflows/apply-phase.md` | Modified | +executor assignment, +Codex path, +fallback, +error handling |
| `~/.claude/paul-framework/templates/PLAN.md` | Modified | +codex mode w frontmatter, tabeli, sekcji |
## Deviations from Plan
None — plan executed as specified.
## Next Phase Readiness
**Ready:** PAUL wspiera 4 tryby delegation: auto, parallel, codex, off. Codex zintegrowany jako executor z pełną ochroną (file overlap, UTF-8, fallback).
**Blockers:** None
---
*Phase: 67-paul-codex-executor, Plan: 01*
*Completed: 2026-04-03*

View File

@@ -0,0 +1,222 @@
---
phase: 68-code-deduplication-refactor
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- src/Core/Http/SslCertificateResolver.php (new)
- src/Modules/Settings/AllegroApiClient.php
- src/Modules/Settings/ApaczkaApiClient.php
- src/Modules/Settings/ShopproApiClient.php
- src/Modules/Settings/AllegroOAuthClient.php
- src/Modules/Shipments/AllegroTrackingService.php
- src/Modules/Shipments/InpostShipmentService.php
- src/Modules/Shipments/InpostTrackingService.php
- src/Core/Http/ToggleableRepositoryTrait.php (new)
- src/Modules/Automation/AutomationController.php
- src/Modules/Settings/EmailMailboxRepository.php
- src/Modules/Settings/EmailTemplateRepository.php
- src/Modules/Settings/ReceiptConfigRepository.php
- src/Modules/Settings/EmailMailboxController.php
- src/Modules/Settings/EmailTemplateController.php
- src/Modules/Settings/ReceiptConfigController.php
- src/Core/Http/RedirectPathResolver.php (new)
- src/Modules/Settings/AllegroIntegrationController.php
- src/Modules/Settings/ApaczkaIntegrationController.php
- src/Modules/Settings/InpostIntegrationController.php
autonomous: true
delegation: auto
---
<objective>
## Goal
Wyeliminowac zduplikowana logike biznesowa i infrastrukturalna z codebase — 7x getCaBundlePath, 7x toggleStatus, 3x resolveRedirectPath — przez ekstrakcje do wspolnych klas/traitow.
## Purpose
Duplikacja kodu powoduje realne bugi (jak dzisiejszy brak godzin na paragonach — ta sama logika w 2 miejscach, poprawiona tylko w 1). Konsolidacja zmniejsza ryzyko rozsynchronizowania i ulatwia utrzymanie.
## Output
- `Core/Http/SslCertificateResolver.php` — statyczna metoda zastepujaca 7 kopii getCaBundlePath
- `Core/Http/ToggleableRepositoryTrait.php` — trait z metoda toggleActive() dla repozytoriow
- `Core/Http/RedirectPathResolver.php` — statyczna metoda zastepujaca 3 kopie resolveRedirectPath
- 13 zrefaktoryzowanych plikow zrodlowych
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
## Source Files
@src/Modules/Settings/AllegroApiClient.php
@src/Modules/Settings/ApaczkaApiClient.php
@src/Modules/Settings/ShopproApiClient.php
@src/Modules/Settings/AllegroOAuthClient.php
@src/Modules/Shipments/AllegroTrackingService.php
@src/Modules/Shipments/InpostShipmentService.php
@src/Modules/Shipments/InpostTrackingService.php
@src/Modules/Automation/AutomationController.php
@src/Modules/Settings/EmailMailboxRepository.php
@src/Modules/Settings/EmailTemplateRepository.php
@src/Modules/Settings/ReceiptConfigRepository.php
@src/Modules/Settings/AllegroIntegrationController.php
@src/Modules/Settings/ApaczkaIntegrationController.php
@src/Modules/Settings/InpostIntegrationController.php
</context>
<acceptance_criteria>
## AC-1: getCaBundlePath wyeliminowane z 7 klas
```gherkin
Given 7 klas posiada identyczna prywatna metode getCaBundlePath()
When refaktor zostanie zastosowany
Then istnieje jedna klasa SslCertificateResolver ze statyczna metoda resolve()
And zadna z 7 klas nie posiada juz metody getCaBundlePath
And wszystkie wywolania uzywaja SslCertificateResolver::resolve()
And PHP lint przechodzi bez bledow
```
## AC-2: toggleStatus skonsolidowane w trait
```gherkin
Given 4+ repozytoriow posiada podobna metode toggleActive/toggleStatus
When refaktor zostanie zastosowany
Then istnieje trait ToggleableRepositoryTrait z metoda toggleActive()
And repozytoria uzywaja traita zamiast wlasnej implementacji
And kontrolery toggleStatus wywoluja repozytorium bez zmian w zachowaniu
And PHP lint przechodzi bez bledow
```
## AC-3: resolveRedirectPath skonsolidowane
```gherkin
Given 3 kontrolery integracji posiadaja identyczna metode resolveRedirectPath
When refaktor zostanie zastosowany
Then istnieje jedna klasa/metoda RedirectPathResolver
And 3 kontrolery uzywaja wspolnej implementacji
And PHP lint przechodzi bez bledow
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Ekstrakcja SslCertificateResolver z 7 kopii getCaBundlePath</name>
<files>
src/Core/Http/SslCertificateResolver.php (new),
src/Modules/Settings/AllegroApiClient.php,
src/Modules/Settings/ApaczkaApiClient.php,
src/Modules/Settings/ShopproApiClient.php,
src/Modules/Settings/AllegroOAuthClient.php,
src/Modules/Shipments/AllegroTrackingService.php,
src/Modules/Shipments/InpostShipmentService.php,
src/Modules/Shipments/InpostTrackingService.php
</files>
<action>
1. Przeczytaj getCaBundlePath() z jednej z 7 klas (identyczne)
2. Utworz src/Core/Http/SslCertificateResolver.php:
- final class, namespace App\Core\Http
- public static function resolve(): string — logika z getCaBundlePath
3. W kazdej z 7 klas:
- Dodaj use App\Core\Http\SslCertificateResolver
- Zamien wywolania $this->getCaBundlePath() na SslCertificateResolver::resolve()
- Usun prywatna metode getCaBundlePath()
4. Uruchom PHP lint na wszystkich 8 plikach
Unikaj: zmieniania jakiejkolwiek innej logiki w tych klasach
</action>
<verify>PHP lint: php -l na wszystkich 8 plikach + grep -r "getCaBundlePath" src/ zwraca 0 wynikow</verify>
<done>AC-1 satisfied: getCaBundlePath nie istnieje w zadnej z 7 klas, SslCertificateResolver jest jedynym zrodlem</done>
</task>
<task type="auto">
<name>Task 2: Ekstrakcja ToggleableRepositoryTrait z powtorzonej logiki toggleActive</name>
<files>
src/Core/Http/ToggleableRepositoryTrait.php (new),
src/Modules/Settings/EmailMailboxRepository.php,
src/Modules/Settings/EmailTemplateRepository.php,
src/Modules/Settings/ReceiptConfigRepository.php,
src/Modules/Automation/AutomationController.php,
src/Modules/Settings/EmailMailboxController.php,
src/Modules/Settings/EmailTemplateController.php,
src/Modules/Settings/ReceiptConfigController.php
</files>
<action>
1. Przeczytaj metody toggleActive/toggleStatus z repozytoriow — zidentyfikuj wspolny wzorzec
2. Utworz src/Core/Http/ToggleableRepositoryTrait.php:
- trait ToggleableRepositoryTrait, namespace App\Core\Http
- Metoda toggleActive(string $table, int $id, string $column = 'is_active'): bool
- Wymaga aby klasa uzywajaca traita miala property $db (Medoo)
3. W kazdym repozytorium z toggleActive:
- Dodaj use ToggleableRepositoryTrait
- Usun lokalna metode toggleActive/toggleStatus
- Jesli sygnatura sie rozni, dostosuj wywolania w kontrolerach
4. PHP lint na wszystkich zmienionych plikach
Unikaj: zmieniania logiki kontrolerow poza dostosowaniem wywolan toggle
</action>
<verify>PHP lint na wszystkich zmienionych plikach + metody toggleActive/toggleStatus nie istnieja lokalnie w repozytoriach</verify>
<done>AC-2 satisfied: trait ToggleableRepositoryTrait jest jedynym zrodlem logiki toggle</done>
</task>
<task type="auto">
<name>Task 3: Ekstrakcja RedirectPathResolver z 3 kontrolerow integracji</name>
<files>
src/Core/Http/RedirectPathResolver.php (new),
src/Modules/Settings/AllegroIntegrationController.php,
src/Modules/Settings/ApaczkaIntegrationController.php,
src/Modules/Settings/InpostIntegrationController.php
</files>
<action>
1. Przeczytaj resolveRedirectPath() z 3 kontrolerow — potwierdz identycznosc
2. Utworz src/Core/Http/RedirectPathResolver.php:
- final class, namespace App\Core\Http
- public static function resolve(string $requestedPath, array $allowedPaths, string $default): string
3. W kazdym z 3 kontrolerow:
- Dodaj use App\Core\Http\RedirectPathResolver
- Zamien wywolania $this->resolveRedirectPath(...) na RedirectPathResolver::resolve(...)
- Usun prywatna metode resolveRedirectPath()
4. PHP lint na 4 plikach
Unikaj: zmieniania logiki walidacji/przekierowan poza ekstrakcja
</action>
<verify>PHP lint na 4 plikach + grep -r "function resolveRedirectPath" src/ zwraca 0 wynikow</verify>
<done>AC-3 satisfied: resolveRedirectPath nie istnieje w zadnym z 3 kontrolerow</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- database/migrations/* — brak zmian schematu
- resources/views/* — brak zmian widokow
- routes/web.php — brak zmian routingu (chyba ze wymagane przez nowe zależności DI)
- Logika biznesowa w zmienianych klasach — refaktor dotyczy TYLKO duplikacji
## SCOPE LIMITS
- Ten plan obejmuje tylko getCaBundlePath, toggleStatus i resolveRedirectPath
- Duplikacje apiRequest, downloadLabel, buildReceiverAddress, resolveColumns — osobny plan (68-02)
- Nie refaktoryzowac validateCsrf (wymaga analizy middleware — osobny plan)
</boundaries>
<verification>
Before declaring plan complete:
- [ ] PHP lint przechodzi na wszystkich zmienionych plikach
- [ ] grep -r "getCaBundlePath" src/ — 0 wynikow
- [ ] grep -r "function resolveRedirectPath" src/ — 0 wynikow
- [ ] Ponowna analiza duplikacji w codebase — potwierdzenie eliminacji
- [ ] Wszystkie acceptance criteria spelnione
</verification>
<success_criteria>
- 7 kopii getCaBundlePath → 1 klasa SslCertificateResolver
- 7 kopii toggleStatus → 1 trait ToggleableRepositoryTrait
- 3 kopie resolveRedirectPath → 1 klasa RedirectPathResolver
- ~170 linii zduplikowanego kodu wyeliminowane
- Zero regresji (PHP lint clean)
- Ponowna analiza duplikacji potwierdza eliminacje
</success_criteria>
<output>
After completion, create `.paul/phases/68-code-deduplication-refactor/68-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,149 @@
---
phase: 68-code-deduplication-refactor
plan: 01
subsystem: infra
tags: [refactor, deduplication, trait, static-helper]
requires: []
provides:
- SslCertificateResolver — single source for CA bundle path
- ToggleableRepositoryTrait — reusable toggle pattern for repositories
- RedirectPathResolver — single source for redirect path validation
- ReceiptService — single source for receipt issuance logic
affects: [future integrations, new repositories with toggle, new controllers with redirects]
tech-stack:
added: []
patterns: [static resolver classes, repository traits]
key-files:
created:
- src/Core/Http/SslCertificateResolver.php
- src/Core/Http/ToggleableRepositoryTrait.php
- src/Core/Http/RedirectPathResolver.php
- src/Modules/Accounting/ReceiptService.php
- src/Modules/Accounting/ReceiptIssueException.php
modified:
- src/Modules/Settings/AllegroApiClient.php
- src/Modules/Settings/ApaczkaApiClient.php
- src/Modules/Settings/ShopproApiClient.php
- src/Modules/Settings/AllegroOAuthClient.php
- src/Modules/Shipments/AllegroTrackingService.php
- src/Modules/Shipments/InpostShipmentService.php
- src/Modules/Shipments/InpostTrackingService.php
- src/Modules/Settings/EmailMailboxRepository.php
- src/Modules/Settings/EmailTemplateRepository.php
- src/Modules/Settings/ReceiptConfigRepository.php
- src/Modules/Automation/AutomationRepository.php
- src/Modules/Settings/AllegroIntegrationController.php
- src/Modules/Settings/ApaczkaIntegrationController.php
- src/Modules/Settings/InpostIntegrationController.php
- src/Modules/Accounting/ReceiptController.php
- src/Modules/Automation/AutomationService.php
- src/Modules/Cron/CronHandlerFactory.php
- routes/web.php
key-decisions:
- "Static resolver classes for stateless utilities (SSL, redirect)"
- "Trait for toggle pattern — repos keep thin public wrapper for backward compat"
- "ReceiptService as full service class (not static) — needs DI dependencies"
patterns-established:
- "Stateless utility → final class with static methods in Core/Http/"
- "Shared repo behavior → trait in Core/Http/ with $this->db/$this->pdo access"
duration: ~25min
started: 2026-04-03T19:10:00Z
completed: 2026-04-03T19:35:00Z
---
# Phase 68 Plan 01: Code Deduplication Refactor Summary
**Extracted 3 shared utilities + 1 service from 17+ duplicated methods across codebase, eliminating ~250 lines of copy-pasted code**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~25min |
| Started | 2026-04-03T19:10:00Z |
| Completed | 2026-04-03T19:35:00Z |
| Tasks | 3 completed (+ 1 pre-plan ReceiptService) |
| Files modified | 18 |
| Delegation | 3 tasks via Claude Agent (parallel) |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: getCaBundlePath eliminated from 7 classes | Pass | 0 occurrences remain, SslCertificateResolver is sole source |
| AC-2: toggleStatus consolidated in trait | Pass | 4 repos use ToggleableRepositoryTrait, controllers unchanged |
| AC-3: resolveRedirectPath consolidated | Pass | 0 occurrences remain, RedirectPathResolver is sole source |
## Accomplishments
- Extracted `SslCertificateResolver` replacing 7 identical `getCaBundlePath()` copies
- Extracted `ToggleableRepositoryTrait` used by 4 repositories (Automation, EmailMailbox, EmailTemplate, ReceiptConfig)
- Extracted `RedirectPathResolver` replacing 3 identical `resolveRedirectPath()` copies
- Extracted `ReceiptService` consolidating receipt issuance from ReceiptController + AutomationService (pre-plan bugfix that prompted this phase)
- Fixed receipt datetime bug: `date('Y-m-d')``date('Y-m-d H:i:s')` in automation path
- Migration 000077: `sale_date` DATE→DATETIME + backfill old receipts from `created_at`
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `src/Core/Http/SslCertificateResolver.php` | Created | CA bundle path resolution (was in 7 classes) |
| `src/Core/Http/ToggleableRepositoryTrait.php` | Created | Shared toggle active/inactive pattern |
| `src/Core/Http/RedirectPathResolver.php` | Created | Redirect path validation (was in 3 controllers) |
| `src/Modules/Accounting/ReceiptService.php` | Created | Centralized receipt issuance logic |
| `src/Modules/Accounting/ReceiptIssueException.php` | Created | Dedicated exception for receipt errors |
| `database/migrations/20260403_000077_*` | Created | sale_date DATETIME + backfill |
| 7x API/Tracking clients | Modified | getCaBundlePath → SslCertificateResolver |
| 4x Repositories | Modified | toggleActive → ToggleableRepositoryTrait |
| 3x Integration controllers | Modified | resolveRedirectPath → RedirectPathResolver |
| `ReceiptController.php` | Modified | Delegates to ReceiptService |
| `AutomationService.php` | Modified | Delegates to ReceiptService, removed 7 private methods |
| `CronHandlerFactory.php` | Modified | Wires ReceiptService |
| `routes/web.php` | Modified | Wires ReceiptService |
## Deviations from Plan
### Summary
| Type | Count | Impact |
|------|-------|--------|
| Scope additions | 1 | ReceiptService extracted pre-plan as bugfix response |
| Auto-fixed | 0 | None |
| Deferred | 0 | None |
**Total impact:** ReceiptService was essential — it fixed the datetime bug AND eliminated the largest duplication case.
## Re-analysis Results (Post-Refactor)
Remaining duplications found:
| Priority | Issue | Copies | Recommendation |
|----------|-------|--------|----------------|
| HIGH | validateCsrf() | 6 | CsrfValidator service |
| MEDIUM | isActive filter closure | 3 | Utility function |
| LOW | array_values(array_filter()) | 12+ | ArrayHelper |
| LOW | date instantiation pattern | 8+ | Monitor only |
| LOW | array_map('intval') | 4 | Monitor only |
## Next Phase Readiness
**Ready:**
- Core/Http/ established as location for shared utilities
- Pattern proven: static resolvers + traits work cleanly with existing architecture
- Re-analysis baseline documented for plan 68-02
**Concerns:**
- validateCsrf duplication is security-sensitive — should be next priority
- ToggleableRepositoryTrait uses `$this->pdo` — verify consistency across repos
**Blockers:** None
---
*Phase: 68-code-deduplication-refactor, Plan: 01*
*Completed: 2026-04-03*