---
phase: 15-email-sending
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
  - composer.json
  - composer.lock
  - src/Modules/Email/EmailSendingService.php
  - src/Modules/Email/VariableResolver.php
  - src/Modules/Email/AttachmentGenerator.php
  - src/Modules/Orders/OrdersController.php
  - resources/views/orders/show.php
  - resources/scss/modules/_email-send.scss
  - resources/views/orders/partials/email-send-modal.php
  - routes/web.php
  - DOCS/DB_SCHEMA.md
  - DOCS/ARCHITECTURE.md
  - DOCS/TECH_CHANGELOG.md
autonomous: false
---

<objective>
## Goal
Zaimplementować pełny flow wysyłki e-mail z widoku zamówienia: wybór szablonu, podgląd z rozwiązanymi zmiennymi, wysyłka przez SMTP (PHPMailer), automatyczne załączniki (paragon PDF), logowanie do email_logs.

## Purpose
Zamyka milestone v0.4 Moduł E-mail — sprzedawca może wysyłać maile do kupujących bezpośrednio z zamówienia, z gotowymi szablonami i załącznikami, bez opuszczania panelu.

## Output
- PHPMailer jako zależność composer
- EmailSendingService: resolwer zmiennych, generowanie załączników, wysyłka SMTP
- Modal wysyłki e-mail na widoku zamówienia (wybór szablonu, podgląd, wysyłka)
- Endpoint POST /orders/{id}/send-email
- Logowanie wysyłek w email_logs z wyświetleniem w zakładce Dokumenty
</objective>

<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md

## Prior Work
@.paul/phases/13-email-mailboxes/13-01-SUMMARY.md — DB foundation, SMTP mailbox CRUD, IntegrationSecretCipher
@.paul/phases/14-email-templates/14-01-SUMMARY.md — Template CRUD, Quill.js, VARIABLE_GROUPS, ATTACHMENT_TYPES

## Source Files
@src/Modules/Settings/EmailMailboxRepository.php
@src/Modules/Settings/EmailTemplateRepository.php
@src/Modules/Settings/EmailTemplateController.php — VARIABLE_GROUPS, SAMPLE_DATA, ATTACHMENT_TYPES
@src/Modules/Settings/IntegrationSecretCipher.php
@src/Modules/Orders/OrdersController.php
@src/Modules/Accounting/ReceiptController.php — pdf() method, dompdf pattern
@src/Modules/Accounting/ReceiptRepository.php
@resources/views/orders/show.php
@routes/web.php
@database/migrations/20260315_000056_create_email_logs_table.sql
</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 after APPLY)
</skills>

<acceptance_criteria>

## AC-1: Wysyłka e-mail z zamówienia
```gherkin
Given użytkownik jest na widoku zamówienia z adresem e-mail kupującego
When kliknie "Wyślij e-mail", wybierze szablon i kliknie "Wyślij"
Then e-mail zostanie wysłany na adres kupującego przez skonfigurowaną skrzynkę SMTP
And zmienne w szablonie ({{zamowienie.numer}}, {{kupujacy.imie_nazwisko}} itd.) zostaną zastąpione danymi zamówienia
```

## AC-2: Załącznik paragon PDF
```gherkin
Given szablon ma ustawiony attachment_1 = 'receipt'
And zamówienie ma wystawiony paragon
When użytkownik wyśle e-mail z tym szablonem
Then do maila zostanie dołączony paragon w formacie PDF
And nazwa pliku to numer_paragonu.pdf
```

## AC-3: Logowanie wysyłek
```gherkin
Given użytkownik wysłał e-mail z zamówienia
When otworzy zakładkę Dokumenty na widoku zamówienia
Then zobaczy wpis z datą, tematem, odbiorcą, statusem (sent/failed) i opcją podglądu
```

## AC-4: Podgląd przed wysyłką
```gherkin
Given użytkownik wybrał szablon w modalu wysyłki
When kliknie "Podgląd"
Then zobaczy temat i treść z rozwiązanymi zmiennymi (dane z aktualnego zamówienia)
And zobaczy informację o załącznikach (jeśli dostępne)
```

## AC-5: Walidacja i obsługa błędów
```gherkin
Given brak skonfigurowanej skrzynki SMTP lub brak aktywnych szablonów
When użytkownik kliknie "Wyślij e-mail"
Then zobaczy komunikat o braku konfiguracji (nie modal wysyłki)

Given wysyłka SMTP nie powiedzie się
When błąd zostanie zwrócony przez PHPMailer
Then email_logs zapisze status='failed' z error_message
And użytkownik zobaczy komunikat o błędzie
```

