feat(121+122): smsplanet conversation, notifications, default footer

Phase 121 — SMSPLANET Conversation + Notifications:
- migration 20260512_000110 adds smsplanet conversation + notifications tables
- src/Modules/Sms (SmsConversationService, SmsMessageRepository, SmsplanetWebhookController)
- src/Modules/Notifications (Repository, Controller, ApiController)
- order SMS tab, notification center, sender mode, inbound webhook
- public notifications.js + layouts/app.php integration

Phase 122 — SMSPLANET Default SMS Footer:
- migration 20260512_000111 adds smsplanet_integration_settings.default_footer
- footer appended to test SMS and order SMS, validated against 918 char limit
- settings textarea + compact order SMS note when footer configured

Bundled (could not split per-phase without hunk staging):
- routes/web.php (also carries Phase 118 fakturownia redirects)
- DOCS/{ARCHITECTURE,DB_SCHEMA,TECH_CHANGELOG}.md (118 + 121 + 122 entries)
- .paul/codebase/{architecture,db_schema,tech_changelog}.md (118 + 121 + 122)
- .paul/STATE.md, ROADMAP.md, changelog/2026-05-12.md (UNIFY closure)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-05-12 20:37:41 +02:00
parent 8f14851d85
commit 360eef128d
34 changed files with 2538 additions and 128 deletions

View File

@@ -40,6 +40,8 @@ HTTP Request
| **Email** | 3 | `EmailSendingService`, `VariableResolver`, `AttachmentGenerator` | Template-based email with PDF attachments |
| **Automation** | 6 | `AutomationService` (834 LOC), `AutomationRepository`, `AutomationExecutionLogRepository` | Event→condition→action rules, email triggers |
| **Settings** | 54+ | Integration controllers, OAuth clients, API clients (Fakturownia incl.), mappers | Allegro/shopPRO/Apaczka/InPost/Fakturownia config, status mappings |
| **Sms** | 3 | `SmsMessageRepository`, `SmsConversationService`, `SmsplanetWebhookController` | SMSPLANET outbound order SMS, inbound webhook parsing, order matching |
| **Notifications** | 3 | `NotificationRepository`, `NotificationController`, `NotificationApiController` | Global notification history, unread polling API, mark-read actions |
| **Cron** | 12 | `CronRepository`, `CronHandlerFactory`, handler classes | Scheduled imports, syncs, token refresh |
| **Printing** | 4 | `PrintApiController`, `PrintJobRepository`, `ApiKeyMiddleware` | REST API for Windows print client |
| **Statistics** | 3 | `OrdersStatisticsController`, `OrdersStatisticsRepository`, `statistics-summary-charts.js` | Daily order statistics and monthly summary charts |
@@ -309,7 +311,7 @@ tests/
- Zarzadza pojedynczym rekordem `smsplanet_integration_settings` (`id=1`) i bazowym wpisem `integrations` typu `smsplanet`.
- Obsluguje dwie metody autoryzacji: Bearer token oraz `key` + `password`.
- Szyfruje token, klucz API i haslo przez `IntegrationSecretCipher`; formularz widzi tylko flagi `has_api_token`, `has_api_key` i `has_api_password`.
- Udostepnia `getCredentials()` tylko dla kompletnej i aktywnej konfiguracji testowej wysylki SMS.
- Udostepnia `getCredentials()` tylko dla kompletnej i aktywnej konfiguracji testowej wysylki SMS, razem z opcjonalna `default_footer`.
### SmsplanetApiClient (`src/Modules/Settings/SmsplanetApiClient.php`)
- Wykonuje `POST https://api2.smsplanet.pl/sms` jako `application/x-www-form-urlencoded`.
@@ -319,11 +321,24 @@ tests/
### SmsplanetIntegrationController (`src/Modules/Settings/SmsplanetIntegrationController.php`)
- Endpointy: `GET /settings/integrations/smsplanet`, `POST /settings/integrations/smsplanet/save`, `POST /settings/integrations/smsplanet/test`.
- `test` realnie wysyla SMS z edytowalna trescia i zapisuje wynik w `integrations.last_test_*`.
- `test` realnie wysyla SMS z edytowalna trescia, dopisuje `default_footer` gdy jest skonfigurowana i zapisuje wynik w `integrations.last_test_*`.
### IntegrationsHubController
- Dodaje wiersz SMSPLANET do `/settings/integrations` ze statusem konfiguracji, sekretu, aktywnosci i ostatniego testu.
## Phase 121 - SMSPLANET Conversation + Notifications
### SmsConversationService (`src/Modules/Sms/SmsConversationService.php`)
- Wysyla SMS z poziomu zamowienia przez `SmsplanetApiClient`, dopisuje `default_footer` gdy jest skonfigurowana, zapisuje finalna tresc w `sms_messages` i uzywa `sender_mode` do wyboru nadpisu albo numeru 2WAY.
- Parsuje publiczny webhook `/webhooks/smsplanet/inbound`, normalizuje telefony i dopasowuje przychodzacy SMS do najnowszego zamowienia po telefonie klienta/adresu.
- Endpoint inbound akceptuje POST i GET; format 2WAY `message=<JSON>` jest dekodowany, sukces zwraca plain `OK`, a dopasowanie zamowienia korzysta z `order_addresses.phone`.
- Tworzy `notifications.type='sms_inbound'` z linkiem do `/orders/{id}?tab=sms`.
### Notifications module
- `/notifications` pokazuje historie powiadomien i pozwala oznaczac wpisy jako przeczytane.
- `/api/notifications/unread` zasila topbar badge oraz `public/assets/js/modules/notifications.js`.
- Browser Notification API jest progresywne: brak zgody nie blokuje strony ani pollingu.
## Phase 120 — Alert Component Unification
### Alert component (`resources/views/components/alert.php`)

