- System tray app z NotifyIcon + ContextMenuStrip - Polling API orderPRO (GET /api/print/jobs/pending) - Pobieranie etykiet PDF i druk przez PdfiumViewer - Formularz ustawień: URL API, klucz, drukarka, interwał - Okno logów z historią (ciemny motyw, Consolas) - Self-contained .NET 8 publish (win-x64) - Milestone v0.7 Zdalne drukowanie etykiet — COMPLETE Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
325 lines
14 KiB
Markdown
325 lines
14 KiB
Markdown
---
|
||
phase: 20-windows-client
|
||
plan: 01
|
||
type: execute
|
||
wave: 1
|
||
depends_on: ["18-01", "19-01"]
|
||
files_modified:
|
||
- clients/windows/OrderPROPrint/OrderPROPrint.sln
|
||
- clients/windows/OrderPROPrint/OrderPROPrint.csproj
|
||
- clients/windows/OrderPROPrint/Program.cs
|
||
- clients/windows/OrderPROPrint/TrayApplicationContext.cs
|
||
- clients/windows/OrderPROPrint/Services/PrintApiClient.cs
|
||
- clients/windows/OrderPROPrint/Services/PrintService.cs
|
||
- clients/windows/OrderPROPrint/Services/PollingService.cs
|
||
- clients/windows/OrderPROPrint/Forms/SettingsForm.cs
|
||
- clients/windows/OrderPROPrint/Forms/SettingsForm.Designer.cs
|
||
- clients/windows/OrderPROPrint/Models/PrintJob.cs
|
||
- clients/windows/OrderPROPrint/Models/AppSettings.cs
|
||
- clients/windows/OrderPROPrint/Properties/Resources.resx
|
||
- clients/windows/OrderPROPrint/app.config
|
||
autonomous: false
|
||
---
|
||
|
||
<objective>
|
||
## Goal
|
||
Stworzyć aplikację C# WinForms działającą w system tray, która odpytuje API orderPRO o zlecenia wydruku, pobiera etykiety i drukuje je na drukarce termicznej Xprinter XP-420B (format A6).
|
||
|
||
## Purpose
|
||
Użytkownik może zlecić wydruk etykiety z przeglądarki (faza 19) i etykieta automatycznie wydrukuje się na drukarce podłączonej do jego komputera — bez ręcznego pobierania pliku i drukowania.
|
||
|
||
## Output
|
||
- Aplikacja WinForms (.NET 8) w `clients/windows/OrderPROPrint/`
|
||
- System tray icon z menu kontekstowym
|
||
- Polling API co N sekund (konfigurowalne)
|
||
- Automatyczny druk etykiet A6 na wybranej drukarce
|
||
- Formularz ustawień (URL API, klucz API, drukarka, interwał)
|
||
</objective>
|
||
|
||
<context>
|
||
## Project Context
|
||
@.paul/PROJECT.md
|
||
@.paul/ROADMAP.md
|
||
@.paul/STATE.md
|
||
|
||
## Prior Work
|
||
@.paul/phases/18-print-queue-backend/18-01-SUMMARY.md
|
||
- API endpoints (API key auth via X-Api-Key header, SHA-256):
|
||
- GET /api/print/jobs/pending — lista zleceń do wydruku (JSON array)
|
||
- GET /api/print/jobs/{id}/download — pobieranie pliku etykiety (binary)
|
||
- POST /api/print/jobs/{id}/complete — oznaczenie jako wydrukowane
|
||
- Auth: header X-Api-Key z raw key (serwer hashuje SHA-256 i porównuje)
|
||
|
||
@.paul/phases/19-ui-integration/19-01-SUMMARY.md
|
||
- UI tworzy print jobs przez POST /api/print/jobs i /api/print/jobs/bulk
|
||
- Status "pending" = gotowe do pobrania przez Windows Client
|
||
|
||
## Source Files
|
||
@src/Modules/Printing/PrintApiController.php — API endpoints consumed by this client
|
||
@src/Modules/Printing/ApiKeyMiddleware.php — auth pattern (X-Api-Key header)
|
||
</context>
|
||
|
||
<skills>
|
||
## Required Skills (from SPECIAL-FLOWS.md)
|
||
|
||
| Skill | Priority | When to Invoke | Loaded? |
|
||
|-------|----------|----------------|---------|
|
||
| sonar-scanner | required | Po APPLY, przed UNIFY | ○ |
|
||
|
||
## Skill Invocation Checklist
|
||
- [ ] sonar-scanner loaded (run before UNIFY) — uwaga: skanuje tylko PHP, C# poza zakresem
|
||
</skills>
|
||
|
||
<acceptance_criteria>
|
||
|
||
## AC-1: Aplikacja uruchamia się w system tray
|
||
```gherkin
|
||
Given aplikacja OrderPROPrint jest uruchomiona
|
||
When użytkownik patrzy na system tray (obszar powiadomień)
|
||
Then widzi ikonę aplikacji OrderPROPrint
|
||
And po kliknięciu prawym przyciskiem widzi menu: "Ustawienia", "Wstrzymaj/Wznów", "O programie", "Zamknij"
|
||
And dwuklik otwiera formularz ustawień
|
||
```
|
||
|
||
## AC-2: Konfiguracja połączenia z API
|
||
```gherkin
|
||
Given użytkownik otworzył formularz ustawień
|
||
When wpisuje URL API (np. https://orderpro.projectpro.pl), klucz API i wybiera drukarkę
|
||
Then ustawienia zapisują się w pliku konfiguracyjnym (app.config lub JSON)
|
||
And po ponownym uruchomieniu ustawienia są zapamiętane
|
||
And przycisk "Testuj połączenie" weryfikuje poprawność URL + klucza
|
||
```
|
||
|
||
## AC-3: Polling i pobieranie zleceń
|
||
```gherkin
|
||
Given aplikacja ma poprawną konfigurację API
|
||
When polling timer odpytuje GET /api/print/jobs/pending
|
||
Then pobiera listę zleceń ze statusem "pending"
|
||
And dla każdego zlecenia pobiera etykietę GET /api/print/jobs/{id}/download
|
||
And po wydruku oznacza POST /api/print/jobs/{id}/complete
|
||
And ikona tray pokazuje liczbę przetworzonych zleceń (tooltip lub balloon)
|
||
```
|
||
|
||
## AC-4: Drukowanie etykiety A6 na drukarce termicznej
|
||
```gherkin
|
||
Given pobrano plik etykiety (PDF)
|
||
When aplikacja wysyła do wybranej drukarki
|
||
Then etykieta drukuje się w formacie A6 (105×148mm)
|
||
And orientacja i marginesy są poprawne dla drukarki termicznej
|
||
And w razie błędu druku zlecenie nie jest oznaczane jako complete
|
||
```
|
||
|
||
## AC-5: Obsługa błędów i odporność
|
||
```gherkin
|
||
Given aplikacja jest uruchomiona
|
||
When API jest niedostępne lub klucz nieprawidłowy
|
||
Then ikona tray zmienia się na stan "error" (np. czerwona)
|
||
And tooltip pokazuje ostatni błąd
|
||
And polling kontynuuje próby (nie crash)
|
||
And po przywróceniu połączenia wraca do normalnego działania
|
||
```
|
||
|
||
</acceptance_criteria>
|
||
|
||
<tasks>
|
||
|
||
<task type="auto">
|
||
<name>Task 1: Projekt C# WinForms + system tray + ustawienia</name>
|
||
<files>
|
||
clients/windows/OrderPROPrint/OrderPROPrint.sln,
|
||
clients/windows/OrderPROPrint/OrderPROPrint.csproj,
|
||
clients/windows/OrderPROPrint/Program.cs,
|
||
clients/windows/OrderPROPrint/TrayApplicationContext.cs,
|
||
clients/windows/OrderPROPrint/Forms/SettingsForm.cs,
|
||
clients/windows/OrderPROPrint/Forms/SettingsForm.Designer.cs,
|
||
clients/windows/OrderPROPrint/Models/AppSettings.cs,
|
||
clients/windows/OrderPROPrint/Properties/Resources.resx
|
||
</files>
|
||
<action>
|
||
1. Utwórz projekt .NET 8 WinForms w `clients/windows/OrderPROPrint/`:
|
||
- `dotnet new winforms -n OrderPROPrint`
|
||
- Target: net8.0-windows
|
||
- NuGet: System.Text.Json (wbudowane), żadnych zewnętrznych zależności
|
||
|
||
2. Program.cs:
|
||
- Application.Run(new TrayApplicationContext())
|
||
- Nie pokazuj głównego okna — tylko tray
|
||
|
||
3. TrayApplicationContext (dziedziczy ApplicationContext):
|
||
- NotifyIcon z ikoną (embedded resource lub SystemIcons.Application)
|
||
- ContextMenuStrip z pozycjami:
|
||
- "Ustawienia" → otwiera SettingsForm
|
||
- "Wstrzymaj" / "Wznów" → toggle polling
|
||
- separator
|
||
- "O programie" → MessageBox z wersją
|
||
- "Zamknij" → Application.Exit()
|
||
- DoubleClick na ikonę → otwiera SettingsForm
|
||
- Tooltip: "OrderPRO Print — Oczekiwanie" (aktualizowany przez PollingService)
|
||
|
||
4. AppSettings (model):
|
||
- ApiUrl (string), ApiKey (string), PrinterName (string), PollIntervalSeconds (int, default 10)
|
||
- Zapis/odczyt do JSON: %APPDATA%/OrderPROPrint/settings.json
|
||
- Metoda Load() i Save() — statyczne
|
||
|
||
5. SettingsForm (WinForms designer):
|
||
- Pola: txtApiUrl, txtApiKey, cmbPrinter, nudPollInterval
|
||
- cmbPrinter: wypełniony z System.Drawing.Printing.PrinterSettings.InstalledPrinters
|
||
- Przycisk "Testuj połączenie" — GET /api/print/jobs/pending z podanym URL+key
|
||
- Sukces: zielony label "Połączono ✓"
|
||
- Błąd: czerwony label z komunikatem
|
||
- Przycisk "Zapisz" — AppSettings.Save() + zamknij formularz
|
||
- Przycisk "Anuluj" — zamknij bez zapisu
|
||
|
||
Avoid: Nie używaj WPF ani MAUI — czysty WinForms dla prostoty
|
||
Avoid: Nie dodawaj auto-start na tym etapie (to future enhancement)
|
||
</action>
|
||
<verify>
|
||
- `dotnet build` przechodzi bez błędów
|
||
- Aplikacja uruchamia się i pojawia się ikona w system tray
|
||
- Menu kontekstowe działa
|
||
- Formularz ustawień otwiera się i zamyka
|
||
- Lista drukarek się ładuje
|
||
- Ustawienia zapisują się do %APPDATA%/OrderPROPrint/settings.json
|
||
</verify>
|
||
<done>AC-1 satisfied (system tray), AC-2 satisfied (konfiguracja)</done>
|
||
</task>
|
||
|
||
<task type="auto">
|
||
<name>Task 2: API client + polling + drukowanie</name>
|
||
<files>
|
||
clients/windows/OrderPROPrint/Services/PrintApiClient.cs,
|
||
clients/windows/OrderPROPrint/Services/PrintService.cs,
|
||
clients/windows/OrderPROPrint/Services/PollingService.cs,
|
||
clients/windows/OrderPROPrint/Models/PrintJob.cs,
|
||
clients/windows/OrderPROPrint/TrayApplicationContext.cs
|
||
</files>
|
||
<action>
|
||
1. PrintJob (model):
|
||
- Id (int), OrderId (int), PackageId (int), LabelPath (string), Status (string)
|
||
- Deserializacja z JSON response GET /api/print/jobs/pending
|
||
|
||
2. PrintApiClient:
|
||
- Constructor: (string apiUrl, string apiKey)
|
||
- HttpClient z default header X-Api-Key
|
||
- async Task<List<PrintJob>> GetPendingJobsAsync()
|
||
- GET {apiUrl}/api/print/jobs/pending
|
||
- Deserializuj JSON response → List<PrintJob>
|
||
- async Task<byte[]> DownloadLabelAsync(int jobId)
|
||
- GET {apiUrl}/api/print/jobs/{jobId}/download
|
||
- Return raw bytes (PDF)
|
||
- async Task<bool> MarkCompleteAsync(int jobId)
|
||
- POST {apiUrl}/api/print/jobs/{jobId}/complete
|
||
- Return true jeśli 200
|
||
- async Task<bool> TestConnectionAsync()
|
||
- GET {apiUrl}/api/print/jobs/pending (sprawdza czy nie 401)
|
||
- Obsługa błędów: HttpRequestException, TaskCanceledException (timeout)
|
||
|
||
3. PrintService:
|
||
- static void PrintPdf(byte[] pdfBytes, string printerName)
|
||
- Zapisz PDF do temp file → drukuj przez Process.Start z parametrami drukarki
|
||
- Alternatywa: użyj System.Drawing.Printing.PrintDocument z renderowaniem PDF
|
||
- Preferowane podejście: zapisz temp PDF, użyj SumatraPDF CLI (portable) do silent print:
|
||
`SumatraPDF.exe -print-to "PrinterName" -silent label.pdf`
|
||
- Jeśli SumatraPDF niedostępny: fallback na ShellExecute "print" verb
|
||
- Ustaw rozmiar papieru: A6 (105×148mm) lub custom size z drukarki
|
||
- Po wydruku: usuń temp file
|
||
|
||
4. PollingService:
|
||
- Constructor: (PrintApiClient client, PrintService printService, Action<string> onStatusUpdate, Action<string> onError)
|
||
- System.Threading.Timer z interwałem z AppSettings
|
||
- Na każdy tick:
|
||
a. GetPendingJobsAsync()
|
||
b. Dla każdego job: DownloadLabelAsync → PrintPdf → MarkCompleteAsync
|
||
c. Aktualizuj status (callback onStatusUpdate)
|
||
d. Przy błędzie: callback onError, nie przerywaj pętli
|
||
- Start() / Stop() / IsRunning property
|
||
- Mutex/lock żeby nie nakładały się dwa ticki
|
||
|
||
5. Integracja w TrayApplicationContext:
|
||
- Po załadowaniu ustawień: utwórz PrintApiClient + PollingService
|
||
- onStatusUpdate → aktualizuj tooltip ikony ("Wydrukowano 3 etykiety")
|
||
- onError → zmień ikonę na error state, tooltip z komunikatem
|
||
- "Wstrzymaj" → PollingService.Stop(), zmień tekst menu na "Wznów"
|
||
- Przy zmianie ustawień → restart PollingService z nowymi parametrami
|
||
|
||
Avoid: Nie blokuj UI thread — cała komunikacja HTTP i drukowanie async
|
||
Avoid: Nie ignoruj błędów — każdy failed print musi być widoczny
|
||
Avoid: Nie oznaczaj jako complete jeśli druk nie powiódł się
|
||
</action>
|
||
<verify>
|
||
- `dotnet build` przechodzi
|
||
- Aplikacja łączy się z API (test z prawdziwym kluczem)
|
||
- Polling pobiera pending jobs
|
||
- Etykieta drukuje się na wskazanej drukarce
|
||
- Po wydruku job oznaczany jako complete w UI orderPRO
|
||
- Błąd API nie crashuje aplikacji
|
||
</verify>
|
||
<done>AC-3 satisfied (polling), AC-4 satisfied (drukowanie), AC-5 satisfied (obsługa błędów)</done>
|
||
</task>
|
||
|
||
<task type="checkpoint:human-verify" gate="blocking">
|
||
<what-built>
|
||
Aplikacja Windows OrderPROPrint — system tray, polling API, drukowanie etykiet A6.
|
||
</what-built>
|
||
<how-to-verify>
|
||
1. Otwórz `clients/windows/OrderPROPrint/` w Visual Studio lub uruchom `dotnet run`
|
||
2. Sprawdź: ikona pojawia się w system tray
|
||
3. Prawy klik → "Ustawienia" → wpisz URL + klucz API + wybierz drukarkę
|
||
4. Kliknij "Testuj połączenie" → powinno być "Połączono ✓"
|
||
5. Zapisz ustawienia, zamknij formularz
|
||
6. W orderPRO: zlecij wydruk etykiety (przycisk "Drukuj" w widoku przesyłki)
|
||
7. Poczekaj na polling (domyślnie 10s)
|
||
8. Sprawdź: etykieta wydrukowana na drukarce Xprinter
|
||
9. Sprawdź: w orderPRO kolejka wydruku → status "completed"
|
||
10. Sprawdź: tooltip ikony pokazuje liczbę wydrukowanych
|
||
11. Odłącz internet → sprawdź czy ikona zmienia się na "error"
|
||
12. Podłącz z powrotem → polling powinien wrócić do normy
|
||
</how-to-verify>
|
||
<resume-signal>Type "approved" to continue, or describe issues to fix</resume-signal>
|
||
</task>
|
||
|
||
</tasks>
|
||
|
||
<boundaries>
|
||
|
||
## DO NOT CHANGE
|
||
- src/Modules/Printing/* (backend API gotowe z fazy 18-19)
|
||
- database/migrations/* (schemat stabilny)
|
||
- Endpointy API — klient konsumuje istniejące, nie modyfikuje serwera
|
||
|
||
## SCOPE LIMITS
|
||
- Nie dodawaj auto-start z Windows (future enhancement)
|
||
- Nie buduj instalatora MSI/Setup (na razie `dotnet publish`)
|
||
- Nie dodawaj auto-update mechanizmu
|
||
- Nie modyfikuj kodu PHP — to jest czysty C# project
|
||
- Drukowanie tylko PDF — nie konwertuj do ZPL/EPL (drukarki termiczne z Windows driver)
|
||
|
||
</boundaries>
|
||
|
||
<verification>
|
||
Before declaring plan complete:
|
||
- [ ] `dotnet build` przechodzi bez błędów i ostrzeżeń
|
||
- [ ] Aplikacja uruchamia się w system tray (brak głównego okna)
|
||
- [ ] Formularz ustawień: URL, klucz API, drukarka, interwał
|
||
- [ ] "Testuj połączenie" działa z prawdziwym API
|
||
- [ ] Polling pobiera pending jobs z API
|
||
- [ ] Etykieta PDF drukuje się na wybranej drukarce
|
||
- [ ] Po wydruku job oznaczany jako complete
|
||
- [ ] Błędy nie crashują aplikacji
|
||
- [ ] Tooltip aktualizuje się z informacjami o statusie
|
||
- [ ] Brak natywnych alert() — to WinForms, więc MessageBox jest OK
|
||
- [ ] Wszystkie acceptance criteria spełnione
|
||
</verification>
|
||
|
||
<success_criteria>
|
||
- Wszystkie 5 AC spełnione
|
||
- Aplikacja stabilna — działa ciągle w tle bez crash
|
||
- Komunikacja z API poprawna (auth, polling, download, complete)
|
||
- Druk na drukarce termicznej w formacie A6
|
||
- Kod czytelny, SRP, async/await
|
||
</success_criteria>
|
||
|
||
<output>
|
||
After completion, create `.paul/phases/20-windows-client/20-01-SUMMARY.md`
|
||
</output>
|