</acceptance_criteria>

<tasks>

<task type="auto">
  <name>Task 1: PHPMailer + EmailSendingService z resolwerem zmiennych i załącznikami</name>
  <files>
    composer.json, composer.lock,
    src/Modules/Email/EmailSendingService.php,
    src/Modules/Email/VariableResolver.php,
    src/Modules/Email/AttachmentGenerator.php
  </files>
  <action>
    1. `composer require phpmailer/phpmailer` (v6.x)

    2. Utworzyć `src/Modules/Email/VariableResolver.php`:
       - Metoda `resolve(string $template, array $orderData): string`
       - Zamienia `{{grupa.zmienna}}` na wartości z danych zamówienia
       - Mapowanie zmiennych (reuse logiki z EmailTemplateController::VARIABLE_GROUPS):
         - zamowienie: numer (external_id lub id), numer_zewnetrzny, zrodlo (source), kwota (total_amount), waluta (currency), data (created_at formatted)
         - kupujacy: imie_nazwisko, email, telefon, login — z buyer_* pól zamówienia
         - adres: ulica, miasto, kod_pocztowy, kraj — z delivery address
         - firma: nazwa, nip — z company_settings
       - Zmienne bez wartości → pusty string (nie zostawiać {{...}})
       - Metoda `buildVariableMap(array $order, array $companySettings): array` — buduje płaską mapę 'grupa.zmienna' => wartość

    3. Utworzyć `src/Modules/Email/AttachmentGenerator.php`:
       - Metoda `generate(string $type, array $order): ?array` → ['filename' => '...', 'content' => '...binary...', 'mime' => 'application/pdf']
       - Dla type='receipt':
         - Pobrać paragon z ReceiptRepository::findByOrderId($orderId)
         - Jeśli brak paragonu → return null (nie blokuje wysyłki, po prostu brak załącznika)
         - Renderować PDF tym samym wzorcem co ReceiptController::pdf() — Dompdf z widoku receipts/print
         - Filename: str_replace(['/', '\\'], '_', $receiptNumber) . '.pdf'
       - Rozszerzalność: switch($type) z case 'receipt', default → null

    4. Utworzyć `src/Modules/Email/EmailSendingService.php`:
       - Konstruktor: Medoo $db, VariableResolver, AttachmentGenerator, IntegrationSecretCipher, TemplateEngine
       - Metoda `send(int $orderId, int $templateId, ?int $mailboxId = null): array`
         - Pobrać zamówienie (z buyer, address, items)
         - Pobrać szablon z EmailTemplateRepository
         - Pobrać skrzynkę: jeśli mailboxId podany → użyj; jeśli szablon ma mailbox_id → użyj; fallback → domyślna (is_default=1)
         - Jeśli brak skrzynki → throw RuntimeException('Brak skonfigurowanej skrzynki SMTP')
         - Pobrać company_settings dla zmiennych firma.*
         - Rozwiązać zmienne w subject i body_html przez VariableResolver
         - Wygenerować załączniki przez AttachmentGenerator (jeśli attachment_1 set)
         - Wysłać przez PHPMailer:
           - SMTP auth z odszyfrowanym hasłem (IntegrationSecretCipher::decrypt)
           - SMTPSecure = mailbox.smtp_encryption (tls/ssl/none→'')
           - Port = mailbox.smtp_port
           - From = mailbox.sender_email / sender_name
           - To = order buyer email
           - Subject = resolved subject
           - Body = resolved body_html (isHTML = true)
           - Attachments: addStringAttachment() dla każdego wygenerowanego
         - Zalogować do email_logs: template_id, mailbox_id, order_id, recipient_email, recipient_name, subject, body_html (resolved), attachments_json, status (sent/failed), error_message, sent_at
         - Return: ['success' => bool, 'error' => ?string, 'log_id' => int]
       - Metoda `preview(int $orderId, int $templateId): array`
         - Rozwiązuje zmienne i zwraca ['subject' => '...', 'body_html' => '...', 'attachments' => [...nazwy]]
         - NIE wysyła maila

    Avoid:
    - Nie używać natywnej funkcji mail() — tylko PHPMailer SMTP
    - Nie duplikować logiki zmiennych z EmailTemplateController — wydzielić do VariableResolver i docelowo zastąpić SAMPLE_DATA w kontrolerze referencją
    - Nie tworzyć plików tymczasowych na dysku dla załączników — używać addStringAttachment (in-memory)
  </action>
  <verify>
    - composer show phpmailer/phpmailer zwraca wersję 6.x
    - php -l src/Modules/Email/EmailSendingService.php — brak błędów składni
    - php -l src/Modules/Email/VariableResolver.php — brak błędów składni
    - php -l src/Modules/Email/AttachmentGenerator.php — brak błędów składni
  </verify>
  <done>AC-1 (wysyłka SMTP), AC-2 (załącznik paragon), AC-5 (obsługa błędów) — backend ready</done>