View File

@@ -558,11 +558,11 @@ UNIQUE: `(type, name)`
| `created_at` | DATETIME | NO | |
| `updated_at` | DATETIME | NO | |
**fakturownia_integration_settings** — Fakturownia account credentials (Phase 113-01; multi-account via integration_id)
**fakturownia_integration_settings** — Fakturownia account credentials (Phase 118; fixed 1 row)
| Column | Type | Nullable | Notes |
|--------|------|----------|-------|
| `id` | INT UNSIGNED | NO | PK |
| `integration_id` | INT UNSIGNED | NO | UNIQUE, FK → integrations(id) CASCADE |
| `id` | INT UNSIGNED | NO | PK, always 1 |
| `integration_id` | INT UNSIGNED | NO | UNIQUE, FK → integrations(id) CASCADE; points to the single global Fakturownia integration |
| `account_prefix` | VARCHAR(64) | NO | Subdomain: {prefix}.fakturownia.pl |
| `api_token_encrypted` | TEXT | YES | AES-encrypted via `IntegrationSecretCipher` |
| `department_id` | VARCHAR(64) | YES | Optional Fakturownia department |
@@ -571,7 +571,7 @@ UNIQUE: `(type, name)`
| `created_at` | DATETIME | NO | |
| `updated_at` | DATETIME | NO | |
UNIQUE: `(integration_id)` — one settings row per Fakturownia integration. Multiple integrations of `type='fakturownia'` allowed.
UNIQUE: `(integration_id)` — one global Fakturownia settings row. Phase 118 migration keeps the active Fakturownia integration, rewires delegated `invoice_configs.integration_id` to it, and removes extra Fakturownia integration rows.
---
@@ -600,9 +600,12 @@ UNIQUE: `(integration_id)` - one global HostedSMS settings row.
| `api_token_encrypted` | TEXT | YES | AES-encrypted Bearer token via `IntegrationSecretCipher` |
| `api_key_encrypted` | TEXT | YES | AES-encrypted API key via `IntegrationSecretCipher` |
| `api_password_encrypted` | TEXT | YES | AES-encrypted API password via `IntegrationSecretCipher` |
| `sender` | VARCHAR(32) | YES | SMSPLANET `from` sender |
| `sender` | VARCHAR(32) | YES | Text sender / nadpis |
| `sender_mode` | VARCHAR(16) | NO | DEFAULT `text`; `text` uses `sender`, `phone` uses `sender_phone` |
| `sender_phone` | VARCHAR(32) | YES | SMSPLANET 2WAY phone number |
| `clear_polish` | TINYINT(1) | NO | DEFAULT 0 |
| `transactional` | TINYINT(1) | NO | DEFAULT 0 |
| `default_footer` | TEXT | YES | Optional global footer appended to SMSPLANET test and order SMS |
| `created_at` | DATETIME | NO | |
| `updated_at` | DATETIME | NO | |
@@ -610,6 +613,12 @@ UNIQUE: `(integration_id)` - one global SMSPLANET settings row.
---
**sms_messages** - SMSPLANET inbound/outbound conversation history (Phase 121): stores direction, provider, nullable `order_id BIGINT UNSIGNED`, original and normalized phone endpoints, SMS body, provider `message_id`, status, raw JSON payload, optional `created_by`, and timestamps. Indexes: `(order_id, created_at)`, normalized phone columns, and `(provider, message_id)`.
**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.
---
## Accounting / Receipts
**receipt_configs** — Receipt generation configurations
@@ -666,7 +675,7 @@ UNIQUE: `(config_id, year, month)`
|--------|------|----------|-------|
| `id` | INT UNSIGNED | NO | PK |
| `name` | VARCHAR(128) | NO | |
| `integration_id` | INT UNSIGNED | YES | FK → integrations(id) SET NULL — points to Fakturownia account when delegated |
| `integration_id` | INT UNSIGNED | YES | FK → integrations(id) SET NULL — delegated configs always point to the single global Fakturownia integration |
| `is_delegated` | TINYINT(1) | NO | DEFAULT 0 — when 1, invoice creation calls Fakturownia API |
| `is_active` | TINYINT(1) | NO | DEFAULT 1 |
| `number_format` | VARCHAR(64) | NO | DEFAULT 'FV/%N/%M/%Y' |

