Compare commits

..

6 Commits

Author SHA1 Message Date
1d613d8226 fix: build-update.ps1 obsługa SQL-only paczek (0 plików)
Skrypt failował przy Set-Location do temp dir który nie istniał
gdy paczka nie miała plików (tylko migracja SQL).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:00:57 +01:00
174a85a707 ver. 0.316: migracja brakującej kolumny type w pp_shop_products_custom_fields
Kolumna type była używana w kodzie od v0.277 ale nigdy nie miała
migracji ALTER TABLE. Instancje ze starszą bazą dostawały
PDOException: Column not found przy zapisie produktu.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:00:03 +01:00
835386a887 build: update package v0.315
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 17:52:58 +01:00
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
17 changed files with 271 additions and 12 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

@@ -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

@@ -262,6 +262,10 @@ if (Test-Path $tempDir) {
Remove-Item -Recurse -Force $tempDir
}
if (-not (Test-Path $tempDir)) {
New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
}
foreach ($f in $filesToPack) {
$destPath = Join-Path $tempDir $f
$destDir = Split-Path $destPath -Parent
@@ -295,7 +299,16 @@ if (Test-Path $zipPath) {
# Pakuj zawartosc temp dir (bez folderu temp/)
$originalLocation = Get-Location
Set-Location $tempDir
Compress-Archive -Path '*' -DestinationPath "../../$zipPath" -Force
$tempItems = Get-ChildItem -Force
if ($tempItems) {
Compress-Archive -Path '*' -DestinationPath "../../$zipPath" -Force
} else {
# SQL-only update: create minimal ZIP with empty placeholder
$placeholderPath = "_sql_only_update.txt"
Set-Content -Path $placeholderPath -Value "SQL-only update $versionNumber"
Compress-Archive -Path $placeholderPath -DestinationPath "../../$zipPath" -Force
Remove-Item $placeholderPath -Force
}
Set-Location $originalLocation
Write-Ok "Utworzono ZIP: $zipPath"

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,18 @@ Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze.
---
## ver. 0.316 (2026-02-23) - Migracja brakującej kolumny type w custom fields
- **FIX**: Dodanie brakującej kolumny `type` w tabeli `pp_shop_products_custom_fields` — kolumna była używana w kodzie od v0.277 ale nigdy nie miała migracji ALTER TABLE, przez co instancje ze starszą bazą dostawały `PDOException: Column not found: 1054 Unknown column 'type'` przy zapisie produktu
---
## 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

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

View File

@@ -1 +0,0 @@
1. Może warto przepisać zadania cron, na tabelę, tak żeby zadania cron się kolejkowały i wykonywały za podstawie wpisów z bazy danych, takż żeby własnie nie było sytuacji, że zamówienie będzie próbowało zmienić status w apilo, zanim to zamówienie tam trafi. Czy wszystkie takie zadania trafiałyby do kolejki i wykonywałyby się chronologicznie. Ewentualnie jakieś inne podejście możesz zaproponować.

1
migrations/0.316.sql Normal file
View File

@@ -0,0 +1 @@
ALTER TABLE `pp_shop_products_custom_fields` ADD COLUMN `type` VARCHAR(30) NOT NULL DEFAULT '' AFTER `name`;

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": [
]
}

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

Binary file not shown.

View File

@@ -0,0 +1,23 @@
{
"changelog": "FIX - PDOException w listowaniu atrybutow admin (SQLSTATE HY093)",
"version": "0.315",
"files": {
"added": [
],
"deleted": [
],
"modified": [
"autoload/Domain/Attribute/AttributeRepository.php"
]
},
"checksum_zip": "sha256:cfe6eb7dfad896c4ea885c2f9a52c6a389d4e38379a0bf64f5d429910d87e55f",
"sql": [
],
"date": "2026-02-23",
"directories_deleted": [
]
}

View File

@@ -1,11 +1,17 @@
<b>ver. 0.315 - 23.02.2026</b><br />
FIX - PDOException w listowaniu atrybutow admin (SQLSTATE HY093)
<hr>
<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 płatności Apilo (int cast na apilo_order_id PPxxxxxx dawał 0) + logowanie decyzji sync do pp_log
FIX - sync pł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
FIX - krytyczne bugi integracji Apilo: curl_getinfo po curl_close, nieskoÄąââ¬Ĺľczona „™tla wysyÄąââ¬Ĺˇki, ceny 0.00 PLN, walidacja cen
<hr>
<b>ver. 0.311 - 23.02.2026</b><br />
FIX - race condition callback ąâ€šatnoĹ›ci Apilo, persistence filtrÄĹ‚w tabel admin, poprawki cen zamÄĹ‚wieĹ„
FIX - race condition callback ‚„ąâ€š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 = 314;
$current_ver = 316;
for ($i = 1; $i <= $current_ver; $i++)
{