</task>

<task type="auto">
  <name>Task 2: Modal wysyłki e-mail + endpoint + logowanie + wyświetlanie w Dokumentach</name>
  <files>
    src/Modules/Orders/OrdersController.php,
    resources/views/orders/show.php,
    resources/views/orders/partials/email-send-modal.php,
    resources/scss/modules/_email-send.scss,
    routes/web.php,
    DOCS/DB_SCHEMA.md,
    DOCS/ARCHITECTURE.md,
    DOCS/TECH_CHANGELOG.md
  </files>
  <action>
    1. Dodać route w routes/web.php:
       - POST /orders/{id}/send-email → [$ordersController, 'sendEmail']
       - POST /orders/{id}/email-preview → [$ordersController, 'emailPreview']

    2. W OrdersController dodać metody:
       - `sendEmail(Request $request)`:
         - CSRF validation (_token)
         - Pobrać template_id, mailbox_id (opcjonalny) z POST
         - Wywołać EmailSendingService::send()
         - Flash message sukces/błąd
         - JSON response (AJAX): ['success' => bool, 'message' => '...']
       - `emailPreview(Request $request)`:
         - Pobrać template_id z POST
         - Wywołać EmailSendingService::preview()
         - JSON response: ['subject' => '...', 'body_html' => '...', 'attachments' => [...]]

    3. Utworzyć `resources/views/orders/partials/email-send-modal.php`:
       - Modal overlay z klasą .email-send-modal
       - Formularz:
         - Select "Szablon" — lista aktywnych szablonów (przekazane z kontrolera)
         - Select "Skrzynka" — lista aktywnych skrzynek (z opcją "Domyślna z szablonu")
         - Readonly "Odbiorca" — buyer email z zamówienia
         - Przycisk "Podgląd" — AJAX POST /orders/{id}/email-preview → wyświetla resolved subject + body w div.preview
         - Div .email-preview-area — ukryty domyślnie, pokazuje podgląd subject + body + lista załączników
         - Przycisk "Wyślij" — AJAX POST /orders/{id}/send-email
         - Przycisk "Anuluj" — zamyka modal
       - CSRF token hidden input
       - JS: fetch API do podglądu i wysyłki, loading states, error handling
       - Po udanej wysyłce: zamknij modal, pokaż OrderProAlerts.success(), odśwież sekcję dokumentów (lub reload)

    4. W resources/views/orders/show.php:
       - Dodać przycisk "Wyślij e-mail" w .order-details-actions (po "Wystaw paragon"):
         - `<button class="btn btn--secondary" id="btn-send-email">Wyślij e-mail</button>`
         - Przycisk aktywny tylko jeśli: są aktywne szablony + aktywne skrzynki + zamówienie ma buyer email
         - Jeśli brak konfiguracji → btn--disabled z title="Skonfiguruj skrzynkę i szablony w Ustawieniach"
       - Include modal partial: `<?php include __DIR__ . '/partials/email-send-modal.php'; ?>`
       - W zakładce "documents" dodać sekcję "Wysłane e-maile":
         - Tabela: Data | Temat | Odbiorca | Status | Akcje
         - Status: badge sent (zielony) / failed (czerwony)
         - Akcje: przycisk "Podgląd" otwierający modal z body_html z logu
         - Dane z email_logs WHERE order_id = current order
       - W kontrolerze show(): załadować email_logs dla zamówienia + aktywne szablony + aktywne skrzynki

    5. Style SCSS w resources/scss/modules/_email-send.scss:
       - .email-send-modal — overlay + centered card (wzorować na istniejących modalach w projekcie)
       - .email-preview-area — border, padding, max-height z overflow-y scroll
       - .email-log-status--sent / --failed badges
       - Import w głównym SCSS

    6. Zaktualizować DOCS/DB_SCHEMA.md, DOCS/ARCHITECTURE.md, DOCS/TECH_CHANGELOG.md:
       - Nowy moduł Email (EmailSendingService, VariableResolver, AttachmentGenerator)
       - Nowe endpointy /orders/{id}/send-email i /email-preview
       - PHPMailer jako nowa zależność

    Avoid:
    - Nie dodawać natywnych alert()/confirm() — używać OrderProAlerts
    - Nie trzymać styli w pliku widoku — tylko SCSS
    - Nie tworzyć nowego taba na widoku zamówienia — wyświetlać emaile w istniejącej zakładce Dokumenty
    - Nie dodawać bulk email z listy zamówień — tylko z widoku pojedynczego zamówienia
  </action>
  <verify>
    - php -l src/Modules/Orders/OrdersController.php — brak błędów składni
    - Przycisk "Wyślij e-mail" widoczny na /orders/{id}
    - Modal otwiera się po kliknięciu
    - Podgląd pokazuje rozwiązane zmienne
    - Po wysyłce email_logs ma nowy rekord
    - Zakładka Dokumenty pokazuje historię wysyłek
  </verify>
  <done>AC-1 (pełny flow UI), AC-3 (logowanie i wyświetlanie), AC-4 (podgląd), AC-5 (walidacja UI) satisfied</done>
