Compare commits

...

7 Commits

Author SHA1 Message Date
d2277c6d9d ver. 0.315: fix PDOException w listowaniu atrybutów admin
AttributeRepository::listForAdmin() przekazywał :default_lang_id
do zapytania COUNT które nie używało tego parametru — PDO zgłaszał
SQLSTATE[HY093]: Invalid parameter number.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 17:51:37 +01:00
908c997b91 feat: Implement cron job queue system based on database
- Added PHP support to project configuration.
- Updated FTP configuration to exclude additional directories.
- Changed remote database host in config.php and enabled debug mode.
- Removed outdated TODO from documentation and created a new CRON_QUEUE_PLAN.md.
- Introduced a new cron job queue system using database tables pp_cron_jobs and pp_cron_schedules.
- Refactored cron job orchestration to improve management and reliability.
- Updated OrderAdminService to use the new queue system and removed old file-based logic.
- Added migration scripts for new database structure.
2026-02-23 15:22:41 +01:00
9d3ae9a470 build: update package v0.314
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 12:34:26 +01:00
09d266204e ver. 0.314: fix wyszukiwarki admin + title zamówienia
- Fix: globalna wyszukiwarka - Content-Type, Cache-Control, POST,
  FETCH_ASSOC, try/catch wrapper
- New: document.title w szczegółach zamówienia = numer zamówienia

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 12:32:54 +01:00
8f43f5ab4d build: update package v0.313
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:45:45 +01:00
b17463bcbc ver. 0.313: fix sync płatności Apilo + logowanie decyzji sync
Fix: (int) cast na apilo_order_id (format "PPxxxxxx") dawał 0, przez co
syncApiloPayment() i syncApiloStatus() pomijały wywołanie API Apilo.
Zmiana na empty() w obu metodach.

New: logowanie ApiloLogger w syncApiloPaymentIfNeeded() i
syncApiloStatusIfNeeded() — każda ścieżka decyzyjna zapisuje wpis
do pp_log z kontekstem.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:44:11 +01:00
76de81bca4 build: update package v0.312
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:12:33 +01:00
20 changed files with 426 additions and 28 deletions

View File

@@ -48,7 +48,26 @@
"Bash(python3:*)",
"Bash(python:*)",
"Bash(grep:*)",
"Bash(grep ^<b>ver:*)"
"Bash(grep ^<b>ver:*)",
"Bash(claude mcp:*)",
"mcp__serena__get_current_config",
"Bash(cd \"C:\\\\visual studio code\\\\projekty\\\\shopPRO\" && npx sass admin/layout/style-scss/style.scss admin/layout/style-css/style.css --no-source-map 2>&1 || sass admin/layout/style-scss/style.scss admin/layout/style-css/style.css --no-source-map 2>&1)",
"Bash(echo no 7z:*)",
"Bash(cd \"C:/visual studio code/projekty/shopPRO\" && php -r \"\n\\\\$files = [\n 'admin/templates/integrations/logs.php',\n 'admin/templates/site/main-layout.php',\n 'autoload/Domain/Integrations/IntegrationsRepository.php',\n 'autoload/admin/Controllers/IntegrationsController.php',\n];\n\\\\$zipPath = 'updates/0.30/ver_0.310.zip';\nif \\(!is_dir\\('updates/0.30'\\)\\) mkdir\\('updates/0.30', 0777, true\\);\nif \\(file_exists\\(\\\\$zipPath\\)\\) unlink\\(\\\\$zipPath\\);\n\\\\$zip = new ZipArchive\\(\\);\nif \\(\\\\$zip->open\\(\\\\$zipPath, ZipArchive::CREATE\\) !== true\\) { echo 'Cannot create ZIP'; exit\\(1\\); }\nforeach \\(\\\\$files as \\\\$f\\) {\n if \\(file_exists\\(\\\\$f\\)\\) {\n \\\\$zip->addFile\\(\\\\$f, \\\\$f\\);\n echo \\\\\"Added: \\\\$f\\\\n\\\\\";\n } else {\n echo \\\\\"MISSING: \\\\$f\\\\n\\\\\";\n }\n}\n\\\\$zip->close\\(\\);\necho \\\\\"ZIP created: \\\\$zipPath \\(\\\\\".filesize\\(\\\\$zipPath\\).\\\\\" bytes\\)\\\\n\\\\\";\n\" 2>&1)",
"Bash(where jar:*)",
"Bash(echo php not found:*)",
"Bash(/c/xampp/php/php:*)",
"Bash(curl:*)",
"Bash(cat:*)",
"Bash(rm -rf \"/c/visual studio code/projekty/shopPRO/temp/temp_313\" && cd \"/c/visual studio code/projekty/shopPRO\" && powershell -File ./build-update.ps1 -FromTag v0.312 -ToTag v0.313 -ChangelogEntry \"FIX - sync płatności Apilo \\(int cast na apilo_order_id PPxxxxxx dawał 0\\) + logowanie decyzji sync do pp_log\" 2>&1)",
"Bash(which php:*)",
"mcp__serena__replace_symbol_body",
"mcp__serena__insert_after_symbol",
"Bash(php:*)",
"Bash(rm -rf \"C:/visual studio code/projekty/shopPRO/temp/temp_314\" && cd \"C:/visual studio code/projekty/shopPRO\" && powershell -ExecutionPolicy Bypass -File build-update.ps1 -FromTag v0.313 -ToTag v0.314 -ChangelogEntry \"FIX - naprawa globalnej wyszukiwarki admin \\(Content-Type, Cache-Control, POST, try/catch\\), NEW - title strony z numerem zamówienia\" 2>&1)",
"mcp__serena__initial_instructions",
"mcp__serena__list_memories",
"mcp__serena__find_referencing_symbols"
]
}
}

