diff --git a/CLAUDE.md b/CLAUDE.md index 517074b..9b4d500 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,7 +36,7 @@ composer test PHPUnit 9.6 via `phpunit.phar`. Bootstrap: `tests/bootstrap.php`. Config: `phpunit.xml`. -Current suite: **750 tests, 2114 assertions**. +Current suite: **758 tests, 2135 assertions**. ### Creating Updates See `docs/UPDATE_INSTRUCTIONS.md` for the full procedure. Updates are ZIP packages in `updates/0.XX/`. Never include `*.md` files, `updates/changelog.php`, or root `.htaccess` in update ZIPs. diff --git a/admin/templates/integrations/logs.php b/admin/templates/integrations/logs.php new file mode 100644 index 0000000..29889cf --- /dev/null +++ b/admin/templates/integrations/logs.php @@ -0,0 +1,19 @@ + $this->viewModel]); ?> + +
+ + Wyczysc wszystkie logi + +
+ + diff --git a/admin/templates/site/main-layout.php b/admin/templates/site/main-layout.php index eb30b64..17c2f5e 100644 --- a/admin/templates/site/main-layout.php +++ b/admin/templates/site/main-layout.php @@ -153,6 +153,11 @@ shopPRO +
  • + + Logi + +
  • diff --git a/autoload/Domain/Integrations/IntegrationsRepository.php b/autoload/Domain/Integrations/IntegrationsRepository.php index 8074358..e23a98e 100644 --- a/autoload/Domain/Integrations/IntegrationsRepository.php +++ b/autoload/Domain/Integrations/IntegrationsRepository.php @@ -56,6 +56,63 @@ class IntegrationsRepository return true; } + // ── Logs ──────────────────────────────────────────────────── + + /** + * Pobiera logi z tabeli pp_log z paginacją, sortowaniem i filtrowaniem. + * + * @return array{items:array, total:int} + */ + public function getLogs( array $filters, string $sortColumn, string $sortDir, int $page, int $perPage ): array + { + $where = []; + + if ( !empty( $filters['log_action'] ) ) { + $where['action[~]'] = '%' . $filters['log_action'] . '%'; + } + + if ( !empty( $filters['message'] ) ) { + $where['message[~]'] = '%' . $filters['message'] . '%'; + } + + if ( !empty( $filters['order_id'] ) ) { + $where['order_id'] = (int) $filters['order_id']; + } + + $total = $this->db->count( 'pp_log', $where ); + + $where['ORDER'] = [ $sortColumn => $sortDir ]; + $where['LIMIT'] = [ ( $page - 1 ) * $perPage, $perPage ]; + + $items = $this->db->select( 'pp_log', '*', $where ); + if ( !is_array( $items ) ) { + $items = []; + } + + return [ + 'items' => $items, + 'total' => (int) $total, + ]; + } + + /** + * Usuwa wpis logu po ID. + */ + public function deleteLog( int $id ): bool + { + $this->db->delete( 'pp_log', [ 'id' => $id ] ); + return true; + } + + /** + * Czyści wszystkie logi z tabeli pp_log. + */ + public function clearLogs(): bool + { + $this->db->delete( 'pp_log', [] ); + return true; + } + // ── Product linking (Apilo) ───────────────────────────────── public function linkProduct( int $productId, $externalId, $externalName ): bool diff --git a/autoload/admin/Controllers/IntegrationsController.php b/autoload/admin/Controllers/IntegrationsController.php index 29d6888..e182a90 100644 --- a/autoload/admin/Controllers/IntegrationsController.php +++ b/autoload/admin/Controllers/IntegrationsController.php @@ -2,6 +2,7 @@ namespace admin\Controllers; use Domain\Integrations\IntegrationsRepository; +use admin\ViewModels\Common\PaginatedTableViewModel; class IntegrationsController { @@ -12,6 +13,114 @@ class IntegrationsController $this->repository = $repository; } + public function logs(): string + { + $sortableColumns = ['id', 'action', 'order_id', 'message', 'date']; + + $filterDefinitions = [ + [ + 'key' => 'log_action', + 'label' => 'Akcja', + 'type' => 'text', + ], + [ + 'key' => 'message', + 'label' => 'Wiadomosc', + 'type' => 'text', + ], + [ + 'key' => 'order_id', + 'label' => 'ID zamowienia', + 'type' => 'text', + ], + ]; + + $listRequest = \admin\Support\TableListRequestFactory::fromRequest( + $filterDefinitions, + $sortableColumns, + 'id' + ); + + $result = $this->repository->getLogs( + $listRequest['filters'], + $listRequest['sortColumn'], + $listRequest['sortDir'], + $listRequest['page'], + $listRequest['perPage'] + ); + + $rows = []; + $lp = ($listRequest['page'] - 1) * $listRequest['perPage'] + 1; + + foreach ( $result['items'] as $item ) { + $id = (int)($item['id'] ?? 0); + $context = trim( (string)($item['context'] ?? '') ); + $contextHtml = ''; + if ( $context !== '' ) { + $contextHtml = '' + . ''; + } + + $rows[] = [ + 'lp' => $lp++ . '.', + 'action' => htmlspecialchars( (string)($item['action'] ?? ''), ENT_QUOTES, 'UTF-8' ), + 'order_id' => $item['order_id'] ? (int)$item['order_id'] : '-', + 'message' => htmlspecialchars( (string)($item['message'] ?? ''), ENT_QUOTES, 'UTF-8' ), + 'context' => $contextHtml, + 'date' => !empty( $item['date'] ) ? date( 'Y-m-d H:i:s', strtotime( (string)$item['date'] ) ) : '-', + ]; + } + + $total = (int)$result['total']; + $totalPages = max( 1, (int)ceil( $total / $listRequest['perPage'] ) ); + + $viewModel = new PaginatedTableViewModel( + [ + ['key' => 'lp', 'label' => 'Lp.', 'class' => 'text-center', 'sortable' => false], + ['key' => 'date', 'sort_key' => 'date', 'label' => 'Data', 'class' => 'text-center', 'sortable' => true], + ['key' => 'action', 'sort_key' => 'action', 'label' => 'Akcja', 'sortable' => true], + ['key' => 'order_id', 'sort_key' => 'order_id', 'label' => 'Zamowienie', 'class' => 'text-center', 'sortable' => true], + ['key' => 'message', 'sort_key' => 'message', 'label' => 'Wiadomosc', 'sortable' => true], + ['key' => 'context', 'label' => 'Kontekst', 'sortable' => false, 'raw' => true], + ], + $rows, + $listRequest['viewFilters'], + [ + 'column' => $listRequest['sortColumn'], + 'dir' => $listRequest['sortDir'], + ], + [ + 'page' => $listRequest['page'], + 'per_page' => $listRequest['perPage'], + 'total' => $total, + 'total_pages' => $totalPages, + ], + array_merge( $listRequest['queryFilters'], [ + 'sort' => $listRequest['sortColumn'], + 'dir' => $listRequest['sortDir'], + 'per_page' => $listRequest['perPage'], + ] ), + $listRequest['perPageOptions'], + $sortableColumns, + '/admin/integrations/logs/', + 'Brak wpisow w logach.' + ); + + return \Shared\Tpl\Tpl::view( 'integrations/logs', [ + 'viewModel' => $viewModel, + ] ); + } + + public function logs_clear(): void + { + $this->repository->clearLogs(); + \Shared\Helpers\Helpers::alert( 'Logi zostaly wyczyszczone.' ); + header( 'Location: /admin/integrations/logs/' ); + exit; + } + public function apilo_settings(): string { return \Shared\Tpl\Tpl::view( 'integrations/apilo-settings', [ diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 8c2b0fb..9791af4 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.310 (2026-02-23) - Logi integracji w panelu admin + +- **NEW**: Zakładka "Logi" w sekcji Integracje — podgląd tabeli `pp_log` z paginacją, sortowaniem, filtrami (akcja, wiadomość, ID zamówienia) i rozwijalnym kontekstem JSON +- **NEW**: `IntegrationsRepository::getLogs()`, `deleteLog()`, `clearLogs()` — metody do obsługi logów +- **NEW**: `IntegrationsController::logs()`, `logs_clear()` — akcje kontrolera +- **NEW**: Przycisk "Wyczyść wszystkie logi" z potwierdzeniem + +--- + ## ver. 0.309 (2026-02-23) - ApiloLogger + cache-busting CSS/JS + poprawki UI - **NEW**: `ApiloLogger` — logowanie operacji Apilo do tabeli `pp_log` z kontekstem JSON (send_order, resend_order, payment_sync, status_sync, status_poll) diff --git a/docs/TESTING.md b/docs/TESTING.md index e470a86..585b3d1 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -23,7 +23,7 @@ composer test # standard ## Aktualny stan ```text -OK (750 tests, 2114 assertions) +OK (758 tests, 2135 assertions) ``` Zweryfikowano: 2026-02-22 (ver. 0.304) diff --git a/tests/Unit/Domain/Integrations/IntegrationsRepositoryTest.php b/tests/Unit/Domain/Integrations/IntegrationsRepositoryTest.php index 96a979d..8bd2771 100644 --- a/tests/Unit/Domain/Integrations/IntegrationsRepositoryTest.php +++ b/tests/Unit/Domain/Integrations/IntegrationsRepositoryTest.php @@ -298,4 +298,69 @@ class IntegrationsRepositoryTest extends TestCase $this->assertSame('1', (string)$result[0]['id']); $this->assertSame('Przelew', (string)$result[0]['name']); } + + // ── Logs ──────────────────────────────────────────────────── + + public function testGetLogsReturnsItemsAndTotal(): void + { + $this->mockDb->expects($this->once()) + ->method('count') + ->with('pp_log', $this->anything()) + ->willReturn(2); + + $this->mockDb->expects($this->once()) + ->method('select') + ->with('pp_log', '*', $this->anything()) + ->willReturn([ + ['id' => 1, 'action' => 'send_order', 'message' => 'OK', 'date' => '2026-01-01 12:00:00'], + ['id' => 2, 'action' => 'status_sync', 'message' => 'Synced', 'date' => '2026-01-02 12:00:00'], + ]); + + $result = $this->repository->getLogs([], 'id', 'DESC', 1, 15); + + $this->assertIsArray($result); + $this->assertArrayHasKey('items', $result); + $this->assertArrayHasKey('total', $result); + $this->assertCount(2, $result['items']); + $this->assertSame(2, $result['total']); + } + + public function testGetLogsReturnsEmptyWhenNoResults(): void + { + $this->mockDb->method('count')->willReturn(0); + $this->mockDb->method('select')->willReturn([]); + + $result = $this->repository->getLogs([], 'id', 'DESC', 1, 15); + + $this->assertSame(0, $result['total']); + $this->assertEmpty($result['items']); + } + + public function testGetLogsHandlesNullFromSelect(): void + { + $this->mockDb->method('count')->willReturn(0); + $this->mockDb->method('select')->willReturn(null); + + $result = $this->repository->getLogs([], 'id', 'DESC', 1, 15); + + $this->assertSame([], $result['items']); + } + + public function testDeleteLogCallsDelete(): void + { + $this->mockDb->expects($this->once()) + ->method('delete') + ->with('pp_log', ['id' => 42]); + + $this->assertTrue($this->repository->deleteLog(42)); + } + + public function testClearLogsDeletesAll(): void + { + $this->mockDb->expects($this->once()) + ->method('delete') + ->with('pp_log', []); + + $this->assertTrue($this->repository->clearLogs()); + } } diff --git a/tests/Unit/admin/Controllers/IntegrationsControllerTest.php b/tests/Unit/admin/Controllers/IntegrationsControllerTest.php index 79e87bd..3cd51db 100644 --- a/tests/Unit/admin/Controllers/IntegrationsControllerTest.php +++ b/tests/Unit/admin/Controllers/IntegrationsControllerTest.php @@ -35,6 +35,33 @@ class IntegrationsControllerTest extends TestCase ); } + public function testHasLogsMethods(): void + { + $methods = [ + 'logs', + 'logs_clear', + ]; + + foreach ($methods as $method) { + $this->assertTrue( + method_exists($this->controller, $method), + "Method $method does not exist" + ); + } + } + + public function testLogsReturnsString(): void + { + $reflection = new \ReflectionClass($this->controller); + $this->assertEquals('string', (string) $reflection->getMethod('logs')->getReturnType()); + } + + public function testLogsClearReturnsVoid(): void + { + $reflection = new \ReflectionClass($this->controller); + $this->assertEquals('void', (string) $reflection->getMethod('logs_clear')->getReturnType()); + } + public function testHasAllApiloSettingsMethods(): void { $methods = [ diff --git a/updates/versions.php b/updates/versions.php index b96b56b..2c56836 100644 --- a/updates/versions.php +++ b/updates/versions.php @@ -1,5 +1,5 @@