- Email notyfikacji zawiera numer zamówienia, klienta, datę, kwotę - Order joby (send_order, sync_payment, sync_status) ponawiane w nieskończoność co 30 min - Rozróżnienie PONAWIANY vs TRWAŁY BŁĄD w emailu - Cleanup stuck jobów po udanym wysłaniu zamówienia - +2 testy infinite retry w CronJobRepositoryTest Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
202 lines
7.9 KiB
Markdown
202 lines
7.9 KiB
Markdown
---
|
|
phase: 09-apilo-email-fix
|
|
plan: 01
|
|
type: execute
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified: [cron.php, autoload/Domain/CronJob/CronJobRepository.php, autoload/Domain/CronJob/CronJobType.php]
|
|
autonomous: true
|
|
---
|
|
|
|
<objective>
|
|
## Goal
|
|
1. Wzbogacić email notyfikacji o trwałym błędzie Apilo o czytelne dane zamówienia (numer, klient, kwota)
|
|
2. Zamówienia Apilo (send_order, sync_payment, sync_status) muszą być ponawiane w nieskończoność co 30 minut
|
|
3. Email o błędzie nadal wysyłany (jako ostrzeżenie), ale job wraca do pending zamiast permanent failure
|
|
4. Po udanym wysłaniu zamówienia — czyścimy powiązane failed/pending joby
|
|
|
|
## Purpose
|
|
Administrator dostaje email bez informacji o którym zamówieniu chodzi. Dodatkowo, po 10 próbach zamówienie przestaje być synchronizowane — to niedopuszczalne, bo zamówienie musi trafić do Apilo.
|
|
|
|
## Output
|
|
- Zmodyfikowany `cron.php` — lepsza treść emaila + czyszczenie jobów po sukcesie
|
|
- Zmodyfikowany `CronJobRepository` — obsługa infinite retry
|
|
- Zmodyfikowany `CronJobType` — stała backoffu 30min dla Apilo
|
|
</objective>
|
|
|
|
<context>
|
|
## Project Context
|
|
@.paul/PROJECT.md
|
|
@.paul/ROADMAP.md
|
|
|
|
## Prior Work
|
|
@.paul/phases/08-apilo-orders-fix/08-01-SUMMARY.md
|
|
|
|
## Source Files
|
|
@cron.php (linie 198-529 — handler APILO_SEND_ORDER, linie 763-781 — email notification)
|
|
@autoload/Domain/CronJob/CronJobRepository.php (markFailed — linie 131-156)
|
|
@autoload/Domain/CronJob/CronJobType.php (stałe backoff)
|
|
</context>
|
|
|
|
<acceptance_criteria>
|
|
|
|
## AC-1: Email zawiera czytelne dane zamówienia
|
|
```gherkin
|
|
Given trwale nieudane zadanie Apilo z payload zawierającym order_id
|
|
When system wysyła email notyfikacji
|
|
Then email zawiera: numer zamówienia, dane klienta, datę zamówienia, kwotę
|
|
And temat emaila zawiera numery zamówień
|
|
```
|
|
|
|
## AC-2: Brak order_id w payload nie powoduje błędu
|
|
```gherkin
|
|
Given trwale nieudane zadanie Apilo bez order_id w payload (np. apilo_token_keepalive)
|
|
When system wysyła email notyfikacji
|
|
Then email wyświetla dane job-a bez sekcji zamówienia, bez błędów
|
|
```
|
|
|
|
## AC-3: Joby zamówień Apilo ponawiają się w nieskończoność co 30 minut
|
|
```gherkin
|
|
Given job typu apilo_send_order, apilo_sync_payment lub apilo_sync_status
|
|
When job osiąga max_attempts
|
|
Then job NIE jest oznaczany jako failed
|
|
And job wraca do pending ze scheduled_at = now + 30 minut
|
|
And email ostrzegawczy jest wysyłany (z informacją że job dalej jest ponawiany)
|
|
```
|
|
|
|
## AC-4: Inne joby Apilo (token, product sync) nadal mają limit prób
|
|
```gherkin
|
|
Given job typu apilo_token_keepalive lub apilo_product_sync
|
|
When job osiąga max_attempts
|
|
Then job jest oznaczany jako failed (zachowanie bez zmian)
|
|
```
|
|
|
|
## AC-5: Po udanym wysłaniu zamówienia czyszczone są powiązane failed joby
|
|
```gherkin
|
|
Given zamówienie wysłane pomyślnie do Apilo (apilo_order_id ustawiony)
|
|
When handler APILO_SEND_ORDER kończy się sukcesem
|
|
Then powiązane joby apilo_sync_payment i apilo_sync_status ze statusem failed
|
|
zostają usunięte lub anulowane (żeby nie zaśmiecały kolejki)
|
|
```
|
|
|
|
</acceptance_criteria>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Infinite retry dla order-related Apilo jobów</name>
|
|
<files>autoload/Domain/CronJob/CronJobType.php, autoload/Domain/CronJob/CronJobRepository.php</files>
|
|
<action>
|
|
**CronJobType.php:**
|
|
1. Dodać stałą `APILO_ORDER_BACKOFF_SECONDS = 1800` (30 minut)
|
|
2. Dodać statyczną metodę `isOrderRelatedApiloJob($jobType)` zwracającą true dla:
|
|
- APILO_SEND_ORDER, APILO_SYNC_PAYMENT, APILO_SYNC_STATUS
|
|
|
|
**CronJobRepository::markFailed():**
|
|
3. Przed sprawdzeniem `$attempts >= $maxAttempts`:
|
|
- Pobrać `job_type` z bazy (dodać do selecta w linia 133)
|
|
- Jeśli `CronJobType::isOrderRelatedApiloJob($jobType)`:
|
|
- ZAWSZE wracaj do pending (nigdy failed)
|
|
- Użyj stałego backoffu `APILO_ORDER_BACKOFF_SECONDS` zamiast exponential
|
|
- Ustaw `last_error` jak normalnie
|
|
- Dla pozostałych jobów — logika bez zmian
|
|
|
|
UWAGA: Nie zmieniaj sygnatury markFailed() — dodaj job_type do wewnętrznego selecta
|
|
</action>
|
|
<verify>
|
|
1. Przeczytaj kod i zweryfikuj że:
|
|
- isOrderRelatedApiloJob zwraca true tylko dla 3 typów
|
|
- markFailed nigdy nie ustawia status=failed dla tych typów
|
|
- Inne joby zachowują się jak dotychczas
|
|
2. Uruchom: ./test.ps1 tests/Unit/Domain/CronJob/
|
|
</verify>
|
|
<done>AC-3, AC-4 satisfied: Order joby retry w nieskończoność, inne bez zmian</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Lepszy email + ostrzeżenie zamiast trwałego błędu + czyszczenie po sukcesie</name>
|
|
<files>cron.php</files>
|
|
<action>
|
|
**Email notification (linie ~763-781):**
|
|
1. Zmienić query o failed joby — RÓWNIEŻ szukać order-related jobów w statusie pending z dużą liczbą prób (np. attempts >= 10), żeby wysyłać ostrzeżenie
|
|
2. Dla każdego job-a: sparsować payload (json_decode jeśli string), wyciągnąć order_id
|
|
3. Jeśli order_id istnieje — pobrać z pp_shop_orders:
|
|
- `order_number` (lub `id` jeśli brak), `client_name`/`client_surname`, `date_order`, `total_brutto`
|
|
4. Sformatować email:
|
|
```
|
|
Job #X (apilo_send_order) — PONAWIANY CO 30 MIN
|
|
Zamówienie: #12345 (ID: 678)
|
|
Klient: Jan Kowalski
|
|
Data zamówienia: 2026-03-19 14:30:00
|
|
Kwota: 199.99 PLN
|
|
Próby: 15
|
|
Błąd: [last_error]
|
|
Ostatnia próba: [updated_at lub scheduled_at]
|
|
```
|
|
5. Dla jobów permanent failed (nie-order): zachować stary format "trwały błąd"
|
|
6. Temat: dodać numery zamówień jeśli dostępne
|
|
7. Email ma rozróżniać: "PONAWIANY" vs "TRWAŁY BŁĄD" w zależności od typu joba
|
|
|
|
**Czyszczenie po sukcesie (w handlerze APILO_SEND_ORDER, po linii ~522-524):**
|
|
8. Po pomyślnym wysłaniu zamówienia (`apilo_order_id` ustawiony):
|
|
- Usunąć/anulować failed/pending joby `apilo_sync_payment` i `apilo_sync_status`
|
|
z payload zawierającym ten sam order_id
|
|
- Użyć: `$mdb->delete('pp_cron_jobs', [...])` lub update status=cancelled
|
|
- To zapobiega zaśmiecaniu kolejki starymi retry jobami
|
|
|
|
UWAGA:
|
|
- Nazwy kolumn zamówienia: sprawdź jakie faktycznie są w pp_shop_orders (mogą być polskie)
|
|
- Payload w bazie to JSON string — json_decode($fj['payload'], true)
|
|
- Nie zmieniaj logiki wysyłania zamówień — tylko email i cleanup
|
|
</action>
|
|
<verify>
|
|
1. Przeczytaj zmodyfikowany kod
|
|
2. Zweryfikuj że query do pp_shop_orders używa poprawnych kolumn
|
|
3. Zweryfikuj brak błędów PHP (null handling, json_decode guard)
|
|
4. Uruchom: ./test.ps1
|
|
</verify>
|
|
<done>AC-1, AC-2, AC-5 satisfied: Email czytelny, cleanup po sukcesie</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<boundaries>
|
|
|
|
## DO NOT CHANGE
|
|
- Logikę wysyłania zamówień do Apilo (curl, payload budowanie)
|
|
- Logikę exponential backoff dla NIE-order jobów
|
|
- Handlery APILO_SYNC_PAYMENT, APILO_SYNC_STATUS, APILO_STATUS_POLL (poza cleanup)
|
|
- Odbiorcę emaila i warunki wysyłki (poza rozszerzeniem query)
|
|
- Tabelę pp_shop_orders — żadnych nowych kolumn
|
|
|
|
## SCOPE LIMITS
|
|
- Tylko retry logic, email formatting, i cleanup
|
|
- Nie dodawać nowych tabel
|
|
- Nie zmieniać enqueue() ani fetchNext()
|
|
|
|
</boundaries>
|
|
|
|
<verification>
|
|
Before declaring plan complete:
|
|
- [ ] Order-related Apilo joby nigdy nie dostają status=failed
|
|
- [ ] Backoff dla order jobów = stałe 30 min
|
|
- [ ] Inne joby zachowują stare zachowanie (exponential, max 10)
|
|
- [ ] Email zawiera numer zamówienia gdy dostępny
|
|
- [ ] Email rozróżnia "ponawiany" vs "trwały błąd"
|
|
- [ ] Po sukcesie wysyłki czyścimy related joby
|
|
- [ ] Brak błędów PHP
|
|
- [ ] Testy przechodzą (./test.ps1)
|
|
- [ ] All acceptance criteria met
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- Zamówienia Apilo są ponawiane w nieskończoność co 30 min
|
|
- Email notyfikacji zawiera czytelne dane zamówienia
|
|
- Po udanym wysłaniu czyszczone są stare joby
|
|
- Zero regresji w testach
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.paul/phases/09-apilo-email-fix/09-01-SUMMARY.md`
|
|
</output>
|