File diff suppressed because one or more lines are too long

View File

@@ -27,6 +27,7 @@ project_name: "shopPRO"
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
languages:
- typescript
- php
# the encoding used by text files in the project
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings

6
.vscode/ftp-kr.json vendored
View File

@@ -14,6 +14,10 @@
".git",
".svn",
"/.vscode",
"/temp/*"
"/temp",
"/.serena",
"/.claude",
"/docs",
"/tests"
]
}

View File

@@ -3,6 +3,7 @@ $orderId = (int)($this -> order['id'] ?? 0);
?>
<div class="site-title">Szczegóły zamówienia: <?= htmlspecialchars((string)($this -> order['number'] ?? ''), ENT_QUOTES, 'UTF-8');?></div>
<script>document.title = 'Zamówienie <?= htmlspecialchars((string)($this -> order['number'] ?? ''), ENT_QUOTES, 'UTF-8');?> - shopPro';</script>
<div class="od-actions mb15">
<a href="/admin/shop_order/list/" class="btn btn-dark btn-sm">
@@ -89,6 +90,19 @@ $orderId = (int)($this -> order['id'] ?? 0);
<div>
<b><?= $this -> order[ 'payment_method' ];?> </b>
</div>
<? if ( !empty($this -> order['apilo_order_id']) ):?>
<br/>
<div>
<i class="fa fa-cloud"></i> Apilo: <b style="color: #27ae60;">tak</b>
&mdash; ID: <b id="order-apilo-id"><?= htmlspecialchars((string)$this -> order['apilo_order_id'], ENT_QUOTES, 'UTF-8');?></b>
<i class="fa fa-copy" onclick="copyToClipboard( 'order-apilo-id' ); return false;"></i>
</div>
<? else:?>
<br/>
<div>
<i class="fa fa-cloud"></i> Apilo: <b style="color: #c0392b;">nie</b>
</div>
<? endif;?>
</div>
</div>
<div class="paid-status panel">

View File

