Files
orderPRO/.paul/phases/20-windows-client/20-01-PLAN.md
Jacek Pyziak 5fef42ba12 feat(20-windows-client): aplikacja C# WinForms do zdalnego druku etykiet
- 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>
2026-03-22 22:49:28 +01:00

14 KiB
Raw Blame History

phase, plan, type, wave, depends_on, files_modified, autonomous
phase plan type wave depends_on files_modified autonomous
20-windows-client 01 execute 1
18-01
19-01
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
false
## 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ł)
## 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)

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

<acceptance_criteria>

AC-1: Aplikacja uruchamia się w system tray

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

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ń

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

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ść

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>

Task 1: Projekt C# WinForms + system tray + ustawienia 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 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)
- `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 AC-1 satisfied (system tray), AC-2 satisfied (konfiguracja) Task 2: API client + polling + drukowanie 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 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ę
- `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 AC-3 satisfied (polling), AC-4 satisfied (drukowanie), AC-5 satisfied (obsługa błędów) Aplikacja Windows OrderPROPrint — system tray, polling API, drukowanie etykiet A6. 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 Type "approved" to continue, or describe issues to fix

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

<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>
After completion, create `.paul/phases/20-windows-client/20-01-SUMMARY.md`