</task>

<task type="checkpoint:human-verify" gate="blocking">
  <what-built>Pełny flow wysyłki e-mail z widoku zamówienia: wybór szablonu, podgląd ze zmiennymi, wysyłka SMTP z załącznikiem paragon PDF, logowanie i historia w zakładce Dokumenty.</what-built>
  <how-to-verify>
    1. Otwórz zamówienie z adresem e-mail kupującego: /orders/{id}
    2. Sprawdź przycisk "Wyślij e-mail" w pasku akcji
    3. Kliknij — powinien otworzyć się modal z wyborem szablonu i skrzynki
    4. Wybierz szablon → kliknij "Podgląd" → sprawdź czy zmienne zostały rozwiązane (numer zamówienia, dane kupującego)
    5. Jeśli szablon ma załącznik "Paragon" i zamówienie ma paragon → podgląd powinien pokazać załącznik
    6. Kliknij "Wyślij" → sprawdź czy mail dotarł na skrzynkę odbiorcy
    7. Sprawdź zakładkę Dokumenty → sekcja "Wysłane e-maile" powinna pokazywać nowy wpis ze statusem "sent"
    8. Przetestuj błąd: zmień SMTP na nieprawidłowy → wyślij → status powinien być "failed" z error_message
    9. Przetestuj brak konfiguracji: usuń skrzynki → przycisk powinien być nieaktywny z tooltipem
  </how-to-verify>
  <resume-signal>Type "approved" to continue, or describe issues to fix</resume-signal>
</task>

</tasks>

<boundaries>

## DO NOT CHANGE
- database/migrations/* — tabele email_logs, email_mailboxes, email_templates już istnieją (Phase 13-14)
- src/Modules/Settings/EmailMailboxController.php — CRUD skrzynek bez zmian
- src/Modules/Settings/EmailTemplateController.php — CRUD szablonów bez zmian (poza ewentualnym reuse VARIABLE_GROUPS)
- src/Modules/Accounting/* — moduł paragonów bez zmian (tylko odczyt z ReceiptRepository)

## SCOPE LIMITS
- Brak bulk email z listy zamówień — tylko z widoku pojedynczego zamówienia
- Brak kolejki/retry — wysyłka synchroniczna (async/cron to przyszły milestone)
- Brak edycji treści maila w modalu — tylko wybór szablonu i podgląd
- Brak nowych tabel/migracji — wykorzystanie istniejącej email_logs

</boundaries>

<verification>
Before declaring plan complete:
- [ ] composer show phpmailer/phpmailer zwraca 6.x
- [ ] php -l na wszystkich nowych/zmodyfikowanych plikach PHP — brak błędów
- [ ] Przycisk "Wyślij e-mail" widoczny na widoku zamówienia
- [ ] Modal otwiera się, podgląd rozwiązuje zmienne
- [ ] Wysyłka SMTP działa (mail dociera do odbiorcy)
- [ ] Załącznik paragon PDF dołączany gdy szablon ma attachment_1='receipt'
- [ ] email_logs zapisuje wpis z poprawnym statusem
- [ ] Zakładka Dokumenty wyświetla historię emaili
- [ ] DOCS zaktualizowane
- [ ] Wszystkie acceptance criteria spełnione
</verification>

<success_criteria>
- All tasks completed
- All verification checks pass
- No PHP errors or warnings introduced
- E-mail z zamówienia działa end-to-end (szablon → podgląd → wysyłka → log → historia)
- Paragon PDF jako załącznik działa (gdy dostępny)
- Brak regresji w istniejących modułach (zamówienia, paragony, szablony, skrzynki)
</success_criteria>

<output>
After completion, create `.paul/phases/15-email-sending/15-01-SUMMARY.md`
</output>