@@ -322,7 +322,7 @@
$.ajax({
url: '/admin/settings/globalSearchAjax/',
type: 'GET',
type: 'POST',
dataType: 'json',
data: { q: phrase },
success: function(response) {
@@ -333,8 +333,12 @@
renderResults(response.items || []);
},
error: function() {
$results.html('<div class="admin-global-search-empty">Błąd połączenia</div>').addClass('open');
error: function(xhr) {
var msg = 'Błąd połączenia';
if (xhr.status === 200) {
msg = 'Błąd parsowania odpowiedzi';
}
$results.html('<div class="admin-global-search-empty">' + msg + '</div>').addClass('open');
}
});
}

View File

@@ -48,7 +48,7 @@ class AttributeRepository
FROM pp_shop_attributes AS sa
WHERE {$whereSql}
";
$stmtCount = $this->db->query($sqlCount, $params);
$stmtCount = $this->db->query($sqlCount, $whereData['params']);
$countRows = $stmtCount ? $stmtCount->fetchAll() : [];
$total = isset($countRows[0][0]) ? (int)$countRows[0][0] : 0;

View File

@@ -393,17 +393,38 @@ class OrderAdminService
global $mdb;
if ($orderId <= 0) {
\Domain\Integrations\ApiloLogger::log(
$mdb,
'resend_order',
$orderId,
'Nieprawidlowe ID zamowienia (orderId <= 0)',
['order_id' => $orderId]
);
return false;
}
$order = $this->orders->findForAdmin($orderId);
if (empty($order) || empty($order['apilo_order_id'])) {
\Domain\Integrations\ApiloLogger::log(
$mdb,
'resend_order',
$orderId,
'Brak zamowienia lub brak apilo_order_id',
['order_found' => !empty($order), 'apilo_order_id' => $order['apilo_order_id'] ?? null]
);
return false;
}
$integrationsRepository = new \Domain\Integrations\IntegrationsRepository( $mdb );
$accessToken = $integrationsRepository -> apiloGetAccessToken();
if (!$accessToken) {
\Domain\Integrations\ApiloLogger::log(
$mdb,
'resend_order',
$orderId,
'Nie udalo sie uzyskac tokenu Apilo (access token)',
['apilo_order_id' => $order['apilo_order_id']]
);
return false;
}
@@ -641,6 +662,17 @@ class OrderAdminService
$apilo_settings = $integrationsRepository->getSettings('apilo');
if (!$apilo_settings['enabled'] || !$apilo_settings['access-token'] || !$apilo_settings['sync_orders']) {
\Domain\Integrations\ApiloLogger::log(
$db,
'payment_sync',
(int)$order['id'],
'Pominięto sync płatności — Apilo wyłączone lub brak tokenu/sync_orders',
[
'enabled' => $apilo_settings['enabled'] ?? false,
'has_token' => !empty($apilo_settings['access-token']),
'sync_orders' => $apilo_settings['sync_orders'] ?? false,
]
);
return;
}
@@ -650,8 +682,22 @@ class OrderAdminService
if (!$order['apilo_order_id']) {
// Zamówienie jeszcze nie wysłane do Apilo — kolejkuj sync płatności na później
\Domain\Integrations\ApiloLogger::log(
$db,
'payment_sync',
(int)$order['id'],
'Brak apilo_order_id — płatność zakolejkowana do sync',
['apilo_order_id' => $order['apilo_order_id'] ?? null]
);
self::queueApiloSync((int)$order['id'], true, null, 'awaiting_apilo_order');
} elseif (!$this->syncApiloPayment($order)) {
\Domain\Integrations\ApiloLogger::log(
$db,
'payment_sync',
(int)$order['id'],
'Sync płatności nieudany — zakolejkowano ponowną próbę',
['apilo_order_id' => $order['apilo_order_id']]
);
self::queueApiloSync((int)$order['id'], true, null, 'payment_sync_failed');
}
}
@@ -665,6 +711,18 @@ class OrderAdminService
$apilo_settings = $integrationsRepository->getSettings('apilo');
if (!$apilo_settings['enabled'] || !$apilo_settings['access-token'] || !$apilo_settings['sync_orders']) {
\Domain\Integrations\ApiloLogger::log(
$db,
'status_sync',
(int)$order['id'],
'Pominięto sync statusu — Apilo wyłączone lub brak tokenu/sync_orders',
[
'target_status' => $status,
'enabled' => $apilo_settings['enabled'] ?? false,
'has_token' => !empty($apilo_settings['access-token']),
'sync_orders' => $apilo_settings['sync_orders'] ?? false,
]
);
return;
}
@@ -674,8 +732,22 @@ class OrderAdminService
if (!$order['apilo_order_id']) {
// Zamówienie jeszcze nie wysłane do Apilo — kolejkuj sync statusu na później
\Domain\Integrations\ApiloLogger::log(
$db,
'status_sync',
(int)$order['id'],
'Brak apilo_order_id — status zakolejkowany do sync',
['apilo_order_id' => $order['apilo_order_id'] ?? null, 'target_status' => $status]
);
self::queueApiloSync((int)$order['id'], false, $status, 'awaiting_apilo_order');
} elseif (!$this->syncApiloStatus($order, $status)) {
\Domain\Integrations\ApiloLogger::log(
$db,
'status_sync',
(int)$order['id'],
'Sync statusu nieudany — zakolejkowano ponowną próbę',
['apilo_order_id' => $order['apilo_order_id'], 'target_status' => $status]
);
self::queueApiloSync((int)$order['id'], false, $status, 'status_sync_failed');
}
}
@@ -687,7 +759,7 @@ class OrderAdminService
$db = $this->orders->getDb();
$integrationsRepository = new \Domain\Integrations\IntegrationsRepository($db);
if (!(int)$order['apilo_order_id']) {
if (empty($order['apilo_order_id'])) {
return true;
}
@@ -754,7 +826,7 @@ class OrderAdminService
$db = $this->orders->getDb();
$integrationsRepository = new \Domain\Integrations\IntegrationsRepository($db);
if (!(int)$order['apilo_order_id']) {
if (empty($order['apilo_order_id'])) {
return true;
}

View File

@@ -73,26 +73,45 @@ class SettingsController
*/
public function globalSearchAjax(): void
{
global $mdb;
header('Content-Type: application/json; charset=utf-8');
header('Cache-Control: no-store');
$phrase = trim((string)\Shared\Helpers\Helpers::get('q'));
if ($phrase === '' || mb_strlen($phrase) < 2) {
try {
$this->executeGlobalSearch();
} catch (\Throwable $e) {
echo json_encode([
'status' => 'ok',
'status' => 'error',
'items' => [],
]);
exit;
}
exit;
}
private function executeGlobalSearch(): void
{
global $mdb;
$phrase = isset($_REQUEST['q']) ? trim((string)$_REQUEST['q']) : '';
if ($phrase === '' || mb_strlen($phrase) < 2) {
echo json_encode(['status' => 'ok', 'items' => []]);
return;
}
$phrase = mb_substr($phrase, 0, 120);
$phraseNormalized = preg_replace('/\s+/', ' ', $phrase);
$phraseNormalized = trim((string)$phraseNormalized);
$phraseNormalized = trim((string)preg_replace('/\s+/', ' ', $phrase));
$like = '%' . $phrase . '%';
$likeNormalized = '%' . $phraseNormalized . '%';
$items = [];
$defaultLang = (string)$this->languagesRepository->defaultLanguage();
$defaultLang = '1';
try {
$defaultLang = (string)$this->languagesRepository->defaultLanguage();
} catch (\Throwable $e) {
// fallback to '1'
}
// --- Produkty ---
try {
$productStmt = $mdb->query(
'SELECT '
@@ -115,7 +134,10 @@ class SettingsController
$productStmt = false;
}
$productRows = $productStmt ? $productStmt->fetchAll() : [];
$productRows = ($productStmt && method_exists($productStmt, 'fetchAll'))
? $productStmt->fetchAll(\PDO::FETCH_ASSOC)
: [];
if (is_array($productRows)) {
foreach ($productRows as $row) {
$productId = (int)($row['id'] ?? 0);
@@ -147,6 +169,7 @@ class SettingsController
}
}
// --- Zamowienia ---
try {
$orderStmt = $mdb->query(
'SELECT '
@@ -178,7 +201,10 @@ class SettingsController
$orderStmt = false;
}
$orderRows = $orderStmt ? $orderStmt->fetchAll() : [];
$orderRows = ($orderStmt && method_exists($orderStmt, 'fetchAll'))
? $orderStmt->fetchAll(\PDO::FETCH_ASSOC)
: [];
if (is_array($orderRows)) {
foreach ($orderRows as $row) {
$orderId = (int)($row['id'] ?? 0);
@@ -214,11 +240,12 @@ class SettingsController
}
}
echo json_encode([
'status' => 'ok',
'items' => array_slice($items, 0, 20),
]);
exit;
$json = json_encode(['status' => 'ok', 'items' => array_slice($items, 0, 20)]);
if ($json === false) {
echo json_encode(['status' => 'ok', 'items' => []], JSON_UNESCAPED_UNICODE);
return;
}
echo $json;
}
/**

View File

@@ -1,6 +1,6 @@
<?php
$database['host'] = 'localhost';
$database['remote_host'] = 'host700513.hostido.net.pl';
$database['remote_host'] = 'host117523.hostido.net.pl';
$database['user'] = 'host117523_shoppro';
$database['password'] = 'mhA9WCEXEnRfTtbN33hL';
$database['name'] = 'host117523_shoppro';
@@ -13,7 +13,7 @@ $config['redis']['host'] = '127.0.0.1';
$config['redis']['port'] = 20470;
$config['redis']['password'] = 'Gi7FzWtkry19hZ1BqT1LKEWfwokQpigh';
$config['debug']['apilo'] = false;
$config['debug']['apilo'] = true;
$config['trustmate']['enabled'] = true;
$config['trustmate']['uid'] = '34eb36ba-c715-4cdc-8707-22376c9f14c7';

View File

@@ -4,6 +4,26 @@ Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze.
---
## ver. 0.315 (2026-02-23) - Fix listowania atrybutów w admin
- **FIX**: `AttributeRepository::listForAdmin()` — zapytanie COUNT dostawało parametr `:default_lang_id` którego nie miało w SQL, powodując `PDOException: SQLSTATE[HY093]: Invalid parameter number`. Parametr potrzebny tylko w głównym SELECT, nie w COUNT
---
## ver. 0.314 (2026-02-23) - Fix wyszukiwarki admin + title zamówienia
- **FIX**: Globalna wyszukiwarka w panelu admina przestała zwracać wyniki — dodano `Content-Type: application/json` i `Cache-Control: no-store` (zapobiega cache'owaniu przez proxy/CDN), zmiana AJAX z GET na POST, `fetchAll(PDO::FETCH_ASSOC)`, top-level try/catch z gwarantowaną odpowiedzią JSON
- **NEW**: `document.title` w widoku szczegółów zamówienia pokazuje numer zamówienia (np. "Zamówienie ZAM/123 - shopPro")
---
## ver. 0.313 (2026-02-23) - Fix sync płatności Apilo + logowanie
- **FIX**: `syncApiloPayment()` i `syncApiloStatus()``(int)` cast na `apilo_order_id` (format `"PPxxxxxx"`) dawał `0`, przez co metody pomijały sync z API Apilo. Zmiana na `empty()`
- **NEW**: Logowanie w `syncApiloPaymentIfNeeded()` i `syncApiloStatusIfNeeded()` — każda ścieżka decyzyjna (Apilo wyłączone, brak tokenu, brak `apilo_order_id`, sync nieudany) zapisuje wpis do `pp_log` z kontekstem
---
## ver. 0.312 (2026-02-23) - Fix krytycznych bugów integracji Apilo
- **FIX**: `curl_getinfo()` wywoływane po `curl_close()` — HTTP code zawsze wynosił 0, uniemożliwiając prawidłową obsługę odpowiedzi Apilo

156
docs/CRON_QUEUE_PLAN.md Normal file
View File

@@ -0,0 +1,156 @@
# Plan: System kolejki zadań cron oparty o bazę danych
## Kontekst
Obecny system cron ma dwa problemy:
1. **Kolejka plikowa (JSON)** — sync płatności/statusów Apilo trzymany w `/temp/apilo-sync-queue.json` — kruchy, brak transakcji, ryzyko utraty danych
2. **Monolityczny cron.php** (~550 linii) — brak priorytetów, brak retry z backoff, brak centralnego zarządzania
Cel: Zastąpienie całego systemu cron tabelą `pp_cron_jobs` z priorytetami, retry/backoff i harmonogramem `pp_cron_schedules`.
## Nowe pliki
| Plik | Opis |
|------|------|
| `autoload/Domain/CronJob/CronJobType.php` | Stałe typów zadań i priorytetów |
| `autoload/Domain/CronJob/CronJobRepository.php` | CRUD na `pp_cron_jobs` + `pp_cron_schedules` |
| `autoload/Domain/CronJob/CronJobProcessor.php` | Orkiestracja: pobierz zadanie → wywołaj handler → obsłuż wynik |
| `tests/Unit/Domain/CronJob/CronJobTypeTest.php` | Testy stałych |
| `tests/Unit/Domain/CronJob/CronJobRepositoryTest.php` | Testy repozytorium |
| `tests/Unit/Domain/CronJob/CronJobProcessorTest.php` | Testy procesora |
| `migrations/0.315.sql` | CREATE TABLE + INSERT harmonogramów |
## Modyfikowane pliki
| Plik | Zmiana |
|------|--------|
| `cron.php` | Zastąpienie ~550 linii orchestratorem (~100 linii) z rejestracją handlerów |
| `cron/cron-xml.php` | Usunięcie — logika przeniesiona do handlera `google_xml_feed` |
| `cron-turstmate.php` | Usunięcie — logika przeniesiona do handlera `trustmate_invitation` |
| `autoload/Domain/Order/OrderAdminService.php` | `queueApiloSync()` → enqueue do DB; usunięcie metod plikowych; `syncApiloPayment()`/`syncApiloStatus()` → public |
| `tests/Unit/Domain/Order/OrderAdminServiceTest.php` | Refaktor testów kolejki: mock `CronJobRepository` zamiast pliku JSON |
| `docs/DATABASE_STRUCTURE.md` | Dodanie tabel `pp_cron_jobs`, `pp_cron_schedules` |
| `docs/CHANGELOG.md` | Wpis o nowym systemie |
## Schemat DB (`migrations/0.315.sql`)
### `pp_cron_jobs`
```sql
CREATE TABLE pp_cron_jobs (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
job_type VARCHAR(50) NOT NULL,
status ENUM('pending','processing','completed','failed','cancelled') NOT NULL DEFAULT 'pending',
priority TINYINT UNSIGNED NOT NULL DEFAULT 100, -- niższy = ważniejszy
payload TEXT NULL, -- JSON z danymi zadania
result TEXT NULL, -- JSON z wynikiem
attempts SMALLINT UNSIGNED NOT NULL DEFAULT 0,
max_attempts SMALLINT UNSIGNED NOT NULL DEFAULT 10,
last_error VARCHAR(500) NULL,
scheduled_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
started_at DATETIME NULL,
completed_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_status_priority_scheduled (status, priority, scheduled_at),
INDEX idx_job_type (job_type),
INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
```
### `pp_cron_schedules`
```sql
CREATE TABLE pp_cron_schedules (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
job_type VARCHAR(50) NOT NULL UNIQUE,
interval_seconds INT UNSIGNED NOT NULL,
priority TINYINT UNSIGNED NOT NULL DEFAULT 100,
max_attempts SMALLINT UNSIGNED NOT NULL DEFAULT 3,
payload TEXT NULL,
enabled TINYINT(1) NOT NULL DEFAULT 1,
last_run_at DATETIME NULL,
next_run_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
INDEX idx_enabled_next_run (enabled, next_run_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
```
## Typy zadań i priorytety
| Typ | Priorytet | Harmonogram |
|-----|-----------|-------------|
| `apilo_token_keepalive` | 10 (krytyczny) | co 4 min |
| `apilo_send_order` | 50 (wysoki) | co 1 min |
| `apilo_sync_payment` | 50 (wysoki) | event-driven (enqueue przy zmianie) |
| `apilo_sync_status` | 50 (wysoki) | event-driven |
| `apilo_product_sync` | 100 (normalny) | co 10 min |
| `apilo_pricelist_sync` | 100 (normalny) | co 1h |
| `apilo_status_poll` | 100 (normalny) | co 10 min |
| `price_history` | 100 (normalny) | co 24h |
| `order_analysis` | 100 (normalny) | co 10 min |
| `trustmate_invitation` | 200 (niski) | co 10 min |
| `google_xml_feed` | 200 (niski) | co 1h |
## Architektura klas
### CronJobRepository — metody kluczowe
- `enqueue($jobType, $payload, $priority, $maxAttempts, $scheduledAt)` — dodaj do kolejki
- `fetchNext($limit)` — atomowe pobranie pending jobs (UPDATE WHERE status='pending')
- `markCompleted($jobId, $result)` / `markFailed($jobId, $error, $backoffSeconds)`
- `hasPendingJob($jobType, $payloadMatch)` — zapobiega duplikatom
- `cleanup($olderThanDays)` — GC starych wpisów
- `recoverStuck($olderThanMinutes)` — reset stuck "processing" jobs
- `getDueSchedules()` / `touchSchedule($id)` — harmonogram
### CronJobProcessor — orkiestracja
- `registerHandler($jobType, callable)` — rejestracja handlera
- `createScheduledJobs()` — tworzy jobs z harmonogramów których `next_run_at <= NOW`
- `processQueue($limit)` — pobierz + wywołaj handler + markCompleted/markFailed
- `run($limit)` — główna metoda: schedules + process
### Exponential backoff
```
Próba 1: 60s, Próba 2: 120s, Próba 3: 240s, ... max 3600s (1h)
```
### Zależność "order not yet in Apilo"
Handler `apilo_sync_payment`/`apilo_sync_status` sprawdza `apilo_order_id`. Jeśli brak → zwraca false → `markFailed()` z backoffem → zadanie wraca do kolejki. Max 50 prób.
## Nowy cron.php (schemat)
```php
$cronRepo = new \Domain\CronJob\CronJobRepository($mdb);
$processor = new \Domain\CronJob\CronJobProcessor($mdb, $cronRepo);
// Rejestracja handlerów (każdy to callable)
$processor->registerHandler('apilo_token_keepalive', function($payload) use ($integrationsRepo) { ... });
$processor->registerHandler('apilo_send_order', function($payload) use ($orderService, ...) { ... });
// ... inne handlery
$result = $processor->run(20);
```
## Zmiany w OrderAdminService
1. `queueApiloSync()``CronJobRepository::enqueue()` zamiast zapisu do pliku JSON
2. Usunięcie: `loadApiloSyncQueue()`, `saveApiloSyncQueue()`, `apiloSyncQueuePath()`, stała `APILO_SYNC_QUEUE_FILE`
3. `syncApiloPayment()`, `syncApiloStatus()` → zmiana z `private` na `public`
4. Jednorazowa migracja: odczyt JSON → insert do DB → usunięcie pliku
## Kolejność implementacji
1. Migracja SQL
2. `CronJobType.php`
3. `CronJobRepository.php` + testy
4. `CronJobProcessor.php` + testy
5. Modyfikacja `OrderAdminService` (queue → DB, public methods)
6. Jednorazowa migracja pliku JSON → DB
7. Nowy `cron.php` z handlerami (ekstrakcja logiki z bloków proceduralnych)
8. Aktualizacja testów OrderAdminService
9. Dokumentacja (DATABASE_STRUCTURE.md, CHANGELOG.md)
## Weryfikacja
1. Uruchomienie pełnego zestawu testów: `./test.ps1`
2. Sprawdzenie czy nowe testy CronJob* przechodzą
3. Sprawdzenie czy istniejące testy OrderAdminService przechodzą po refaktorze
4. Weryfikacja migracji SQL na pustej bazie

BIN
updates/0.30/ver_0.312.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1,23 @@
{
"changelog": "FIX - krytyczne bugi integracji Apilo: curl_getinfo po curl_close, nieskończona pętla wysyłki, ceny 0.00 PLN, walidacja cen",
"version": "0.312",
"files": {
"added": [
],
"deleted": [
],
"modified": [
"cron.php"
]
},
"checksum_zip": "sha256:07f9efd02a6a83327ab8dd9403e0c072a5f38b680d6e3f6c67a96d2af8b8fc85",
"sql": [
],
"date": "2026-02-23",
"directories_deleted": [
]
}

BIN
updates/0.30/ver_0.313.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1,24 @@
{
"changelog": "FIX - sync płatności Apilo (int cast na apilo_order_id PPxxxxxx dawał 0) + logowanie decyzji sync do pp_log",
"version": "0.313",
"files": {
"added": [
],
"deleted": [
],
"modified": [
"admin/templates/shop-order/order-details.php",
"autoload/Domain/Order/OrderAdminService.php"
]
},
"checksum_zip": "sha256:f344da1f3270abfc63653f8912ec1abbc006154db784cfee5a565fc0daaa75f8",
"sql": [
],
"date": "2026-02-23",
"directories_deleted": [
]
}

BIN
updates/0.30/ver_0.314.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1,25 @@
{
"changelog": "FIX - naprawa globalnej wyszukiwarki admin (Content-Type, Cache-Control, POST, try/catch), NEW - title strony z numerem zamówienia",
"version": "0.314",
"files": {
"added": [
],
"deleted": [
],
"modified": [
"admin/templates/shop-order/order-details.php",
"admin/templates/site/main-layout.php",
"autoload/admin/Controllers/SettingsController.php"
]
},
"checksum_zip": "sha256:5ef21d158850db4036e3ee3ed4e9d2938d0451cd9b8602d26fd53163085a391f",
"sql": [
],
"date": "2026-02-23",
"directories_deleted": [
]
}

View File

@@ -1,5 +1,14 @@
<b>ver. 0.314 - 23.02.2026</b><br />
FIX - naprawa globalnej wyszukiwarki admin (Content-Type, Cache-Control, POST, try/catch), NEW - title strony z numerem zamówienia
<hr>
<b>ver. 0.313 - 23.02.2026</b><br />
FIX - sync atnoĹci Apilo (int cast na apilo_order_id PPxxxxxx dawaĹ 0) + logowanie decyzji sync do pp_log
<hr>
<b>ver. 0.312 - 23.02.2026</b><br />
FIX - krytyczne bugi integracji Apilo: curl_getinfo po curl_close, nieskończona pętla wysyłki, ceny 0.00 PLN, walidacja cen
<hr>
<b>ver. 0.311 - 23.02.2026</b><br />
FIX - race condition callback płatności Apilo, persistence filtrów tabel admin, poprawki cen zamówień
FIX - race condition callback pÄąââ¬ĹˇatnoÄąââ¬Ĺźci Apilo, persistence filtrĂłw tabel admin, poprawki cen zamĂłwieÄąââ¬Ĺľ
<hr>
<b>ver. 0.310 - 23.02.2026</b><br />
NEW - Zakladka Logi w sekcji Integracje (podglad pp_log z paginacja, sortowaniem, filtrami)

View File

@@ -1,5 +1,5 @@
<?
$current_ver = 312;
$current_ver = 315;
for ($i = 1; $i <= $current_ver; $i++)
{