- Przycisk "Drukuj" w prepare.php i show.php z AJAX + duplikat protection - Bulk print z listy zamówień (checkboxy + header action) - Kolejka wydruku w Ustawienia > Drukowanie (filtr statusu, retry) - POST /api/print/jobs/bulk endpoint (package_ids + order_ids) - ensureLabel() auto-download przez ShipmentProviderRegistry - Apaczka carrier_id = nazwa usługi, kolumna Przewoznik - Tab persistence (localStorage), label file_exists check - Fix use statement ApaczkaApiClient, redirect po utworzeniu przesyłki - Phase 17 (receipt duplicate guard) + Phase 18 (print queue backend) docs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
12 KiB
12 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous
| phase | plan | type | wave | depends_on | files_modified | autonomous | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 18-print-queue-backend | 01 | execute | 1 |
|
false |
Purpose
Fundament pod system zdalnego drukowania — aplikacja Windows (faza 20) będzie odpytywać te endpointy aby pobierać zlecenia wydruku i drukować etykiety na drukarce termicznej.
Output
- Migracja SQL: tabele print_jobs i print_api_keys
- Moduł Printing: PrintJobRepository, PrintApiKeyRepository, PrintApiController
- ApiKeyMiddleware do uwierzytelniania requestów z aplikacji Windows
- PrintSettingsController: CRUD kluczy API w ustawieniach
- REST API: 4 endpointy (create job, list pending, download label, mark complete)
Source Files
@src/Core/Routing/Router.php @src/Modules/Auth/AuthMiddleware.php @src/Modules/Shipments/ShipmentController.php @routes/web.php
## Required Skills (from SPECIAL-FLOWS.md)| Skill | Priority | When to Invoke | Loaded? |
|---|---|---|---|
| sonar-scanner | required | Po APPLY, przed UNIFY | ○ |
<acceptance_criteria>
AC-1: Tabele DB utworzone poprawnie
Given czysta baza danych
When uruchomię migrację 20260322_000058_create_print_tables.sql
Then tabele print_jobs i print_api_keys istnieją z poprawnymi kolumnami i indeksami
AC-2: CRUD kluczy API w ustawieniach
Given użytkownik jest zalogowany i otwiera Ustawienia > Drukowanie
When tworzy nowy klucz API (podaje nazwę)
Then klucz jest generowany, wyświetlany jednorazowo, zapisany w DB (hash)
And klucz można dezaktywować i usunąć
AC-3: API — tworzenie zlecenia wydruku
Given istnieje etykieta przesyłki (label_path w shipment_packages)
When użytkownik orderPRO wyśle POST /api/print/jobs z session auth
Then zlecenie wydruku zostaje utworzone ze statusem 'pending'
And odpowiedź zawiera ID zlecenia
AC-4: API — pobieranie zleceń i etykiet przez klienta
Given klient Windows uwierzytelnia się kluczem API (header X-Api-Key)
When wyśle GET /api/print/jobs/pending
Then otrzyma listę zleceń ze statusem 'pending'
And gdy wyśle GET /api/print/jobs/{id}/download
Then otrzyma plik PDF etykiety
AC-5: API — oznaczanie zlecenia jako wydrukowane
Given klient Windows pobrał i wydrukował etykietę
When wyśle PATCH /api/print/jobs/{id}/complete z kluczem API
Then zlecenie zmieni status na 'completed' z timestampem completed_at
AC-6: Nieprawidłowy klucz API odrzucony
Given request z nieprawidłowym lub brakującym kluczem API
When klient wyśle request do /api/print/*
Then odpowiedź to 401 Unauthorized (JSON)
</acceptance_criteria>
Task 1: Migracja DB — tabele print_jobs i print_api_keys database/migrations/20260322_000058_create_print_tables.sql Utworzyć migrację SQL z dwiema tabelami:**print_api_keys:**
- `id` INT AUTO_INCREMENT PRIMARY KEY
- `name` VARCHAR(128) NOT NULL — nazwa przyjazna ("Komputer biurowy")
- `key_hash` VARCHAR(128) NOT NULL — hash klucza (SHA-256)
- `key_prefix` VARCHAR(8) NOT NULL — pierwsze 8 znaków klucza (do identyfikacji)
- `is_active` TINYINT(1) NOT NULL DEFAULT 1
- `last_used_at` DATETIME NULL
- `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
Indeksy: UNIQUE na key_hash, INDEX na is_active.
**print_jobs:**
- `id` INT AUTO_INCREMENT PRIMARY KEY
- `order_id` BIGINT NOT NULL — FK → orders.id ON DELETE CASCADE
- `package_id` INT NOT NULL — FK → shipment_packages.id ON DELETE CASCADE
- `label_path` VARCHAR(255) NOT NULL — ścieżka do pliku PDF
- `status` ENUM('pending', 'printing', 'completed', 'failed') NOT NULL DEFAULT 'pending'
- `created_by` INT NOT NULL — kto zlecił wydruk
- `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
- `completed_at` DATETIME NULL
Indeksy: INDEX na status, INDEX na order_id, INDEX na package_id.
Konwencja: zgodna z istniejącymi migracjami (plik .sql, plain DDL).
Uruchomić migrację i sprawdzić SHOW CREATE TABLE print_jobs; SHOW CREATE TABLE print_api_keys;
AC-1 satisfied: tabele istnieją z poprawnymi kolumnami i indeksami
Task 2: Repozytoria i kontrolery — PrintJobRepository, PrintApiKeyRepository, ApiKeyMiddleware, PrintApiController, PrintSettingsController
src/Modules/Printing/PrintJobRepository.php,
src/Modules/Printing/PrintApiKeyRepository.php,
src/Modules/Printing/PrintApiController.php,
src/Modules/Printing/ApiKeyMiddleware.php,
src/Modules/Settings/PrintSettingsController.php,
routes/web.php
**PrintApiKeyRepository** (src/Modules/Printing/):
- `create(string $name, string $keyHash, string $keyPrefix): int` — INSERT, zwraca ID
- `findByKeyHash(string $keyHash): ?array` — szukanie po hashu (do auth)
- `listAll(): array` — lista kluczy (bez hashy, z prefixem)
- `deactivate(int $id): void` — SET is_active = 0
- `delete(int $id): void` — DELETE
- `updateLastUsed(int $id): void` — SET last_used_at = NOW()
- Używać PDO + prepared statements (wzorzec jak ReceiptRepository)
**PrintJobRepository** (src/Modules/Printing/):
- `create(array $data): int` — INSERT, zwraca ID
- `findPending(): array` — SELECT WHERE status = 'pending' ORDER BY created_at ASC
- `findById(int $id): ?array` — z JOIN na orders (internal_order_number) i shipment_packages (tracking_number)
- `markCompleted(int $id): void` — SET status = 'completed', completed_at = NOW()
- `markFailed(int $id, string $reason): void` — SET status = 'failed'
- Używać PDO + prepared statements
**ApiKeyMiddleware** (src/Modules/Printing/):
- Callable `__invoke(Request $request, callable $next): Response`
- Odczytuje header `X-Api-Key` z requestu
- Hashuje klucz (SHA-256), szuka w print_api_keys przez PrintApiKeyRepository
- Jeśli znaleziony i is_active = 1: updateLastUsed(), przekazuje do $next
- Jeśli brak/nieaktywny: zwraca Response::json(['error' => 'Unauthorized'], 401)
- Wzorzec: analogiczny do AuthMiddleware ale dla API
**PrintApiController** (src/Modules/Printing/):
- Constructor DI: PrintJobRepository, Request, AuthService
- `createJob(Request $request): Response` — POST /api/print/jobs
- Wymaga session auth (AuthMiddleware) — wywoływane z UI orderPRO
- Przyjmuje: package_id (required)
- Waliduje: shipment_packages.label_path istnieje i plik fizycznie istnieje
- Tworzy print_job ze statusem 'pending'
- Zwraca JSON: { id, status: 'pending' }
- `listPending(Request $request): Response` — GET /api/print/jobs/pending
- Wymaga API key (ApiKeyMiddleware)
- Zwraca JSON: lista pending jobs z: id, order_number, tracking_number, created_at
- `downloadLabel(Request $request): Response` — GET /api/print/jobs/{id}/download
- Wymaga API key
- Waliduje: job istnieje, plik istnieje
- Zwraca plik PDF (Content-Type: application/pdf)
- `markComplete(Request $request): Response` — PATCH /api/print/jobs/{id}/complete
- Wymaga API key
- Oznacza job jako completed
- Zwraca JSON: { id, status: 'completed' }
**PrintSettingsController** (src/Modules/Settings/):
- `index(Request $request): Response` — lista kluczy API (widok settings/printing)
- `createKey(Request $request): Response` — POST: generuje klucz (bin2hex(random_bytes(32))), hashuje SHA-256, zapisuje hash+prefix, zwraca klucz jednorazowo we flash message
- `deleteKey(Request $request): Response` — POST: usuwa klucz
**Routes** (routes/web.php):
Dodać na końcu pliku:
```php
// Print API — session auth (from orderPRO UI)
$router->post('/api/print/jobs', [$printApiController, 'createJob'], [$authMiddleware]);
// Print API — API key auth (from Windows client)
$router->get('/api/print/jobs/pending', [$printApiController, 'listPending'], [$apiKeyMiddleware]);
$router->get('/api/print/jobs/{id}/download', [$printApiController, 'downloadLabel'], [$apiKeyMiddleware]);
$router->post('/api/print/jobs/{id}/complete', [$printApiController, 'markComplete'], [$apiKeyMiddleware]);
// Print settings
$router->get('/settings/printing', [$printSettingsController, 'index'], [$authMiddleware]);
$router->post('/settings/printing/keys/create', [$printSettingsController, 'createKey'], [$authMiddleware]);
$router->post('/settings/printing/keys/{id}/delete', [$printSettingsController, 'deleteKey'], [$authMiddleware]);
```
DI: Zarejestrować nowe klasy w Application.php (lub tam gdzie inne moduły są rejestrowane).
Avoid:
- Nie używać PATCH (router może nie obsługiwać) — użyć POST dla markComplete
- Nie sklejać SQL — tylko prepared statements
- Nie przechowywać klucza API w plaintext — tylko hash SHA-256
1. Sprawdzić czy pliki PHP nie mają błędów składni: php -l na każdym pliku
2. Sprawdzić czy routes/web.php parsuje się poprawnie
AC-2 partially (backend), AC-3, AC-4, AC-5, AC-6 satisfied: API endpoints działają z prawidłowym auth
Backend do zdalnego drukowania:
- Tabele DB: print_jobs, print_api_keys
- REST API: 4 endpointy (create job, list pending, download, complete)
- API key middleware (X-Api-Key header)
- CRUD kluczy API w ustawieniach (backend only — widok w fazie 19)
1. Uruchom aplikację — sprawdź czy migracja przeszła (brak błędów)
2. Otwórz /settings/printing — powinien załadować się widok (nawet pusty)
3. Opcjonalnie: testuj API curlem
Type "approved" to continue, or describe issues to fix
DO NOT CHANGE
- src/Modules/Shipments/* (nie modyfikować istniejącego flow etykiet)
- src/Modules/Auth/AuthMiddleware.php (nie zmieniać session auth)
- src/Core/Routing/Router.php (nie modyfikować routera)
- database/migrations/ istniejące pliki (nie zmieniać)
SCOPE LIMITS
- Tylko backend — brak widoków HTML (oprócz minimalnego dla settings/printing)
- Brak integracji z przyciskiem "Drukuj" w widoku przesyłek (faza 19)
- Brak aplikacji Windows (faza 20)
- Widok settings/printing: minimalna lista kluczy + formularz tworzenia (pełny design w fazie 19)
<success_criteria>
- All tasks completed
- All verification checks pass
- No errors or warnings introduced
- API key auth działa (valid key → 200, invalid → 401)
- Print jobs CRUD przez API </success_criteria>