--- phase: 13-email-mailboxes plan: 01 type: execute wave: 1 depends_on: [] files_modified: - database/migrations/20260315_000054_create_email_mailboxes_table.sql - database/migrations/20260315_000055_create_email_templates_table.sql - database/migrations/20260315_000056_create_email_logs_table.sql - src/Modules/Settings/EmailMailboxController.php - src/Modules/Settings/EmailMailboxRepository.php - resources/views/settings/email-mailboxes.php - src/Core/Application.php autonomous: false --- ## Goal Stworzyć fundament bazodanowy modułu e-mail (3 tabele) oraz pełny CRUD skrzynek pocztowych SMTP w sekcji Ustawienia z walidacją połączenia. ## Purpose Skrzynki pocztowe to fundament wysyłki e-mail — szablony (faza 14) i wysyłka z zamówień (faza 15) zależą od skonfigurowanych skrzynek SMTP. ## Output - 3 migracje SQL (email_mailboxes, email_templates, email_logs) - CRUD skrzynek pocztowych w Ustawienia > Skrzynki pocztowe - Endpoint testowania połączenia SMTP ## Project Context @.paul/PROJECT.md @.paul/ROADMAP.md @.paul/STATE.md ## Source Files @src/Modules/Settings/ReceiptConfigController.php (wzorzec CRUD w Settings) @src/Modules/Settings/ReceiptConfigRepository.php (wzorzec repository) @src/Modules/Settings/IntegrationSecretCipher.php (szyfrowanie haseł) @resources/views/settings/accounting.php (wzorzec widoku list+edit) @resources/views/layouts/app.php (nawigacja sidebar) @src/Core/Application.php (routing) ## Required Skills (from SPECIAL-FLOWS.md) | Skill | Priority | When to Invoke | Loaded? | |-------|----------|----------------|---------| | sonar-scanner | required | Po APPLY, przed UNIFY | ○ | **BLOCKING:** sonar-scanner musi być uruchomiony przed UNIFY. ## Skill Invocation Checklist - [ ] sonar-scanner uruchomiony po APPLY ## AC-1: Migracje tworzą 3 tabele ```gherkin Given baza danych bez tabel email_* When uruchomiona zostanie migracja przez Ustawienia > Baza danych Then tabele email_mailboxes, email_templates, email_logs istnieją z poprawnymi kolumnami i FK ``` ## AC-2: CRUD skrzynek pocztowych ```gherkin Given użytkownik na stronie /settings/email-mailboxes When dodaje nową skrzynkę (nazwa, serwer, port, użytkownik, hasło) Then skrzynka pojawia się na liście z zaszyfrowanym hasłem w DB And można ją edytować i usunąć ``` ## AC-3: Walidacja połączenia SMTP ```gherkin Given użytkownik wypełnił formularz skrzynki pocztowej When kliknie "Testuj połączenie" Then system próbuje nawiązać połączenie SMTP z podanymi danymi And wyświetla komunikat sukcesu lub szczegółowy błąd ``` ## AC-4: Nawigacja ```gherkin Given użytkownik zalogowany do panelu When przechodzi do Ustawienia Then widzi pozycję "Skrzynki pocztowe" w menu Settings And kliknięcie prowadzi do /settings/email-mailboxes ``` Task 1: Migracje SQL — 3 tabele email database/migrations/20260315_000054_create_email_mailboxes_table.sql, database/migrations/20260315_000055_create_email_templates_table.sql, database/migrations/20260315_000056_create_email_logs_table.sql **Tabela `email_mailboxes`:** - id INT UNSIGNED AUTO_INCREMENT PK - name VARCHAR(100) NOT NULL — nazwa wyświetlana (np. "Główna skrzynka") - smtp_host VARCHAR(255) NOT NULL - smtp_port SMALLINT UNSIGNED NOT NULL DEFAULT 587 - smtp_encryption ENUM('tls','ssl','none') NOT NULL DEFAULT 'tls' - smtp_username VARCHAR(255) NOT NULL - smtp_password_encrypted TEXT NOT NULL — szyfrowane przez IntegrationSecretCipher - sender_email VARCHAR(255) NOT NULL — adres nadawcy (from) - sender_name VARCHAR(200) DEFAULT NULL — nazwa nadawcy - is_default TINYINT(1) NOT NULL DEFAULT 0 - is_active TINYINT(1) NOT NULL DEFAULT 1 - created_at DATETIME DEFAULT CURRENT_TIMESTAMP - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP **Tabela `email_templates`:** - id INT UNSIGNED AUTO_INCREMENT PK - name VARCHAR(200) NOT NULL — nazwa szablonu - subject VARCHAR(500) NOT NULL — temat e-mail (może zawierać zmienne) - body_html TEXT NOT NULL — treść HTML (Quill output) - mailbox_id INT UNSIGNED DEFAULT NULL — FK do email_mailboxes (nullable = domyślna) - is_active TINYINT(1) NOT NULL DEFAULT 1 - created_at DATETIME DEFAULT CURRENT_TIMESTAMP - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP - FOREIGN KEY (mailbox_id) REFERENCES email_mailboxes(id) ON DELETE SET NULL **Tabela `email_logs`:** - id BIGINT UNSIGNED AUTO_INCREMENT PK - template_id INT UNSIGNED DEFAULT NULL — FK do email_templates - mailbox_id INT UNSIGNED DEFAULT NULL — FK do email_mailboxes - order_id INT UNSIGNED DEFAULT NULL — FK do orders - recipient_email VARCHAR(255) NOT NULL - recipient_name VARCHAR(200) DEFAULT NULL - subject VARCHAR(500) NOT NULL - body_html TEXT NOT NULL — treść po rozwinięciu zmiennych - attachments_json JSON DEFAULT NULL — lista załączników [{name, path, type}] - status ENUM('sent','failed','pending') NOT NULL DEFAULT 'pending' - error_message TEXT DEFAULT NULL - sent_at DATETIME DEFAULT NULL - created_at DATETIME DEFAULT CURRENT_TIMESTAMP - FOREIGN KEY (template_id) REFERENCES email_templates(id) ON DELETE SET NULL - FOREIGN KEY (mailbox_id) REFERENCES email_mailboxes(id) ON DELETE SET NULL - INDEX idx_email_logs_order (order_id) - INDEX idx_email_logs_status (status) - INDEX idx_email_logs_sent_at (sent_at) Wszystkie migracje idempotentne (IF NOT EXISTS). Uruchomić migrację przez /settings/database — tabele widoczne w bazie AC-1 satisfied: 3 tabele email_* istnieją z poprawnymi kolumnami i FK Task 2: CRUD skrzynek pocztowych + test SMTP src/Modules/Settings/EmailMailboxController.php, src/Modules/Settings/EmailMailboxRepository.php, resources/views/settings/email-mailboxes.php, src/Core/Application.php, resources/views/layouts/app.php **EmailMailboxRepository** (wzorzec jak ReceiptConfigRepository): - listAll(): array — wszystkie skrzynki (hasło NIE zwracane w liście) - findById(int $id): ?array — z odszyfrowanym hasłem (do edycji) - save(array $data): int — insert/update; hasło szyfrowane IntegrationSecretCipher - delete(int $id): bool — usunięcie skrzynki - findDefault(): ?array — skrzynka z is_default=1 **EmailMailboxController** (wzorzec jak ReceiptConfigController): - index(Request): Response — lista + formularz edycji (?edit=ID) - save(Request): Response — walidacja + zapis; pola: name, smtp_host, smtp_port, smtp_encryption, smtp_username, smtp_password, sender_email, sender_name, is_default - delete(Request): Response — usunięcie z potwierdzeniem - testConnection(Request): Response — AJAX POST; próba fsockopen/stream_socket_client do smtp_host:smtp_port z timeoutem 5s; odpowiedź JSON {success, message} **Walidacja testConnection:** - Odczytaj dane z POST (lub z DB jeśli id podane) - Odszyfruj hasło jeśli z DB - Spróbuj otworzyć socket do smtp_host:smtp_port (stream_socket_client, timeout 5s) - Jeśli OK: EHLO + AUTH LOGIN z username/password - Zwróć JSON: {success: true/false, message: "Połączenie OK" / "Błąd: ..."} - Użyj natywnego PHP (fsockopen/stream_socket_client + SMTP commands) — NIE dodawaj PHPMailer/SwiftMailer w tej fazie **Widok email-mailboxes.php** (wzorzec jak accounting.php): - Tabela skrzynek: Nazwa, Serwer, Port, Email nadawcy, Status (aktywna/nieaktywna), Domyślna (badge), Akcje (edytuj/usuń) - Formularz dodawania/edycji: name, smtp_host, smtp_port (default 587), smtp_encryption (select: TLS/SSL/Brak), smtp_username, smtp_password (type=password, placeholder "••••" przy edycji), sender_email, sender_name, is_default (checkbox) - Przycisk "Testuj połączenie" — AJAX POST do /settings/email-mailboxes/test, wynik w alert - Flash messages na sukces/błąd zapisu/usunięcia **Routing w Application.php:** - GET /settings/email-mailboxes → EmailMailboxController::index - POST /settings/email-mailboxes/save → EmailMailboxController::save - POST /settings/email-mailboxes/delete → EmailMailboxController::delete - POST /settings/email-mailboxes/test → EmailMailboxController::testConnection **Nawigacja w app.php:** - Dodać link "Skrzynki pocztowe" w sekcji Settings sidebar (ikona: ✉ lub odpowiednia z istniejącego zestawu) - currentSettings === 'email-mailboxes' **Hasło przy edycji:** - Jeśli pole password puste przy save — zachowaj istniejące zaszyfrowane hasło - Jeśli wypełnione — zaszyfruj nowe Avoid: Nie dodawać żadnych zewnętrznych bibliotek mailingowych (PHPMailer, SwiftMailer) — w tej fazie wystarczy natywny socket do testu połączenia. Biblioteka mailingowa będzie dodana w fazie 15. 1. Otworzyć /settings/email-mailboxes — widoczna pusta lista 2. Dodać skrzynkę z danymi SMTP — pojawia się na liście 3. Kliknąć "Testuj połączenie" — JSON response z wynikiem 4. Edytować skrzynkę (bez zmiany hasła) — hasło zachowane 5. Usunąć skrzynkę — znika z listy AC-2, AC-3, AC-4 satisfied: CRUD skrzynek działa, test SMTP działa, nawigacja widoczna CRUD skrzynek pocztowych z testem połączenia SMTP 1. Uruchomić migrację: Ustawienia > Baza danych > Migruj 2. Przejść do: Ustawienia > Skrzynki pocztowe 3. Dodać skrzynkę testową (dowolny serwer SMTP) 4. Kliknąć "Testuj połączenie" — sprawdzić wynik 5. Edytować skrzynkę — zmienić nazwę, zostawić hasło puste → hasło zachowane 6. Zaznaczyć "Domyślna" → badge na liście 7. Usunąć skrzynkę → znika Type "approved" to continue, or describe issues to fix ## DO NOT CHANGE - src/Modules/Accounting/* (moduł paragonów stabilny) - src/Modules/Orders/* (moduł zamówień — modyfikacje w fazie 15) - database/migrations/000001-000053 (istniejące migracje) ## SCOPE LIMITS - Ten plan tworzy tylko CRUD skrzynek pocztowych — szablony wiadomości to faza 14 - NIE dodawać bibliotek mailingowych (PHPMailer itp.) — to faza 15 - NIE implementować wysyłki maili — tylko test połączenia SMTP - NIE modyfikować widoków zamówień Before declaring plan complete: - [ ] 3 migracje SQL wykonane poprawnie (tabele istnieją) - [ ] CRUD skrzynek: dodawanie, edycja, usuwanie działa - [ ] Hasło SMTP szyfrowane w bazie (IntegrationSecretCipher) - [ ] Test połączenia SMTP zwraca JSON z wynikiem - [ ] Nawigacja: link "Skrzynki pocztowe" w Settings sidebar - [ ] Flash messages na sukces/błąd operacji - [ ] Brak alert()/confirm() — użyty OrderProAlerts - [ ] CSRF token w formularzach (_token) - [ ] Wszystkie acceptance criteria spełnione - Wszystkie 3 tabele email_* istnieją w bazie - CRUD skrzynek pocztowych w pełni funkcjonalny - Test połączenia SMTP działa (JSON response) - Hasła zaszyfrowane w DB - Nawigacja w sidebar poprawna - Brak błędów PHP, brak nowych ostrzeżeń After completion, create `.paul/phases/13-email-mailboxes/13-01-SUMMARY.md`