feat(124): sms templates CRUD + order picker
- 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>
This commit is contained in:
@@ -414,3 +414,61 @@ tests/
|
||||
- Edycja przez `<a href=".../edit?id=N">`, toggle/delete przez `<form>` z `_token` i `js-confirm-delete`.
|
||||
- Wspolny pattern miedzy `accounting-receipts.php` i `accounting-invoices.php` (faktury maja dodatkowe kolumny: Tryb, Konto Fakturowni).
|
||||
|
||||
|
||||
## Phase 124 — SMS Templates
|
||||
|
||||
### SmsTemplateRepository (`src/Modules/Sms/SmsTemplateRepository.php`)
|
||||
- CRUD na `sms_templates` (PDO prepared statements, ToggleableRepositoryTrait).
|
||||
- `listAll()` (cala lista alfabetycznie po `name`), `listActive()` (tylko is_active=1, kolumny `id|name|body` do dropdownu w UI).
|
||||
- `save(array): int` waliduje wymagane `name` + `body` (rzuca `RuntimeException` gdy puste); wykonuje INSERT albo UPDATE wg obecnosci `id` w payloadzie; zwraca id rekordu.
|
||||
- `delete(int)`, `toggleStatus(int)` przez `toggleActive('sms_templates', $id)`.
|
||||
|
||||
### SmsVariableResolver (`src/Modules/Sms/SmsVariableResolver.php`)
|
||||
- Wydzielony z `Email\VariableResolver` — wspolna logika zmiennych dla Email i SMS.
|
||||
- `buildVariableMap(order, addresses, companySettings)` zwraca mape placeholderow: `zamowienie.*`, `kupujacy.*`, `adres.*`, `firma.*`, `przesylka.*` (`przesylka.numer`/`przesylka.link_sledzenia` z najnowszej paczki przez `ShipmentPackageRepository::findLatestByOrderId` + `DeliveryStatus::trackingUrl`).
|
||||
- `resolve(template, variableMap)` zastepuje `{{group.var}}` wartoscia z mapy (puste gdy brak klucza).
|
||||
|
||||
### Email\VariableResolver (refaktor)
|
||||
- Pozostaje final class z tym samym API publicznym (`buildVariableMap`/`resolve`) — `EmailSendingService` niezmieniony.
|
||||
- Konstruktor: `(ShipmentPackageRepository $repo, ?SmsVariableResolver $inner = null)`. Gdy `$inner` nie podany, sam tworzy SmsVariableResolver — backward compat dla starego wiringu.
|
||||
- Metody publiczne deleguja do `$this->inner` — zero duplikacji logiki zmiennych.
|
||||
|
||||
### SmsTemplateController (`src/Modules/Settings/SmsTemplateController.php`)
|
||||
- Mirror `EmailTemplateController` bez Quill/skrzynki/zalacznika/duplikacji.
|
||||
- Akcje: `index` (lista), `create`/`edit`/`save` (form CRUD), `delete`, `toggleStatus` (AJAX JSON), `getVariables` (JSON paleta dla ewentualnego dynamic palette).
|
||||
- `VARIABLE_GROUPS` jako stala klasy — pelne 5 grup (zamowienie/kupujacy/adres/firma/przesylka) zgodnie ze wspolnym SmsVariableResolver.
|
||||
- Routy: `/settings/sms-templates`, `/create`, `/edit`, `/save`, `/delete`, `/toggle`, `/variables`. CSRF `_token` na POST. Flash `settings.sms_templates.success|error`.
|
||||
|
||||
### OrdersController (rozszerzenie)
|
||||
- Dodane optional params konstruktora: `?SmsTemplateRepository $smsTemplates`, `?SmsVariableResolver $smsVariableResolver`, `?CompanySettingsRepository $companySettingsRepo` (po istniejacych SMS params; default null = backward compat).
|
||||
- `show()` przekazuje `$smsTemplates` (list active) do widoku jako `smsTemplates`.
|
||||
- Nowa metoda `smsTemplate(Request)` -> `GET /orders/{id}/sms/template?template_id=N` -> JSON `{ok, body, name}` z rozwinietymi zmiennymi. 400/404/500 dla nieprawidlowych parametrow/braku rekordu.
|
||||
|
||||
### Widok `orders/show.php`
|
||||
- Nad textarea `name="message"` (`#js-sms-message`) dodany conditional `<select data-sms-template-picker data-order-id data-message-target="js-sms-message">` z opcja domyslna + aktywne szablony (renderowany tylko gdy `$smsTemplatesList !== []`).
|
||||
- Textarea ma teraz `id="js-sms-message"` — JS target.
|
||||
|
||||
### Frontend module `public/assets/js/modules/sms-template-picker.js`
|
||||
- Vanilla JS, idempotent guard `window.__smsTemplatePickerBound` + per-element `dataset.smsPickerBound`.
|
||||
- Na `change` selecta: fetch `/orders/{id}/sms/template?template_id=N`, podstaw body do textarea, fire `input` event.
|
||||
- Gdy textarea ma juz tresc -> `OrderProAlerts.confirm({...})` options-object API (Phase 114 pattern). Po zatwierdzeniu nadpisuje, po anulowaniu resetuje select. Fallback na natywny `confirm()`.
|
||||
- Ladowany globalnie z `layouts/app.php` (linia po `notifications.js`).
|
||||
|
||||
### Wspolny resolver — wiring DI (`routes/web.php`)
|
||||
- `$smsVariableResolver = new SmsVariableResolver($shipmentPackageRepositoryForOrders);`
|
||||
- `$variableResolver = new VariableResolver($shipmentPackageRepositoryForOrders, $smsVariableResolver);` (drugi argument opcjonalny dla BC).
|
||||
- `$smsTemplateRepository = new SmsTemplateRepository($app->db());`
|
||||
- `$smsTemplateController = new SmsTemplateController($template, $translator, $auth, $smsTemplateRepository);`
|
||||
- `$ordersController` rozszerzony o 3 trailing params (smsTemplateRepository, smsVariableResolver, companySettingsRepository).
|
||||
|
||||
### SCSS — `_sms-templates.scss`
|
||||
- Nowy partial `resources/scss/modules/_sms-templates.scss` z klasami `.sms-template-*` (active label, counter, body grid) oraz `.sms-var-panel/.sms-var-group/.sms-var-item` dla palety zmiennych.
|
||||
- Import w `app.scss` po `customer-risk-alert`.
|
||||
|
||||
### Stopka — preserved Phase 122 contract
|
||||
- Szablony SMS NIE zawieraja `default_footer` — operator wpisuje sama tresc.
|
||||
- `SmsConversationService::buildFinalOutboundBody()` dokleja stopke raz przy `sendFromOrder()` (po wstawieniu szablonu i ewentualnej edycji przez operatora). Walidacja `MAX_SMS_LENGTH = 918` obowiazuje na finalnej tresci.
|
||||
|
||||
### BREAKING / migration
|
||||
- Migracja `20260512_000112_create_sms_templates.sql` — `CREATE TABLE IF NOT EXISTS sms_templates` (DDL, brak SELECT no-op).
|
||||
- Brak innych zmian schematu. `OrdersController` ctor: 3 NEW optional params (default null) — backwards compatible.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Database Schema
|
||||
|
||||
**Updated:** 2026-05-12 | **Total tables:** 60 | **Engine:** InnoDB | **Charset:** utf8mb4_unicode_ci
|
||||
**Updated:** 2026-05-12 | **Total tables:** 61 | **Engine:** InnoDB | **Charset:** utf8mb4_unicode_ci
|
||||
|
||||
---
|
||||
|
||||
@@ -617,6 +617,18 @@ UNIQUE: `(integration_id)` - one global SMSPLANET settings row.
|
||||
|
||||
**notifications** - Global notification center (Phase 121): stores type, title, body, target URL, related order/SMS references, `read_at`, and `created_at`. Indexes support unread polling by `(read_at, created_at)` and relation lookups.
|
||||
|
||||
**sms_templates** — SMS templates for quick send from order detail (Phase 124)
|
||||
| Column | Type | Nullable | Notes |
|
||||
|--------|------|----------|-------|
|
||||
| `id` | INT UNSIGNED | NO | PK, AUTO_INCREMENT |
|
||||
| `name` | VARCHAR(200) | NO | Display name in template picker |
|
||||
| `body` | TEXT | NO | Template body with `{{group.var}}` placeholders (e.g. `{{zamowienie.numer}}`, `{{przesylka.numer}}`). Footer NOT included — appended automatically by `SmsConversationService::buildFinalOutboundBody()` |
|
||||
| `is_active` | TINYINT(1) | NO | DEFAULT 1 — filters listActive() for picker dropdown |
|
||||
| `created_at` | DATETIME | NO | DEFAULT CURRENT_TIMESTAMP |
|
||||
| `updated_at` | DATETIME | NO | DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP |
|
||||
|
||||
Indexes: `sms_templates_active_name_idx (is_active, name)` — supports active-templates dropdown query.
|
||||
|
||||
---
|
||||
|
||||
## Accounting / Receipts
|
||||
|
||||
@@ -1,5 +1,31 @@
|
||||
# Technical Changelog
|
||||
|
||||
## 2026-05-12 - Phase 124 Plan 01: SMS Templates
|
||||
|
||||
**Co zrobiono:**
|
||||
- Nowa tabela `sms_templates(id, name, body, is_active, created_at, updated_at)` + indeks `(is_active, name)` — migracja `20260512_000112_create_sms_templates.sql` (DDL).
|
||||
- `Sms\SmsTemplateRepository` z minimalnym CRUD (`listAll/listActive/findById/save/delete/toggleStatus`); walidacja name+body w `save()`.
|
||||
- `Sms\SmsVariableResolver` (wydzielony z `Email\VariableResolver`) — wspolna logika `buildVariableMap` + `resolve` dla Email i SMS (placeholdery `{{zamowienie.*|kupujacy.*|adres.*|firma.*|przesylka.*}}`).
|
||||
- `Email\VariableResolver` zrefaktorowany na fasade — konstruktor opcjonalnie przyjmuje SmsVariableResolver; metody publiczne deleguja. `EmailSendingService` bez zmian.
|
||||
- `Settings\SmsTemplateController` + widoki `settings/sms-templates.php` (lista) + `settings/sms-templates-form.php` (CRUD form z paleta zmiennych po prawej, licznikiem znakow, walidacja maxlength 918).
|
||||
- 7 nowych rout: `GET/POST /settings/sms-templates`, `/create`, `/edit`, `/save`, `/delete`, `/toggle`, `/variables`.
|
||||
- Dropdown "Wybierz szablon" w zakladce SMS na `/orders/{id}` (renderowany tylko gdy istnieja aktywne szablony) -> JS module `sms-template-picker.js` fetchuje `GET /orders/{id}/sms/template?template_id=N` i wkleja rozwiniete body do textarea. Przy niepustej textarea pyta przez `OrderProAlerts.confirm({...})` (options-object API).
|
||||
- `OrdersController::smsTemplate()` — nowy endpoint JSON; rozszerzony konstruktor o `?SmsTemplateRepository`, `?SmsVariableResolver`, `?CompanySettingsRepository` (default null = BC).
|
||||
- Sidebar Ustawien rozszerzony o link "Szablony SMS" (active state na `currentSettings === 'sms-templates'`).
|
||||
- Nowy SCSS partial `modules/_sms-templates.scss` (paleta zmiennych, licznik znakow). Import w `app.scss`.
|
||||
- Tlumaczenia `orders.details.sms.template_picker(_placeholder)` w `resources/lang/pl.php`.
|
||||
|
||||
**Dlaczego:**
|
||||
- Operator wysyla powtarzalne SMS-y (numer sledzenia, przypomnienie o platnosci, prosba o opinie). Szablony eliminuja recznie wpisywanie tekstu i tracking number, redukujac wysylke do dropdown + ewentualnej korekty.
|
||||
- Wspolny VariableResolver bo dokladnie te same placeholdery sa potrzebne w Email i SMS (DRY); zachowanie kontraktu `Email\VariableResolver` jako fasady = zero ryzyka regresji w EmailSendingService.
|
||||
- Stopka SMSPLANET pozostaje doklejana wylacznie przez `SmsConversationService::buildFinalOutboundBody()` (Phase 122) — nie duplikujemy jej w szablonach, walidacja 918 znakow obowiazuje na finalnej tresci.
|
||||
|
||||
**Migracja:**
|
||||
- `php bin/migrate.php` po wlaczeniu MySQL — utworzy `sms_templates`.
|
||||
- Operator po wdrozeniu tworzy szablony manualnie z `/settings/sms-templates`.
|
||||
|
||||
**BREAKING:** brak. `OrdersController` ctor: nowe params optional. `Email\VariableResolver` ctor: nowy opcjonalny drugi argument (default null = self-construct SmsVariableResolver).
|
||||
|
||||
## 2026-05-12 - Phase 123 Plan 01: Receipts Export VAT Breakdown
|
||||
|
||||
**Co zrobiono:**
|
||||
|
||||
Reference in New Issue
Block a user