Files
shopPRO/.paul/phases/09-apilo-email-fix/09-01-PLAN.md
Jacek 72864d18ba fix: Apilo email z danymi zamówienia + infinite retry co 30 min dla order jobów
- 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>
2026-03-19 11:23:02 +01:00

7.9 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous
phase plan type wave depends_on files_modified autonomous
09-apilo-email-fix 01 execute 1
cron.php
autoload/Domain/CronJob/CronJobRepository.php
autoload/Domain/CronJob/CronJobType.php
true
## 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
## 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)

<acceptance_criteria>

AC-1: Email zawiera czytelne dane zamówienia

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

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

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

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

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>

Task 1: Infinite retry dla order-related Apilo jobów autoload/Domain/CronJob/CronJobType.php, autoload/Domain/CronJob/CronJobRepository.php **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
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/ AC-3, AC-4 satisfied: Order joby retry w nieskończoność, inne bez zmian Task 2: Lepszy email + ostrzeżenie zamiast trwałego błędu + czyszczenie po sukcesie cron.php **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
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 AC-1, AC-2, AC-5 satisfied: Email czytelny, cleanup po sukcesie

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()
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

<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>
After completion, create `.paul/phases/09-apilo-email-fix/09-01-SUMMARY.md`