View File

@@ -1,5 +1,44 @@
# Technical Changelog
## 2026-05-12 - SMSPLANET Inbound Webhook Fix
**Co zrobiono:**
- Poprawiono inbound SMSPLANET: dopasowanie po telefonie uzywa `order_addresses.phone`, a nie nieistniejacego w produkcji `orders.buyer_phone`.
- Dodano GET dla `/webhooks/smsplanet/inbound` obok POST, dekodowanie formatu 2WAY `message=<JSON>`, odpowiedz plain `OK` po sukcesie i odporniejsze scalanie JSON body z parametrami requestu.
**Dlaczego:**
- Publiczny POST webhooka zwracal 422 przez blad SQL `Unknown column 'o.buyer_phone'`, wiec odpowiedzi SMS nie byly zapisywane.
## 2026-05-12 - Phase 122 Plan 01: SMSPLANET Default SMS Footer
**Co zrobiono:**
- Dodano migracje `20260512_000111_smsplanet_default_footer.sql` z kolumna `smsplanet_integration_settings.default_footer`.
- Rozszerzono konfiguracje SMSPLANET o opcjonalna stopke SMS z limitem 300 znakow.
- Testowa wysylka oraz SMS z zamowienia dopinaja stopke przez pusta linie, waliduja finalna tresc w limicie 918 znakow i zapisuja finalne body w historii rozmowy.
**Dlaczego:**
- Operator ma utrzymywac jeden wspolny podpis firmy bez recznego kopiowania go do kazdej wiadomosci SMS.
**Weryfikacja:**
- Do uzupelnienia po APPLY.
## 2026-05-12 - Phase 121 Plan 01: SMSPLANET Conversation + Notifications
**Co zrobiono:**
- Dodano migracje `20260512_000110_smsplanet_conversation_notifications.sql` z tabelami `sms_messages`, `notifications` oraz polami `sender_mode` i `sender_phone`.
- Dodano backend `Sms` i `Notifications`, publiczny webhook SMSPLANET, zakladke SMS w zamowieniu, centrum powiadomien, topbar badge i polling JS.
- Usunieto tymczasowy override testowego nadawcy SMSPLANET; API uzywa wybranego trybu nadawcy.
- Poprawiono migracje po pierwszej probie na bazie: rzeczywiste `orders.id` ma typ `BIGINT UNSIGNED`, wiec `sms_messages.order_id` i `notifications.related_order_id` tez musza miec `BIGINT UNSIGNED`.
**Dlaczego:**
- Operator ma prowadzic dwukierunkowa rozmowe SMSPLANET w szczegolach zamowienia i widziec nowe odpowiedzi klientow globalnie.
**Weryfikacja:**
- `php -l` PASS dla nowych/zmienionych PHP.
- `npm run build:css` PASS.
- Migracja PASS przez techniczne polaczenie `DB_HOST_REMOTE`; manualne smoke testy UI/webhook nadal wymagaja sesji w aplikacji.
- `sonar-scanner` niedostepny w PATH.
## 2026-05-12 - Phase 120 Plan 01: Alert Component Unification
**Co zrobiono:**