- Nowa tabela sms_templates (name + body + is_active) + minimalny CRUD.
- /settings/sms-templates: lista + formularz z paleta zmiennych (pill chips).
- Wydzielono Sms\SmsVariableResolver ze wspolna logika placeholderow;
Email\VariableResolver staje sie cienka fasada — EmailSendingService bez zmian.
- Dropdown "Wybierz szablon" w zakladce SMS na /orders/{id} z fetch
GET /orders/{id}/sms/template + OrderProAlerts.confirm przy nadpisaniu.
- Stopka SMSPLANET dalej doklejana wylacznie przez SmsConversationService
(Phase 122 contract preserved).
- Sidebar Ustawien: nowy link "Szablony SMS".
Migration: 20260512_000112_create_sms_templates.sql (CREATE TABLE).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
170 lines
12 KiB
Markdown
170 lines
12 KiB
Markdown
---
|
|
phase: 124-sms-templates
|
|
plan: 01
|
|
subsystem: sms
|
|
tags: [sms, templates, smsplanet, orders, settings]
|
|
requires:
|
|
- phase: 117-smsplanet-integration
|
|
provides: SmsplanetIntegrationRepository, SmsplanetApiClient
|
|
- phase: 121-smsplanet-conversation-notifications
|
|
provides: SmsConversationService::sendFromOrder, SMS tab w /orders/{id}
|
|
- phase: 122-smsplanet-default-footer
|
|
provides: default_footer + MAX_SMS_LENGTH = 918 validation on final body
|
|
- phase: 14-email-templates
|
|
provides: email_templates CRUD pattern, VARIABLE_GROUPS structure
|
|
provides:
|
|
- sms_templates DB table + CRUD repository
|
|
- /settings/sms-templates panel (lista + formularz)
|
|
- shared SmsVariableResolver (extracted from Email\VariableResolver)
|
|
- GET /orders/{id}/sms/template endpoint (JSON, variables resolved per-order)
|
|
- Dropdown "Wybierz szablon" w zakladce SMS na /orders/{id}
|
|
- sms-template-picker.js (vanilla JS, OrderProAlerts.confirm pattern)
|
|
affects: [future SMS automation (send_sms action), invoice.created event]
|
|
tech-stack:
|
|
added: []
|
|
patterns:
|
|
- "Shared VariableResolver pattern: Email\\VariableResolver as thin facade delegating to Sms\\SmsVariableResolver"
|
|
- "Idempotent JS module guard: window.__smsTemplatePickerBound + dataset.smsPickerBound"
|
|
- "Pill-style variable chips with monospace code + descriptive label (.sms-var-item)"
|
|
key-files:
|
|
created:
|
|
- database/migrations/20260512_000112_create_sms_templates.sql
|
|
- src/Modules/Sms/SmsTemplateRepository.php
|
|
- src/Modules/Sms/SmsVariableResolver.php
|
|
- src/Modules/Settings/SmsTemplateController.php
|
|
- resources/views/settings/sms-templates.php
|
|
- resources/views/settings/sms-templates-form.php
|
|
- resources/scss/modules/_sms-templates.scss
|
|
- public/assets/js/modules/sms-template-picker.js
|
|
modified:
|
|
- src/Modules/Email/VariableResolver.php
|
|
- src/Modules/Orders/OrdersController.php
|
|
- routes/web.php
|
|
- resources/views/orders/show.php
|
|
- resources/views/layouts/app.php
|
|
- resources/lang/pl.php
|
|
- resources/scss/app.scss
|
|
key-decisions:
|
|
- "SMS template fields minimal: name + body + is_active (no subject/mailbox/attachment)"
|
|
- "Footer NOT in template — appended by SmsConversationService (Phase 122 contract preserved)"
|
|
- "Email\\VariableResolver becomes facade — preserves EmailSendingService contract, zero regression risk"
|
|
- "Variable picker = pill chips with {{var}} + description on same row (not dropdown like email)"
|
|
- "Action column uses flex+gap not inline-flex on td — robust against block-display <form> children"
|
|
patterns-established:
|
|
- "Shared resolver: keep facade in original namespace for BC, move logic to new namespace"
|
|
- "Idempotent JS modules: window.__moduleBound + dataset.moduleBound guards"
|
|
- "Template picker UX: dropdown insert + OrderProAlerts.confirm on non-empty target"
|
|
duration: ~90min
|
|
started: 2026-05-12T23:30:00Z
|
|
completed: 2026-05-13T00:30:00Z
|
|
---
|
|
|
|
# Phase 124 Plan 01 — SMS Templates — SUMMARY
|
|
|
|
**SMS templates CRUD w `/settings/sms-templates` + dropdown "Wybierz szablon" na zakladce SMS w szczegolach zamowienia — wstawia tresc z rozwinietymi zmiennymi `{{kupujacy.imie_nazwisko}}`, `{{przesylka.numer}}` itp. Wspolny `SmsVariableResolver` wydzielony z Email\\VariableResolver bez regresji w wysylce e-mail.**
|
|
|
|
## Performance
|
|
|
|
| Metric | Value |
|
|
|--------|-------|
|
|
| Duration | ~90 min (PLAN + APPLY + UI fixes + UNIFY) |
|
|
| Started | 2026-05-12T23:30:00Z |
|
|
| Completed | 2026-05-13T00:30:00Z |
|
|
| Tasks | 5/5 ukonczone |
|
|
| Files modified | 17 (8 created + 9 modified) |
|
|
|
|
**Status:** UNIFY complete. Operator zaakceptowal UI po dwoch iteracjach UI fixes (chipy zmiennych + nowrap akcji w liscie).
|
|
|
|
## Files modified
|
|
|
|
| File | Change |
|
|
|------|--------|
|
|
| `database/migrations/20260512_000112_create_sms_templates.sql` | NEW — CREATE TABLE `sms_templates` + idx_active_name |
|
|
| `src/Modules/Sms/SmsTemplateRepository.php` | NEW — CRUD PDO repo z `ToggleableRepositoryTrait` |
|
|
| `src/Modules/Sms/SmsVariableResolver.php` | NEW — wspolny resolver wydzielony z Email\VariableResolver |
|
|
| `src/Modules/Email/VariableResolver.php` | REFACTOR — final class staje sie cienka fasada delegujaca do SmsVariableResolver; konstruktor optional 2nd arg dla BC |
|
|
| `src/Modules/Settings/SmsTemplateController.php` | NEW — index/create/edit/save/delete/toggleStatus/getVariables |
|
|
| `src/Modules/Orders/OrdersController.php` | EXTENDED — 3 optional ctor params + `smsTemplate()` endpoint + `smsTemplates` w show() view payload |
|
|
| `routes/web.php` | NEW use'y (SmsTemplateController, SmsTemplateRepository, SmsVariableResolver); wiring DI; 7 rout `/settings/sms-templates/*` + 1 ruta `/orders/{id}/sms/template`; OrdersController wiring +3 params |
|
|
| `resources/views/settings/sms-templates.php` | NEW — lista z toggle AJAX + js-confirm-delete |
|
|
| `resources/views/settings/sms-templates-form.php` | NEW — formularz CRUD z paleta zmiennych + licznikiem znakow |
|
|
| `resources/views/orders/show.php` | EXTENDED — dropdown `<select data-sms-template-picker>` nad textarea; textarea ma id `js-sms-message` |
|
|
| `resources/views/layouts/app.php` | sidebar sublink "Szablony SMS"; `<script>` tag dla sms-template-picker.js |
|
|
| `resources/lang/pl.php` | klucze `orders.details.sms.template_picker(_placeholder)` |
|
|
| `public/assets/js/modules/sms-template-picker.js` | NEW — vanilla JS, idempotent guard, fetch endpoint + OrderProAlerts.confirm przy nadpisaniu |
|
|
| `resources/scss/modules/_sms-templates.scss` | NEW — `.sms-template-*`, `.sms-var-*` klasy |
|
|
| `resources/scss/app.scss` | import nowego partiala |
|
|
| `.paul/codebase/db_schema.md` | sekcja `sms_templates` + Total tables 60 -> 61 |
|
|
| `.paul/codebase/architecture.md` | sekcja "Phase 124 — SMS Templates" |
|
|
| `.paul/codebase/tech_changelog.md` | wpis Phase 124 |
|
|
|
|
## Verification
|
|
|
|
- `php -l` przeszedl bez bledow na wszystkich zmienionych plikach PHP (11 plikow).
|
|
- `php bin/migrate.php` **nie uruchomione** — MySQL niedostepny w trakcie APPLY (sandbox/XAMPP offline). DDL jest prostym `CREATE TABLE IF NOT EXISTS`; operator uruchomi rownolegle z innymi pending migracjami.
|
|
- Browser smoke (CRUD `/settings/sms-templates`, dropdown na `/orders/{id}?tab=sms`, fetch endpoint, nadpisanie z confirm, wysylka SMS ze stopka raz) **pending operator** — wymaga zywego XAMPP + zalogowanego usera.
|
|
- SCSS `app.css` build **pending operator** (`npm run scss` / manualny rebuild) — `resources/scss/app.scss` ma nowy `@use modules/sms-templates`; bez rebuildu klasy `.sms-template-*` i `.sms-var-*` nie maja styli (form pozostaje funkcjonalny, ale paleta zmiennych bedzie bez ramki/koloru).
|
|
|
|
## Acceptance Criteria
|
|
|
|
| AC | Status |
|
|
|----|--------|
|
|
| AC-1: CRUD szablonow SMS w `/settings/sms-templates` | DONE (kod) — manual UAT pending |
|
|
| AC-2: Walidacja zapisu (puste name/body) | DONE — `SmsTemplateRepository::save()` rzuca RuntimeException; controller Flash danger + redirect |
|
|
| AC-3: Wspolny VariableResolver — bez regresji w Email | DONE — `Email\VariableResolver` deleguje, `EmailSendingService` niezmieniony |
|
|
| AC-4: Wstawianie szablonu w zakladce SMS z rozwinietymi zmiennymi | DONE — endpoint JSON + JS module z OrderProAlerts.confirm przy nadpisaniu |
|
|
| AC-5: Stopka doklejana raz, walidacja 918 znakow na finalnej tresci | DONE — szablon nie zawiera stopki; `SmsConversationService` niezmieniony (Phase 122 contract preserved) |
|
|
| AC-6: Sidebar link "Szablony SMS" z active state | DONE |
|
|
|
|
## Deviations from PLAN
|
|
|
|
| Type | Count | Impact |
|
|
|------|-------|--------|
|
|
| Scope additions (UI fixes po UAT) | 2 | Visual polish — bez zmiany kontraktu |
|
|
| Deferred | 0 | Brak |
|
|
| Plan items pominiete (low value) | 2 | Zero ryzyka |
|
|
|
|
### Deviations w trakcie APPLY
|
|
|
|
1. **`SmsConversationService::renderTemplate()` NIE zostala dodana.** Plan Task 2 zakladal nowa metode w Service, ale to nie dodaje wartosci — controller (`OrdersController::smsTemplate()`) uzywa `SmsVariableResolver` bezposrednio. Zachowuje kontrakt Service (Phase 122 contract preserved bez zmian sygnatury konstruktora). Zysk: mniej zmian w SmsConversationService, brak ryzyka regresji w Phase 121/122.
|
|
2. **`OrdersController` dostal `?CompanySettingsRepository` jako trzeci nowy optional param** — OrdersController nie mial wczesniej tego repo; zostal wstrzykniety razem z SmsTemplateRepository i SmsVariableResolver. Wiring w `routes/web.php` przekazuje `$companySettingsRepository` (juz istnial dla company-settings flow).
|
|
3. **Brak `preview` endpointu w SmsTemplateController** (plan wspominal o ewentualnym podgladzie z `order_id`). Pominiete — paleta zmiennych pod textarea + licznik znakow wystarcza.
|
|
|
|
### UI fixes po UAT (scope additions)
|
|
|
|
1. **Layout palety zmiennych** — pierwsza wersja (grid 50/50 z form-grid-2: textarea | paleta side-by-side) operator ocenil jako "kiepsko". Refactor: textarea na pelna szerokosc, paleta przeniesiona ponizej jako pelnoszerokie chipy w grupach oddzielonych dashed separatorem. Chipy zmienione z prostego `inline-block` na `border-radius: 999px` (pill) z `{{var}}` w `<code>` + opisem `<span class="sms-var-item__desc">` w jednym wierszu. Hover indigo (`#eef2ff`/`#6366f1`). Plik: `resources/scss/modules/_sms-templates.scss`, `resources/views/settings/sms-templates-form.php`.
|
|
2. **Akcje w liscie szablonow zawijaja sie** — `td.sms-template-actions` z `white-space: nowrap` nie wystarczyl, bo wewnetrzna `<form>` (delete) jest blokowa (klasa `inline-form` nie istnieje globalnie w SCSS). Fix: `.sms-template-actions { display: flex; flex-wrap: nowrap; gap: 6px; }` + `.sms-template-actions > form { display: inline-flex; margin: 0; }`. Plik: `resources/scss/modules/_sms-templates.scss`.
|
|
|
|
SCSS przebudowany `npx sass --style=compressed` po obu fixach. Operator zaakceptowal final UI ("jest ok").
|
|
|
|
## Pending Actions (operator)
|
|
|
|
- `php bin/migrate.php` (XAMPP MySQL online) — utworzy `sms_templates`.
|
|
- SCSS rebuild (`npm run scss` lub workflow operatora) dla `_sms-templates.scss`.
|
|
- UAT manualny:
|
|
1. `/settings/sms-templates/create` — utworz "Numer sledzenia": body `"Czesc {{kupujacy.imie_nazwisko}}, Twoja przesylka {{przesylka.numer}} jest w drodze. Sledzenie: {{przesylka.link_sledzenia}}"`. Zapisz.
|
|
2. Toggle status (AJAX) — dezaktywuj, ponownie aktywuj.
|
|
3. Edycja — zmien body, zapisz.
|
|
4. Duplikuj? — brak akcji (zgodnie z planem; minimalne pola).
|
|
5. Usun (potwierdzenie OrderProAlerts).
|
|
6. `/orders/{id}?tab=sms` z zamowieniem majacym paczke — dropdown widoczny, wybierz szablon, body wypelnia textarea z rozwinietymi `{{kupujacy.imie_nazwisko}}` i `{{przesylka.numer}}`.
|
|
7. Wpisz cos w textarea, wybierz inny szablon — pojawia sie OrderProAlerts confirm.
|
|
8. Wyslij SMS — sprawdz w `sms_messages.body` ze stopka SMSPLANET doklejona raz (jezeli skonfigurowana).
|
|
9. Regresja Email: wyslij e-mail z zamowienia z istniejacym szablonem — placeholders `{{kupujacy.imie_nazwisko}}` i `{{zamowienie.numer}}` rozwiazane normalnie.
|
|
|
|
## SonarQube
|
|
- CLI niedostepne (per Phase 116/117/121/122 history). Skipped, log w STATE Pending Actions.
|
|
|
|
## Next Phase Readiness
|
|
|
|
**Ready:**
|
|
- `SmsTemplateRepository::listActive()` + `SmsVariableResolver` gotowe do reuse w przyszlej automatyzacji SMS (akcja `send_sms` analogiczna do `send_email` z Phase 16).
|
|
- Wzorzec dropdown + JSON endpoint + OrderProAlerts.confirm = template do innych quick-insert mechanizmow (np. szablony notatek do zamowienia).
|
|
- Sidebar Settings ma teraz blok email-templates + sms-templates obok — naturalne miejsce na kolejne typy szablonow.
|
|
|
|
**Concerns:**
|
|
- Brak preview z realnymi danymi w formularzu (paleta tylko inline). Jezeli operator bedzie chcial weryfikowac wynik przed zapisaniem, dodac endpoint `POST /settings/sms-templates/preview` z `order_id` (mirror EmailTemplateController::preview).
|
|
- `SmsTemplateRepository::save()` rzuca `RuntimeException` zamiast zwracac strukturalne bledy — controller wlapuje przez `Throwable` i przekazuje message do Flash. Dla wiekszej UX precyzji rozwazyc `IntegrationConfigException` w przyszlosci.
|
|
|
|
**Blockers:** None.
|