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