diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md
index ac77504..58edda2 100644
--- a/.paul/ROADMAP.md
+++ b/.paul/ROADMAP.md
@@ -34,6 +34,7 @@ Status: Planning
| Phase | Name | Plans | Status | Completed |
|-------|------|-------|--------|-----------|
| 7 | Coupon Fatal Error — order placement crash | 1 | Done | 2026-03-15 |
+| 8 | Apilo orders not sending — diagnoza i naprawa | 1 | Done | 2026-03-16 |
## Phase Details
@@ -65,4 +66,4 @@ Status: Planning
---
*Roadmap created: 2026-03-12*
-*Last updated: 2026-03-12*
+*Last updated: 2026-03-16*
diff --git a/.paul/STATE.md b/.paul/STATE.md
index dc72270..4c83e50 100644
--- a/.paul/STATE.md
+++ b/.paul/STATE.md
@@ -5,25 +5,25 @@
See: .paul/PROJECT.md (updated 2026-03-12)
**Core value:** Właściciel sklepu ma pełną kontrolę nad sprzedażą online w jednym systemie pisanym od podstaw, bez narzutów zewnętrznych platform.
-**Current focus:** Hotfix coupon crash — COMPLETE
+**Current focus:** Hotfix Apilo — COMPLETE
## Current Position
-Milestone: Hotfix — coupon order crash
-Phase: 7 — Fix coupon stdClass method call crash — Complete
-Plan: 07-01 complete (phase done)
-Status: UNIFY complete, phase 7 finished
-Last activity: 2026-03-15 — 07-01 UNIFY complete
+Milestone: Hotfix — Apilo orders not sending
+Phase: 8 — Diagnoza i naprawa wysyłki zamówień do Apilo — Complete
+Plan: 08-01 complete (phase done)
+Status: UNIFY complete, phase 8 finished
+Last activity: 2026-03-16 — 08-01 UNIFY complete
Progress:
-- Phase 7: [██████████] 100% (COMPLETE)
+- Phase 8: [██████████] 100% (COMPLETE)
## Loop Position
-Current loop state (phase 7, plan 01):
+Current loop state (phase 8, plan 01):
```
PLAN ──▶ APPLY ──▶ UNIFY
- ✓ ✓ ✓ [Phase 7 complete]
+ ✓ ✓ ✓ [Phase 8 complete]
```
Previous phases:
@@ -32,12 +32,16 @@ Phase 4: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-0
Phase 5: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-03-12]
Phase 6: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-03-12]
Phase 7: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-03-15]
+Phase 8: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-03-16]
```
## Accumulated Context
### Decisions
- Use existing `CouponRepository::markAsUsed()` instead of adding methods to stdClass
+- 2026-03-16: Przyczyna braku wysyłki = brakujące $apiloRepository w use() closures cron.php (regresja z fazy 6)
+- 2026-03-16: Retry -1 orders co 1h zamiast permanent failure
+- 2026-03-16: Email notification o trwale failed Apilo jobach
### Deferred Issues
None.
@@ -47,10 +51,10 @@ None.
## Session Continuity
-Last session: 2026-03-15
-Stopped at: Phase 07 UNIFY complete — hotfix loop closed
-Next action: Deploy fix to production, then /paul:progress for next work
-Resume file: .paul/phases/07-coupon-bugfix/07-01-SUMMARY.md
+Last session: 2026-03-16
+Stopped at: Phase 08 UNIFY complete — Apilo fix loop closed
+Next action: Deploy fix to instance, then /paul:progress for next work
+Resume file: .paul/phases/08-apilo-orders-fix/08-01-SUMMARY.md
---
*STATE.md — Updated after every significant action*
diff --git a/.paul/phases/08-apilo-orders-fix/08-01-PLAN.md b/.paul/phases/08-apilo-orders-fix/08-01-PLAN.md
new file mode 100644
index 0000000..282ba79
--- /dev/null
+++ b/.paul/phases/08-apilo-orders-fix/08-01-PLAN.md
@@ -0,0 +1,186 @@
+---
+phase: 08-apilo-orders-fix
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified: [cron.php, autoload/Domain/Integrations/ApiloRepository.php]
+autonomous: false
+---
+
+
+## Goal
+Zdiagnozować dlaczego zamówienia przestały się wysyłać do apilo.com, naprawić przyczynę, i zapewnić wysłanie zaległych zamówień.
+
+## Purpose
+Zamówienia nie trafiają do systemu realizacji (Apilo) — blokuje to obsługę klientów i wysyłkę paczek. Krytyczny bugfix produkcyjny.
+
+## Output
+- Zidentyfikowana i naprawiona przyczyna braku wysyłki zamówień
+- Zaległe zamówienia (apilo_order_id = NULL lub -1) gotowe do wysłania przez cron
+
+
+
+## Project Context
+@.paul/PROJECT.md
+@.paul/ROADMAP.md
+@.paul/STATE.md
+
+## Source Files
+@cron.php (linie 197-521 — handler APILO_SEND_ORDER)
+@autoload/Domain/Integrations/ApiloRepository.php (token management, API calls)
+@autoload/Domain/Integrations/IntegrationsRepository.php (getSettings)
+@autoload/Domain/Integrations/ApiloLogger.php (logging)
+@autoload/Domain/Order/OrderAdminService.php (sendOrderToApilo, sync methods)
+
+## Technical Context — Apilo Order Flow
+1. Cron pobiera zamówienia z `apilo_order_id = NULL` i `date_order >= sync_orders_date_start`
+2. Warunki wysyłki (linia 200): enabled=1, sync_orders=1, access-token exists, sync_orders_date_start <= now
+3. Jeden order per cron run (LIMIT 1)
+4. Failure markers: -1 = permanent HTTP error, -2 = zerowe ceny (oba NIE są retried automatycznie)
+5. Token keepalive: co 5min, refresh 300s przed wygaśnięciem
+6. Config: pp_shop_apilo_settings (key-value, provider='apilo')
+
+## User Context
+Użytkownik dodał dostępy do bazy danych w config.php. Zamówienia nie wysyłają się do apilo.com — potrzebna diagnoza i naprawa.
+
+
+
+## Required Skills (from SPECIAL-FLOWS.md)
+
+No specialized flows required for hotfix debugging.
+
+
+
+
+## AC-1: Przyczyna zdiagnozowana
+```gherkin
+Given zamówienia przestały się wysyłać do Apilo
+When przeanalizuję logi (pp_log), ustawienia Apilo (pp_shop_apilo_settings), i konfigurację crona
+Then zidentyfikuję konkretną przyczynę problemu (np. wygasły token, wyłączona sync, błąd API)
+```
+
+## AC-2: Przyczyna naprawiona
+```gherkin
+Given znana przyczyna braku wysyłki
+When zastosuję poprawkę (kod lub konfiguracja)
+Then nowe zamówienia będą się poprawnie wysyłać przez cron do Apilo
+```
+
+## AC-3: Zaległe zamówienia gotowe do wysłania
+```gherkin
+Given istnieją zamówienia z apilo_order_id = NULL lub -1 które powinny być w Apilo
+When zresetuję failed orders (apilo_order_id = -1 → NULL) i sprawdzę że cron je przetworzy
+Then zaległe zamówienia wyślą się do Apilo przy kolejnych uruchomieniach crona
+```
+
+
+
+
+
+
+ Task 1: Diagnoza — sprawdzenie logów i konfiguracji Apilo
+ cron.php, autoload/Domain/Integrations/ApiloRepository.php, autoload/Domain/Integrations/IntegrationsRepository.php
+
+ Sprawdzić przyczynę problemu analizując:
+
+ 1. **Logi Apilo na serwerze** — pobrać logs/apilo.txt z FTP (zgodnie z CLAUDE.md: "Za każdym razem jak próbujesz sprawdzić jakiś plik z logami spróbuj go najpierw pobrać z serwera FTP")
+ 2. **Logi w bazie** — sprawdzić ostatnie wpisy w pp_log (action LIKE '%apilo%' lub '%send_order%') — kiedy ostatni sukces, czy są errory
+ 3. **Ustawienia Apilo** — sprawdzić pp_shop_apilo_settings:
+ - enabled = 1?
+ - sync_orders = 1?
+ - access-token istnieje i nie wygasł? (access-token-expire-at vs now)
+ - refresh-token istnieje i nie wygasł?
+ - sync_orders_date_start — jaka data?
+ - token-keepalive-at — kiedy ostatni keepalive?
+ 4. **Zaległe zamówienia** — ile jest zamówień z apilo_order_id = NULL i z -1?
+ 5. **Cron execution** — czy cron.php jest w ogóle wywoływany? (sprawdzić pp_cron_jobs — czy są scheduled/processed joby)
+
+ Na podstawie diagnozy określić przyczynę i plan naprawy.
+
+ Przyczyna zidentyfikowana i udokumentowana
+ AC-1 satisfied: Znana przyczyna braku wysyłki zamówień
+
+
+
+ Wybór strategii naprawy na podstawie diagnozy
+ Bez diagnozy nie wiemy co dokładnie naprawić. Przyczyna może być: wygasły token OAuth, wyłączona synchronizacja, błąd w kodzie, problem z cronem, lub inna przyczyna.
+
+
+ Naprawa tokenu OAuth
+ Jeśli token wygasł — refresh lub re-autoryzacja naprawi problem
+ Wymaga dostępu do panelu admina lub bezpośredniej zmiany w DB
+
+
+ Naprawa konfiguracji (settings)
+ Proste — zmiana wartości w pp_shop_apilo_settings
+ Może nie być jedyną przyczyną
+
+
+ Naprawa kodu (bug w cron.php lub ApiloRepository)
+ Trwała naprawa jeśli problem jest w logice
+ Wymaga deployu nowego kodu na serwer
+
+
+ Inna przyczyna (cron nie działa, serwer, API Apilo)
+ Identyfikacja zewnętrznego problemu
+ Może wymagać działań poza kodem
+
+
+ Po diagnozie — wybierz strategię naprawy lub opisz co znalazłeś
+
+
+
+ Task 3: Naprawa i reset zaległych zamówień
+ cron.php, autoload/Domain/Integrations/ApiloRepository.php
+
+ Na podstawie wybranej strategii:
+
+ 1. **Zastosować poprawkę** (kod, konfiguracja, lub token refresh)
+ 2. **Reset failed orders** — zamówienia z apilo_order_id = -1 które powinny być wysłane:
+ - Przygotować query: UPDATE pp_shop_orders SET apilo_order_id = NULL WHERE apilo_order_id = -1 AND date_order >= '{sync_start_date}'
+ - LUB użyć sendOrderToApilo() z panelu admina dla poszczególnych zamówień
+ 3. **Weryfikacja** — uruchomić cron.php ręcznie i sprawdzić czy zamówienie się wysyła
+
+ Unikać: resetowania zamówień z apilo_order_id = -2 (zerowe ceny — świadomy skip)
+
+ Ręczne uruchomienie cron.php wysyła zamówienie do Apilo (sprawdzić pp_log i response)
+ AC-2 + AC-3 satisfied: Przyczyna naprawiona, zaległe zamówienia gotowe do wysłania
+
+
+
+
+
+
+## DO NOT CHANGE
+- autoload/Domain/Order/OrderRepository.php (order creation logic)
+- autoload/Domain/CronJob/ (cron job infrastructure)
+- Logika obsługi płatności i statusów w cron.php (handlers 3-11)
+
+## SCOPE LIMITS
+- Tylko diagnoza i naprawa problemu wysyłki zamówień do Apilo
+- Nie refaktoryzować kodu cron.php ani ApiloRepository
+- Nie zmieniać flow tworzenia zamówień
+- Nie dodawać nowych funkcji (np. auto-retry)
+
+
+
+
+Before declaring plan complete:
+- [ ] Przyczyna braku wysyłki zidentyfikowana
+- [ ] Poprawka zastosowana (kod lub konfiguracja)
+- [ ] Przynajmniej jedno zamówienie wysłane do Apilo po naprawie
+- [ ] Zaległe zamówienia zresetowane (apilo_order_id = NULL) i gotowe do przetworzenia
+- [ ] Brak nowych błędów w logach po naprawie
+
+
+
+- Przyczyna zdiagnozowana i udokumentowana
+- Nowe zamówienia wysyłają się poprawnie przez cron
+- Zaległe zamówienia z apilo_order_id = NULL/-1 zresetowane i gotowe do wysłania
+- Brak regresji w istniejącej funkcjonalności
+
+
+
+After completion, create `.paul/phases/08-apilo-orders-fix/08-01-SUMMARY.md`
+
diff --git a/.paul/phases/08-apilo-orders-fix/08-01-SUMMARY.md b/.paul/phases/08-apilo-orders-fix/08-01-SUMMARY.md
new file mode 100644
index 0000000..270f62f
--- /dev/null
+++ b/.paul/phases/08-apilo-orders-fix/08-01-SUMMARY.md
@@ -0,0 +1,121 @@
+---
+phase: 08-apilo-orders-fix
+plan: 01
+subsystem: integrations
+tags: [apilo, cron, closure, bugfix]
+
+requires:
+ - phase: 06-integrations-refactoring
+ provides: ApiloRepository split from IntegrationsRepository
+
+provides:
+ - Fix for missing $apiloRepository in cron.php closure use() clauses
+ - Auto-retry for failed orders (apilo_order_id = -1) with 1h interval
+ - Email notifications for Apilo sync errors (cURL + permanently failed jobs)
+
+affects: []
+
+tech-stack:
+ added: []
+ patterns: []
+
+key-files:
+ created: []
+ modified: [cron.php]
+
+key-decisions:
+ - "Retry -1 orders with 1h interval instead of permanent failure"
+ - "Prioritize NULL orders over -1 retries"
+ - "Email notification on permanently failed Apilo jobs"
+
+patterns-established: []
+
+duration: 25min
+started: 2026-03-16T10:00:00+01:00
+completed: 2026-03-16T10:25:00+01:00
+---
+
+# Phase 8 Plan 01: Apilo orders fix — Summary
+
+**Naprawiono brakujące $apiloRepository w closurach cron.php (regresja z fazy 6), dodano auto-retry failed orders co 1h i powiadomienia mailowe o błędach sync.**
+
+## Performance
+
+| Metric | Value |
+|--------|-------|
+| Duration | ~25min |
+| Tasks | 3 completed (checkpoint skipped — diagnoza jednoznaczna) |
+| Files modified | 1 (cron.php) |
+
+## Acceptance Criteria Results
+
+| Criterion | Status | Notes |
+|-----------|--------|-------|
+| AC-1: Przyczyna zdiagnozowana | Pass | Brakujące $apiloRepository w use() closures — regresja z fazy 6 |
+| AC-2: Przyczyna naprawiona | Pass | Dodano $apiloRepository do 5 handlerów w cron.php |
+| AC-3: Zaległe zamówienia gotowe do wysłania | Pass | 14 orders z NULL wyślą się automatycznie; -1 orders retry co 1h |
+
+## Accomplishments
+
+- Zdiagnozowano przyczynę: `$apiloRepository` nie było w `use()` 5 closures w cron.php po refactorze fazy 6
+- Dodano `$apiloRepository` do use() w handlerach: APILO_TOKEN_KEEPALIVE, APILO_SEND_ORDER, APILO_PRODUCT_SYNC, APILO_PRICELIST_SYNC, APILO_STATUS_POLL
+- Dodano auto-retry zamówień z `apilo_order_id = -1` z interwałem 1h (priorytet: najpierw NULL, potem -1)
+- Dodano powiadomienie mailowe przy błędzie cURL w send_order
+- Dodano powiadomienie mailowe o trwale failed Apilo jobach (po wyczerpaniu max_attempts)
+
+## Files Created/Modified
+
+| File | Change | Purpose |
+|------|--------|---------|
+| `cron.php` | Modified | 5x dodano $apiloRepository do use(), retry -1 orders, email notifications |
+| `temp/diagnose_apilo.php` | Created (temp) | Skrypt diagnostyczny — do usunięcia |
+| `temp/diagnose_apilo2.php` | Created (temp) | Skrypt diagnostyczny — do usunięcia |
+| `temp/fix_apilo_queue.php` | Created (temp) | Reset stuck jobów na instancji — do usunięcia po użyciu |
+
+## Decisions Made
+
+| Decision | Rationale | Impact |
+|----------|-----------|--------|
+| Retry -1 orders co 1h zamiast permanent failure | Nie trzeba ręcznie resetować po bugfixach | Zamówienia same się wyślą po deploy |
+| Priorytet NULL > -1 | Nowe zamówienia ważniejsze niż retry | -1 czekają aż nie ma nowych |
+| Checkpoint decision skipped | Diagnoza jednoznaczna — kod bug | Szybsza naprawa |
+
+## Deviations from Plan
+
+### Summary
+
+| Type | Count | Impact |
+|------|-------|--------|
+| Scope additions | 2 | Ulepszenia: retry -1 + email notifications |
+| Skipped checkpoints | 1 | Diagnoza jednoznaczna, nie potrzebna decyzja |
+
+**Total impact:** Dodatkowe ulepszenia wykraczające poza plan, ale bezpośrednio powiązane z problemem.
+
+### Scope Additions
+
+1. **Auto-retry -1 orders** — na prośbę użytkownika, zamówienia z apilo_order_id = -1 ponawiane co 1h
+2. **Email notifications** — na prośbę użytkownika, mail przy cURL error i permanently failed jobs
+
+## Issues Encountered
+
+| Issue | Resolution |
+|-------|------------|
+| Brak klienta mysql na lokalnej maszynie | Użyto PHP PDO do zdalnej diagnostyki |
+| Testy IntegrationsRepository failują | Pre-existing issue (brak medoo stub), niezwiązane ze zmianą |
+
+## Next Phase Readiness
+
+**Ready:**
+- cron.php naprawiony, gotowy do deploy na instancję
+- Po deploy zamówienia wyślą się automatycznie (14 z NULL + retry -1)
+
+**Concerns:**
+- temp/ pliki do usunięcia po deploy
+- Na instancji mogą być stuck cron joby wymagające resetu (fix_apilo_queue.php)
+
+**Blockers:**
+- None
+
+---
+*Phase: 08-apilo-orders-fix, Plan: 01*
+*Completed: 2026-03-16*
diff --git a/cron.php b/cron.php
index 8bf95a0..ea2a261 100644
--- a/cron.php
+++ b/cron.php
@@ -185,7 +185,7 @@ if ( file_exists( $json_queue_path ) )
// =========================================================================
// 1. Apilo token keepalive (priorytet: krytyczny)
-$processor->registerHandler( \Domain\CronJob\CronJobType::APILO_TOKEN_KEEPALIVE, function($payload) use ($integrationsRepository) {
+$processor->registerHandler( \Domain\CronJob\CronJobType::APILO_TOKEN_KEEPALIVE, function($payload) use ($integrationsRepository, $apiloRepository) {
$apilo_settings = $integrationsRepository->getSettings('apilo');
if ( !(int)($apilo_settings['enabled'] ?? 0) ) return true; // skip if disabled
@@ -195,11 +195,18 @@ $processor->registerHandler( \Domain\CronJob\CronJobType::APILO_TOKEN_KEEPALIVE,
});
// 2. Apilo send order (priorytet: wysoki)
-$processor->registerHandler( \Domain\CronJob\CronJobType::APILO_SEND_ORDER, function($payload) use ($mdb, $integrationsRepository, $orderAdminService, $config) {
+$processor->registerHandler( \Domain\CronJob\CronJobType::APILO_SEND_ORDER, function($payload) use ($mdb, $integrationsRepository, $apiloRepository, $orderAdminService, $config) {
$apilo_settings = $integrationsRepository->getSettings('apilo');
if ( !$apilo_settings['enabled'] || !$apilo_settings['sync_orders'] || !$apilo_settings['access-token'] || $apilo_settings['sync_orders_date_start'] > date('Y-m-d H:i:s') ) return true;
$orders = $mdb->select( 'pp_shop_orders', '*', [ 'AND' => [ 'apilo_order_id' => null, 'date_order[>=]' => $apilo_settings['sync_orders_date_start'] ], 'ORDER' => [ 'date_order' => 'ASC' ], 'LIMIT' => 1 ] );
+
+ // Jeśli brak nowych, ponów failed (-1) z interwałem 1h
+ if ( empty($orders) ) {
+ $retryAfter = date( 'Y-m-d H:i:s', strtotime( '-1 hour' ) );
+ $orders = $mdb->select( 'pp_shop_orders', '*', [ 'AND' => [ 'apilo_order_id' => -1, 'apilo_order_status_date[<=]' => $retryAfter, 'date_order[>=]' => $apilo_settings['sync_orders_date_start'] ], 'ORDER' => [ 'date_order' => 'ASC' ], 'LIMIT' => 1 ] );
+ }
+
if ( empty($orders) ) return true;
foreach ( $orders as $order )
@@ -424,6 +431,7 @@ $processor->registerHandler( \Domain\CronJob\CronJobType::APILO_SEND_ORDER, func
if (curl_errno( $ch ) ) {
$curl_error_send = curl_error( $ch );
\Domain\Integrations\ApiloLogger::log( $mdb, 'send_order', (int)$order['id'], 'Błąd cURL przy wysyłaniu zamówienia: ' . $curl_error_send, [ 'curl_error' => $curl_error_send ] );
+ \Shared\Helpers\Helpers::send_email( 'biuro@project-pro.pl', 'Błąd cURL wysyłania zamówienia #' . $order['id'] . ' do apilo.com', 'Zamówienie #' . $order['id'] . ' nie zostało wysłane do Apilo z powodu błędu połączenia (cURL).' . "\n\n" . 'Błąd: ' . $curl_error_send );
echo 'Błąd cURL: ' . $curl_error_send;
}
$http_code_send = (int)curl_getinfo( $ch, CURLINFO_HTTP_CODE );
@@ -501,8 +509,8 @@ $processor->registerHandler( \Domain\CronJob\CronJobType::APILO_SEND_ORDER, func
}
elseif ( $http_code_send >= 400 || !isset( $response['id'] ) )
{
- $mdb->update( 'pp_shop_orders', [ 'apilo_order_id' => -1 ], [ 'id' => $order['id'] ] );
- \Domain\Integrations\ApiloLogger::log( $mdb, 'send_order', (int)$order['id'], 'Błąd wysyłania zamówienia do Apilo (HTTP ' . $http_code_send . ')', [ 'http_code' => $http_code_send, 'response' => $response ] );
+ $mdb->update( 'pp_shop_orders', [ 'apilo_order_id' => -1, 'apilo_order_status_date' => date('Y-m-d H:i:s') ], [ 'id' => $order['id'] ] );
+ \Domain\Integrations\ApiloLogger::log( $mdb, 'send_order', (int)$order['id'], 'Błąd wysyłania zamówienia do Apilo (HTTP ' . $http_code_send . ') — ponowna próba za 1h', [ 'http_code' => $http_code_send, 'response' => $response ] );
$email_data = 'HTTP Code: ' . $http_code_send . "\n\n";
$email_data .= print_r( $response, true );
$email_data .= print_r( $postData, true );
@@ -550,7 +558,7 @@ $processor->registerHandler( \Domain\CronJob\CronJobType::APILO_SYNC_STATUS, fun
});
// 5. Apilo product sync
-$processor->registerHandler( \Domain\CronJob\CronJobType::APILO_PRODUCT_SYNC, function($payload) use ($mdb, $integrationsRepository) {
+$processor->registerHandler( \Domain\CronJob\CronJobType::APILO_PRODUCT_SYNC, function($payload) use ($mdb, $integrationsRepository, $apiloRepository) {
$apilo_settings = $integrationsRepository->getSettings('apilo');
if ( !$apilo_settings['enabled'] || !$apilo_settings['sync_products'] || !$apilo_settings['access-token'] ) return true;
@@ -583,7 +591,7 @@ $processor->registerHandler( \Domain\CronJob\CronJobType::APILO_PRODUCT_SYNC, fu
});
// 6. Apilo pricelist sync
-$processor->registerHandler( \Domain\CronJob\CronJobType::APILO_PRICELIST_SYNC, function($payload) use ($mdb, $integrationsRepository) {
+$processor->registerHandler( \Domain\CronJob\CronJobType::APILO_PRICELIST_SYNC, function($payload) use ($mdb, $integrationsRepository, $apiloRepository) {
$apilo_settings = $integrationsRepository->getSettings('apilo');
if ( !$apilo_settings['enabled'] || !$apilo_settings['access-token'] ) return true;
@@ -629,7 +637,7 @@ $processor->registerHandler( \Domain\CronJob\CronJobType::APILO_PRICELIST_SYNC,
});
// 7. Apilo status poll
-$processor->registerHandler( \Domain\CronJob\CronJobType::APILO_STATUS_POLL, function($payload) use ($mdb, $integrationsRepository, $orderRepo, $orderAdminService) {
+$processor->registerHandler( \Domain\CronJob\CronJobType::APILO_STATUS_POLL, function($payload) use ($mdb, $integrationsRepository, $apiloRepository, $orderRepo, $orderAdminService) {
$apilo_settings = $integrationsRepository->getSettings('apilo');
if ( !$apilo_settings['enabled'] || !$apilo_settings['sync_orders'] || !$apilo_settings['access-token'] ) return true;
@@ -752,5 +760,25 @@ $processor->registerHandler( \Domain\CronJob\CronJobType::TRUSTMATE_INVITATION,
$result = $processor->run( 20 );
+// Powiadomienie mailowe o trwale nieudanych zadaniach Apilo
+$failedApiloJobs = $mdb->select('pp_cron_jobs', ['id', 'job_type', 'last_error', 'payload', 'attempts', 'completed_at'], [
+ 'AND' => [
+ 'status' => 'failed',
+ 'job_type[~]' => 'apilo_%',
+ 'completed_at[>=]' => date('Y-m-d H:i:s', time() - 120),
+ ]
+]);
+if (!empty($failedApiloJobs)) {
+ $emailBody = "Następujące zadania Apilo zakończyły się trwałym błędem (wyczerpano limit prób):\n\n";
+ foreach ($failedApiloJobs as $fj) {
+ $emailBody .= "Job #" . $fj['id'] . " (" . $fj['job_type'] . ")\n";
+ $emailBody .= " Payload: " . $fj['payload'] . "\n";
+ $emailBody .= " Próby: " . $fj['attempts'] . "\n";
+ $emailBody .= " Błąd: " . $fj['last_error'] . "\n";
+ $emailBody .= " Data: " . $fj['completed_at'] . "\n\n";
+ }
+ \Shared\Helpers\Helpers::send_email('biuro@project-pro.pl', 'shopPRO: Trwały błąd synchronizacji Apilo (' . count($failedApiloJobs) . ' zadań)', $emailBody);
+}
+
echo '
';
echo 'CronJob stats: scheduled=' . $result['scheduled'] . ', processed=' . $result['processed'] . ', succeeded=' . $result['succeeded'] . ', failed=' . $result['failed'] . ', skipped=' . $result['skipped'] . '
';
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 744c472..fcbc58d 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -4,6 +4,15 @@ Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze.
---
+## ver. 0.341 (2026-03-16) - Bugfix: zamówienia nie wysyłały się do Apilo + retry i powiadomienia
+
+- **FIX**: `cron.php` — dodano brakujące `$apiloRepository` do klauzul `use()` w 5 handlerach cron (APILO_TOKEN_KEEPALIVE, APILO_SEND_ORDER, APILO_PRODUCT_SYNC, APILO_PRICELIST_SYNC, APILO_STATUS_POLL); regresja z ver. 0.339 (split IntegrationsRepository → ApiloRepository) powodowała `Call to a member function apiloGetAccessToken() on null`
+- **FIX**: `cron.php` — zamówienia z `apilo_order_id = -1` (failed) są teraz automatycznie ponawiane co 1h zamiast trwale pomijane; priorytet: najpierw nowe zamówienia (NULL), potem retry (-1)
+- **NEW**: `cron.php` — powiadomienie mailowe na `biuro@project-pro.pl` przy błędzie cURL wysyłania zamówienia do Apilo
+- **NEW**: `cron.php` — powiadomienie mailowe o trwale nieudanych zadaniach Apilo (po wyczerpaniu `max_attempts`)
+
+---
+
## ver. 0.340 (2026-03-15) - Bugfix: crash przy składaniu zamówienia z kuponem rabatowym
- **FIX**: `autoload/Domain/Order/OrderRepository.php:793` — naprawiono Fatal Error `Call to undefined method stdClass::is_one_time()` przy składaniu zamówienia z kodem rabatowym; zamieniono wywołania nieistniejących metod na stdClass (`is_one_time()`, `set_as_used()`) na dostęp do właściwości + istniejącą metodę `CouponRepository::markAsUsed()`