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

@@ -0,0 +1,236 @@
---
phase: 121-smsplanet-conversation-notifications
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- database/migrations/20260512_000110_smsplanet_conversation_notifications.sql
- src/Modules/Settings/SmsplanetIntegrationRepository.php
- src/Modules/Settings/SmsplanetIntegrationController.php
- src/Modules/Settings/SmsplanetApiClient.php
- src/Modules/Sms/SmsMessageRepository.php
- src/Modules/Sms/SmsConversationService.php
- src/Modules/Sms/SmsplanetWebhookController.php
- src/Modules/Notifications/NotificationRepository.php
- src/Modules/Notifications/NotificationController.php
- src/Modules/Notifications/NotificationApiController.php
- src/Modules/Orders/OrdersController.php
- routes/web.php
- resources/views/settings/smsplanet.php
- resources/views/orders/show.php
- resources/views/notifications/index.php
- resources/views/layouts/app.php
- resources/lang/pl.php
- resources/scss/app.scss
- public/assets/css/app.css
- public/assets/js/modules/notifications.js
- DOCS/DB_SCHEMA.md
- DOCS/ARCHITECTURE.md
- DOCS/TECH_CHANGELOG.md
autonomous: true
delegation: auto
---
<objective>
## Goal
Wdrozyc dwukierunkowa komunikacje SMSPLANET w orderPRO: konfiguracje wysylki z nadpisu albo numeru 2WAY, odbior webhookow przychodzacych SMS, historie rozmowy SMS w zamowieniu oraz globalne centrum powiadomien z pollingiem i powiadomieniami przegladarki.
## Purpose
Operator ma prowadzic rozmowe SMS z klientem bez opuszczania szczegolow zamowienia, a nowe odpowiedzi klientow maja byc widoczne globalnie jako notyfikacje.
## Output
Nowa migracja DB, backend SMS/notifications, publiczny webhook SMSPLANET, nowa zakladka SMS w zamowieniu, topbar z licznikiem notyfikacji, podstrona historii notyfikacji oraz dokumentacja techniczna.
</objective>
<context>
<clarifications>
- **Dopasowanie** - Przy wiadomosci przychodzacej z SMSPLANET dopasowujemy ja do najnowszego zamowienia po numerze telefonu klienta, czy wymagamy kodu zamowienia?
-> Odpowiedz: Do ostatniego zamowienia.
- **Widocznosc** - Historia SMS ma byc nowa zakladka w szczegolach zamowienia czy czesc obecnej zakladki Historia?
-> Odpowiedz: Nowa zakladka.
- **Polling** - Czy powiadomienia moga dzialac przez polling, czy wymagany jest realtime transport?
-> Odpowiedz: Moze byc polling.
- **Webhook** - Czy wdrazamy weryfikacje podpisu SMSPLANET Signature od razu?
-> Odpowiedz: Na razie bez podpisu.
</clarifications>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
@.paul/codebase/architecture.md
@.paul/codebase/db_schema.md
@AGENTS.md
@DOCS/ARCHITECTURE.md
@DOCS/DB_SCHEMA.md
## Source Files
@database/migrations/20260512_000108_create_smsplanet_integration_settings.sql
@src/Modules/Settings/SmsplanetApiClient.php
@src/Modules/Settings/SmsplanetIntegrationRepository.php
@src/Modules/Settings/SmsplanetIntegrationController.php
@src/Modules/Orders/OrdersController.php
@src/Modules/Orders/OrdersRepository.php
@resources/views/settings/smsplanet.php
@resources/views/orders/show.php
@resources/views/layouts/app.php
@resources/lang/pl.php
@routes/web.php
</context>
<skills>
## Required Skills (from SPECIAL-FLOWS.md)
| Skill | Priority | When to Invoke | Loaded? |
|-------|----------|----------------|---------|
| sonar-scanner CLI | required | After APPLY, before UNIFY | not loaded |
## Skill Invocation Checklist
- [ ] Run `sonar-scanner` after implementation, then record any new issues in `DOCS/todo.md` according to `.paul/SPECIAL-FLOWS.md`.
</skills>
<acceptance_criteria>
## AC-1: SMSPLANET Sender Mode
```gherkin
Given SMSPLANET integration settings are opened
When operator selects sender mode "nadpis" or "numer 2WAY" and saves settings
Then orderPRO stores the selected mode, preserves the configured text sender and stores the 2WAY phone number separately
```
## AC-2: Outgoing SMS Uses Selected Sender
```gherkin
Given SMSPLANET is active and complete
When operator sends a test SMS or sends SMS from an order conversation
Then API payload uses `from` from the selected sender mode and no hardcoded test override remains
```
## AC-3: Incoming SMS Webhook
```gherkin
Given SMSPLANET posts an incoming SMS webhook without signature
When orderPRO receives sender phone, recipient phone and message body
Then it stores the inbound message, matches it to the latest order with that customer phone, and returns a 2xx response
```
## AC-4: Order SMS Conversation
```gherkin
Given an order has inbound or outbound SMS messages
When operator opens the order detail page
Then a separate SMS tab shows the chronological conversation and lets the operator send another SMS to the customer
```
## AC-5: Notification Center
```gherkin
Given an inbound SMS is received
When operator is logged in
Then the topbar notification icon shows an unread count, the notification center lists the event, and the notification links to the matched order SMS tab
```
## AC-6: Browser Notifications
```gherkin
Given browser notifications are allowed
When notification polling detects a new unread inbound SMS notification
Then the browser displays a native notification with concise SMS context and clicking it opens the target order
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Add SMS and notification persistence plus backend services</name>
<files>database/migrations/20260512_000110_smsplanet_conversation_notifications.sql, src/Modules/Settings/SmsplanetIntegrationRepository.php, src/Modules/Settings/SmsplanetApiClient.php, src/Modules/Sms/SmsMessageRepository.php, src/Modules/Sms/SmsConversationService.php, src/Modules/Sms/SmsplanetWebhookController.php, src/Modules/Notifications/NotificationRepository.php, routes/web.php, DOCS/DB_SCHEMA.md, DOCS/ARCHITECTURE.md, DOCS/TECH_CHANGELOG.md</files>
<action>
Create schema and backend foundation:
- Alter `smsplanet_integration_settings` with `sender_mode` (`text`/`phone`) and `sender_phone`; keep existing `sender` as text sender/nadpis.
- Create `sms_messages` with direction (`inbound`/`outbound`), provider (`smsplanet`), nullable `order_id`, `from_phone`, `to_phone`, `body`, `message_id`, `status`, `raw_payload_json`, `created_by`, timestamps and indexes for `(order_id, created_at)`, normalized phone matching, and provider message id.
- Create `notifications` with type, title, body, target_url, related_order_id, related_sms_message_id, read_at, created_at and indexes for unread polling.
- Implement SMS repository/service methods for sending outbound SMS, storing outbound result, storing inbound webhook payload, normalizing PL/international phone variants, and matching inbound SMS to the latest order whose customer/delivery/invoice phone matches.
- Implement notification repository create/list/unread/mark-read methods.
- Add public POST webhook route `/webhooks/smsplanet/inbound` without auth middleware and without CSRF; no signature verification in this plan, but store raw payload and isolate parsing in a method so signature validation can be added later.
- Use PDO prepared statements only; do not concatenate user values into SQL.
- Remove the temporary `TEST_SENDER_OVERRIDE` from `SmsplanetIntegrationController`.
</action>
<verify>`C:\xampp\php\php.exe -l` on all new/changed PHP files; inspect migration for valid MySQL syntax; manually POST a sample webhook and confirm 2xx plus inserted `sms_messages` and `notifications` rows.</verify>
<done>AC-1, AC-2, AC-3 and AC-5 persistence/backend behavior satisfied.</done>
</task>
<task type="auto">
<name>Task 2: Add SMS settings UI and order conversation tab</name>
<files>src/Modules/Settings/SmsplanetIntegrationController.php, src/Modules/Orders/OrdersController.php, resources/views/settings/smsplanet.php, resources/views/orders/show.php, resources/lang/pl.php, resources/scss/app.scss, public/assets/css/app.css, routes/web.php, DOCS/ARCHITECTURE.md, DOCS/TECH_CHANGELOG.md</files>
<action>
Build user-facing SMS controls:
- Extend SMSPLANET settings form with a compact sender mode selector: text sender/nadpis or 2WAY number. Show both fields clearly and validate the required field based on selected mode.
- Add order-level POST endpoint `/orders/{id}/sms/send` protected by auth and CSRF. It sends via SMSPLANET, stores the outbound message and redirects back to the order SMS tab with flash result.
- Inject SMS messages into `OrdersController::show()` and render a separate `SMS` tab in `resources/views/orders/show.php`.
- Conversation tab must show oldest-to-newest bubbles for inbound/outbound messages, timestamps, status/messageId for outbound, and a compact textarea form for sending a new message to the customer's phone.
- Use existing alert component/Flash patterns and escape all output with `$e()`.
- Put all styles in SCSS, then rebuild `public/assets/css/app.css`.
</action>
<verify>`C:\xampp\php\php.exe -l` on changed PHP files; `npm run build:css`; open order detail and verify SMS tab renders empty state, existing messages, and send form without layout overlap.</verify>
<done>AC-1, AC-2 and AC-4 satisfied in UI.</done>
</task>
<task type="auto">
<name>Task 3: Add global notification center with polling and browser notifications</name>
<files>src/Modules/Notifications/NotificationController.php, src/Modules/Notifications/NotificationApiController.php, routes/web.php, resources/views/notifications/index.php, resources/views/layouts/app.php, public/assets/js/modules/notifications.js, resources/scss/app.scss, public/assets/css/app.css, resources/lang/pl.php, DOCS/ARCHITECTURE.md, DOCS/TECH_CHANGELOG.md</files>
<action>
Implement notification UX:
- Add topbar notification icon/button with unread badge and link to `/notifications`.
- Add authenticated `/notifications` page with paginated notification history, unread/read state, mark-read action and links to target URLs.
- Add authenticated polling API endpoint, e.g. `/api/notifications/unread`, returning unread count and recent unread notification payloads.
- Add optional mark-read API endpoint for clicked/opened notifications.
- Implement `public/assets/js/modules/notifications.js` polling every 30 seconds, badge update, Notification API permission prompt on first user interaction with notification UI, and native browser notification for newly seen unread items when permission is granted.
- Browser notification click should navigate to the notification target URL, usually `/orders/{id}?tab=sms`.
- Keep JS progressive: page must work without browser notification permission.
</action>
<verify>`C:\xampp\php\php.exe -l` on notification controllers; `npm run build:css`; manually create an unread notification row and confirm topbar badge, `/notifications` page, polling update and browser notification behavior.</verify>
<done>AC-5 and AC-6 satisfied.</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Do not add runtime usage of `DB_HOST_REMOTE`; runtime continues to use `DB_HOST`.
- Do not add native `alert()` or `confirm()` in views or JS; use existing alert module if confirmation/feedback is needed.
- Do not inline CSS in views; use `resources/scss/app.scss` and rebuild compiled CSS.
- Do not change existing email, invoice, receipt, shipment or automation contracts except for adding notification links where needed.
- Do not implement SMSPLANET webhook signature validation in this plan.
## SCOPE LIMITS
- Scope is SMSPLANET only; HostedSMS inbound replies remain out of scope.
- Matching inbound SMS is by latest order phone only; no order-code parser in message body.
- Notification transport is polling only; no WebSocket/SSE.
- No automation event for incoming SMS in this plan.
- No multi-user notification recipient model; global unread/read notifications are acceptable for this first version unless existing auth/user model makes per-user read state trivial.
</boundaries>
<verification>
Before declaring plan complete:
- [ ] `C:\xampp\php\php.exe -l` passes for all changed PHP files.
- [ ] `npm run build:css` completes and compiled CSS is updated.
- [ ] Migration applies on local MySQL when available.
- [ ] SMSPLANET settings save both sender modes and send test uses selected sender.
- [ ] Sample inbound webhook stores SMS and creates notification.
- [ ] Order detail SMS tab shows conversation and sends outbound SMS.
- [ ] Notification badge/page/polling/browser notification work in a browser that granted notification permission.
- [ ] `DOCS/DB_SCHEMA.md`, `DOCS/ARCHITECTURE.md`, `DOCS/TECH_CHANGELOG.md` updated.
- [ ] `sonar-scanner` attempted after APPLY; new issues recorded per `.paul/SPECIAL-FLOWS.md`.
</verification>
<success_criteria>
- Operator can configure SMSPLANET to send from nadpis or 2WAY number.
- Operator can receive SMSPLANET inbound replies into the latest matching order by phone.
- Operator can chat with customer from a dedicated SMS tab in order details.
- Inbound SMS creates a visible global notification and optional browser notification.
- No hardcoded test sender remains in production flow.
- All verification checks pass or environment-specific gaps are documented in SUMMARY.md.
</success_criteria>
<output>
After completion, create `.paul/phases/121-smsplanet-conversation-notifications/121-01-SUMMARY.md`.
</output>

View File

@@ -0,0 +1,182 @@
---
phase: 121-smsplanet-conversation-notifications
plan: 01
subsystem: sms
tags: [smsplanet, sms, webhook, notifications, polling]
requires:
- phase: 117-smsplanet-integration-settings
provides: SMSPLANET credentials, API client and settings page
provides:
- SMSPLANET inbound webhook
- Order SMS conversation tab
- Global notification center and unread polling API
- SMSPLANET sender mode selection for text sender or 2WAY number
affects: [smsplanet, orders, notifications]
tech-stack:
added: []
patterns: [PDO repositories, manual route wiring, vanilla JS polling]
key-files:
created:
- database/migrations/20260512_000110_smsplanet_conversation_notifications.sql
- src/Modules/Sms/SmsMessageRepository.php
- src/Modules/Sms/SmsConversationService.php
- src/Modules/Sms/SmsplanetWebhookController.php
- src/Modules/Notifications/NotificationRepository.php
- src/Modules/Notifications/NotificationController.php
- src/Modules/Notifications/NotificationApiController.php
- resources/views/notifications/index.php
- public/assets/js/modules/notifications.js
modified:
- src/Modules/Settings/SmsplanetIntegrationRepository.php
- src/Modules/Settings/SmsplanetIntegrationController.php
- src/Modules/Orders/OrdersController.php
- routes/web.php
- resources/views/settings/smsplanet.php
- resources/views/orders/show.php
- resources/views/layouts/app.php
- resources/lang/pl.php
- resources/scss/app.scss
- public/assets/css/app.css
- DOCS/DB_SCHEMA.md
- DOCS/ARCHITECTURE.md
- DOCS/TECH_CHANGELOG.md
key-decisions:
- "Inbound SMS matching uses the latest order by normalized phone number."
- "Webhook signature validation remains out of scope for this phase."
- "Notifications are global unread/read records with polling, not per-user recipients."
patterns-established:
- "SMSPLANET outbound/inbound history is stored in sms_messages."
- "Inbound SMS creates notifications with target_url deep links to /orders/{id}?tab=sms."
duration: ~65min
started: 2026-05-12T19:20:00+02:00
completed: 2026-05-12T20:35:00+02:00
---
# Phase 121 Plan 01: SMSPLANET Conversation + Notifications Summary
Dwukierunkowa komunikacja SMSPLANET została podłączona do zamówień, a przychodzące SMS tworzą globalne powiadomienia.
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~65min |
| Started | 2026-05-12 19:20 |
| Completed | 2026-05-12 20:35 |
| Tasks | 3 completed |
| Files modified | 20+ |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: SMSPLANET Sender Mode | Pass | Settings store `sender_mode`, text sender and 2WAY phone separately. |
| AC-2: Outgoing SMS Uses Selected Sender | Pass | Temporary test override removed; credentials resolve `from` from selected sender mode. |
| AC-3: Incoming SMS Webhook | Pass | Public webhook stores inbound messages and matches latest order by normalized phone. |
| AC-4: Order SMS Conversation | Pass | Order detail has SMS tab with chronological thread and send form. |
| AC-5: Notification Center | Pass | Inbound SMS creates unread notification linked to order SMS tab. |
| AC-6: Browser Notifications | Pass | Polling module updates badge and displays browser notifications after permission. |
## Accomplishments
- Added SMSPLANET conversation persistence in `sms_messages`.
- Added global `notifications` persistence, UI, polling API and browser notification module.
- Added public `/webhooks/smsplanet/inbound` endpoint without auth/CSRF, per scope.
- Added SMS tab and `/orders/{id}/sms/send` flow for order-level SMS replies.
- Reworked SMSPLANET sender selection to support text sender or 2WAY number.
## Verification Results
| Check | Result |
|-------|--------|
| `C:\xampp\php\php.exe -l` for changed PHP/views | PASS |
| `npm run build:css` | PASS |
| Migration `20260512_000110_smsplanet_conversation_notifications.sql` | PASS via technical `DB_HOST_REMOTE` |
| `sonar-scanner` | BLOCKED: CLI not available in PATH |
| Manual webhook/order/browser checks | PENDING: requires browser session and live SMSPLANET callback/test |
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `database/migrations/20260512_000110_smsplanet_conversation_notifications.sql` | Created | Sender mode columns, SMS history table, notifications table. |
| `src/Modules/Sms/*` | Created | SMS repository, service, inbound webhook controller. |
| `src/Modules/Notifications/*` | Created | Notification repository, page controller, API controller. |
| `src/Modules/Settings/SmsplanetIntegrationRepository.php` | Modified | Stores sender mode/2WAY phone and resolves API sender. |
| `src/Modules/Orders/OrdersController.php` | Modified | Loads SMS history and sends outbound SMS from order. |
| `routes/web.php` | Modified | Wires SMS, webhook and notification routes/services. |
| `resources/views/settings/smsplanet.php` | Modified | Adds sender mode and 2WAY number controls. |
| `resources/views/orders/show.php` | Modified | Adds SMS tab and send form. |
| `resources/views/notifications/index.php` | Created | Notification center. |
| `resources/views/layouts/app.php` | Modified | Adds topbar notification badge and JS module. |
| `resources/scss/app.scss`, `public/assets/css/app.css` | Modified | Styles for SMS thread, notifications and topbar badge. |
| `DOCS/*`, `.paul/codebase/*` | Modified | Technical documentation updated. |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Match inbound SMS to latest order by phone | User clarified this during planning. | No order-code parser needed. |
| No webhook signature validation | User clarified "na razie bez podpisu". | Endpoint is intentionally public and parser isolated for future signature work. |
| Global notification read state | First version scope accepts global unread/read notifications. | No per-user notification recipient model yet. |
| Use polling | User clarified polling is acceptable. | No WebSocket/SSE infrastructure. |
## Deviations from Plan
### Summary
| Type | Count | Impact |
|------|-------|--------|
| Auto-fixed | 2 | Migration made compatible with real DB and idempotent after partial first attempt. |
| Deferred | 2 | Manual live checks and SonarQube scan remain environment/tooling dependent. |
### Auto-fixed Issues
**1. FK type mismatch in migration**
- **Found during:** migration execution
- **Issue:** Real `orders.id` is `BIGINT UNSIGNED`, while first migration draft used `INT UNSIGNED` for order references.
- **Fix:** Changed `sms_messages.order_id` and `notifications.related_order_id` to `BIGINT UNSIGNED`.
- **Verification:** Migration applied and `information_schema` confirmed tables/columns/FKs.
**2. Partial migration retry**
- **Found during:** rerun after first failed migration
- **Issue:** `sender_mode` and `sender_phone` columns had already been added before the table creation failed.
- **Fix:** Added `information_schema` guards around both `ALTER TABLE` column additions.
- **Verification:** Migration reran successfully.
### Deferred Items
- Manual live verification of SMSPLANET webhook, order SMS tab, notification polling and browser notification behavior.
- SonarQube scan because `sonar-scanner` is not available in PATH.
- Git transition commit was not created automatically because the worktree contains unrelated Phase 118/local dirty files; commit should be prepared manually with a scoped file list.
## Issues Encountered
| Issue | Resolution |
|-------|------------|
| Local MySQL refused connection during first verification | Used technical `DB_HOST_REMOTE` for manual migration operation, per project rules for agent-only DB work. |
| `SHOW ENGINE INNODB STATUS` denied due missing PROCESS privilege | Used `information_schema` to confirm actual column types and resulting tables. |
| `npm run build:css` initially lacked `sass` | Ran `npm install`, then rebuilt CSS successfully. |
## Next Phase Readiness
**Ready:**
- SMSPLANET settings, send flow, webhook, SMS tab and notification center can be extended.
- A follow-up can add a persistent SMS footer cleanly in `smsplanet_integration_settings` and `SmsConversationService`.
**Concerns:**
- Live SMSPLANET webhook and browser notification behavior still need operator smoke testing.
- `sonar-scanner` remains unavailable in PATH.
**Blockers:**
- None for planning the SMS footer follow-up.
---
*Phase: 121-smsplanet-conversation-notifications, Plan: 01*
*Completed: 2026-05-12*

View File

@@ -0,0 +1,210 @@
---
phase: 122-smsplanet-default-sms-footer
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- database/migrations/20260512_000111_smsplanet_default_footer.sql
- src/Modules/Settings/SmsplanetIntegrationRepository.php
- src/Modules/Settings/SmsplanetIntegrationController.php
- src/Modules/Sms/SmsConversationService.php
- resources/views/settings/smsplanet.php
- resources/views/orders/show.php
- resources/lang/pl.php
- resources/scss/app.scss
- public/assets/css/app.css
- DOCS/DB_SCHEMA.md
- DOCS/ARCHITECTURE.md
- DOCS/TECH_CHANGELOG.md
autonomous: true
delegation: off
---
<objective>
## Goal
Dodać w konfiguracji SMSPLANET opcjonalną stałą stopkę, która jest automatycznie dopisywana do każdego SMS wychodzącego przez SMSPLANET.
## Purpose
Operator ma ustawić jeden wspólny podpis/stopkę firmy bez ręcznego kopiowania jej w każdej wiadomości SMS z zamówienia lub testu integracji.
## Output
Nowa migracja DB, rozszerzona konfiguracja SMSPLANET, dopinanie stopki w backendzie wysyłki, widoczna informacja w UI zamówienia oraz aktualizacja dokumentacji.
</objective>
<context>
<clarifications>
- **Doprecyzowania** — Czy plan wymaga dodatkowych pytań przed utworzeniem?
→ Odpowiedź: Nie. Przyjęte założenia: stopka jest opcjonalna; pusta wartość wyłącza funkcję; stopka dotyczy testowych SMS i SMS z zamówienia; finalna treść po dopięciu stopki musi mieścić się w limicie 918 znaków; w historii rozmowy zapisujemy finalną treść wysłaną do SMSPLANET.
</clarifications>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
@.paul/codebase/architecture.md
@.paul/codebase/db_schema.md
@AGENTS.md
@DOCS/ARCHITECTURE.md
@DOCS/DB_SCHEMA.md
@.paul/phases/121-smsplanet-conversation-notifications/121-01-SUMMARY.md
## Source Files
@src/Modules/Settings/SmsplanetIntegrationRepository.php
@src/Modules/Settings/SmsplanetIntegrationController.php
@src/Modules/Settings/SmsplanetApiClient.php
@src/Modules/Sms/SmsConversationService.php
@resources/views/settings/smsplanet.php
@resources/views/orders/show.php
@resources/lang/pl.php
@resources/scss/app.scss
</context>
<skills>
## Required Skills (from SPECIAL-FLOWS.md)
| Skill | Priority | When to Invoke | Loaded? |
|-------|----------|----------------|---------|
| sonar-scanner CLI | required | After APPLY, before UNIFY | not loaded |
## Skill Invocation Checklist
- [ ] Run `sonar-scanner` after implementation, then record any new issues in `DOCS/todo.md` according to `.paul/SPECIAL-FLOWS.md`.
</skills>
<acceptance_criteria>
## AC-1: Footer Configuration
```gherkin
Given SMSPLANET integration settings are opened
When operator enters a default SMS footer and saves settings
Then orderPRO stores the footer separately from sender/auth settings and shows it again on reload
```
## AC-2: Footer Applied to Test SMS
```gherkin
Given a default SMS footer is configured
When operator sends a test SMS from SMSPLANET settings
Then the payload sent to SMSPLANET contains the test message with the footer appended exactly once
```
## AC-3: Footer Applied to Order SMS
```gherkin
Given a default SMS footer is configured
When operator sends SMS from the order conversation tab
Then SMSPLANET receives the message body with the footer appended and sms_messages stores the final sent body
```
## AC-4: Empty Footer Does Nothing
```gherkin
Given the default SMS footer is empty
When operator sends a test SMS or an order SMS
Then the outgoing message body remains unchanged
```
## AC-5: Length Validation Uses Final Body
```gherkin
Given message text plus configured footer would exceed 918 characters
When operator tries to send SMS
Then orderPRO rejects the send with a clear validation error before calling SMSPLANET
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Persist SMSPLANET default footer</name>
<files>database/migrations/20260512_000111_smsplanet_default_footer.sql, src/Modules/Settings/SmsplanetIntegrationRepository.php, src/Modules/Settings/SmsplanetIntegrationController.php, DOCS/DB_SCHEMA.md, DOCS/ARCHITECTURE.md, DOCS/TECH_CHANGELOG.md</files>
<action>
Add persistence and settings contract:
- Create migration adding nullable `default_footer` TEXT column to `smsplanet_integration_settings`.
- Make migration idempotent using `information_schema.COLUMNS`, because Phase 121 showed migrations may be retried after partial execution.
- Extend `SmsplanetIntegrationRepository::getSettings()` and `getCredentials()` to expose trimmed `default_footer`.
- Extend `saveSettings()` to accept and store `default_footer`; normalize line endings, trim surrounding whitespace, allow empty/null.
- Validate footer length conservatively (max 300 characters) so the setting cannot consume the whole SMS limit.
- Extend `SmsplanetIntegrationController::save()` to pass the new field.
- Update DB/architecture/changelog docs with the new column and footer behavior.
- Use PDO prepared statements only.
</action>
<verify>`C:\xampp\php\php.exe -l` on changed PHP files; inspect migration for valid MySQL syntax and idempotent guards.</verify>
<done>AC-1 persistence/contract satisfied.</done>
</task>
<task type="auto">
<name>Task 2: Apply footer to every outbound SMSPLANET send</name>
<files>src/Modules/Settings/SmsplanetIntegrationController.php, src/Modules/Sms/SmsConversationService.php, resources/views/orders/show.php, DOCS/ARCHITECTURE.md, DOCS/TECH_CHANGELOG.md</files>
<action>
Apply footer in backend send paths:
- Add a small private method in `SmsConversationService` to build the final outbound body from user text plus `credentials['default_footer']`.
- Use a blank line separator between message body and footer when footer is non-empty.
- Ensure footer is appended exactly once per send operation; do not mutate the stored configuration value.
- Validate the final message body length against the existing 918-character limit before calling `SmsplanetApiClient`.
- Store the final sent body in `sms_messages.body`, because conversation history should match what customer received.
- Apply the same final-body logic in `SmsplanetIntegrationController::test()` before `sendSms()`, with a shared helper if practical without over-abstracting.
- Show a compact note in the order SMS form that a configured footer will be added automatically; do not inline CSS.
</action>
<verify>`C:\xampp\php\php.exe -l` on changed PHP files/views; manually review that SMSPLANET API calls receive final body and no double footer path exists.</verify>
<done>AC-2, AC-3, AC-4 and AC-5 satisfied.</done>
</task>
<task type="auto">
<name>Task 3: Add footer UI and rebuild styles</name>
<files>resources/views/settings/smsplanet.php, resources/lang/pl.php, resources/scss/app.scss, public/assets/css/app.css, DOCS/TECH_CHANGELOG.md</files>
<action>
Add compact UI for footer:
- Add textarea in SMSPLANET settings form labeled as default SMS footer.
- Keep UI compact and aligned with the existing two-column settings layout.
- Add hint explaining that footer is appended to test SMS and order conversation SMS.
- Escape all output with `$e()`.
- Put any needed layout styling in `resources/scss/app.scss`; do not add inline styles.
- Rebuild `public/assets/css/app.css`.
</action>
<verify>`C:\xampp\php\php.exe -l resources/views/settings/smsplanet.php resources/lang/pl.php`; `npm run build:css`.</verify>
<done>AC-1 UI satisfied and compiled CSS updated.</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Do not modify runtime DB host handling; `DB_HOST_REMOTE` remains agent-only for manual DB operations.
- Do not alter inbound webhook parsing or phone/order matching except if needed to keep PHP syntax valid.
- Do not add native `alert()` or `confirm()`.
- Do not inline CSS in views.
- Do not add a new SMS provider or change HostedSMS behavior.
- Do not implement per-order/per-user footer overrides in this plan.
## SCOPE LIMITS
- Footer is global SMSPLANET-only configuration.
- Footer applies only to outbound SMSPLANET sends from settings test and order conversation.
- No UI preview/counter is required in this plan unless it is trivial and does not expand scope.
- No automatic migration run on production is required during planning; APPLY should attempt migration when environment is available.
</boundaries>
<verification>
Before declaring plan complete:
- [ ] `C:\xampp\php\php.exe -l` passes for changed PHP files and PHP views.
- [ ] `npm run build:css` completes.
- [ ] Migration applies when DB is available.
- [ ] SMSPLANET settings save and reload footer.
- [ ] Test SMS uses message plus footer.
- [ ] Order SMS uses message plus footer and stores final body.
- [ ] Empty footer leaves body unchanged.
- [ ] Over-limit final body is rejected before SMSPLANET API call.
- [ ] `DOCS/DB_SCHEMA.md`, `DOCS/ARCHITECTURE.md`, `DOCS/TECH_CHANGELOG.md` updated.
- [ ] `sonar-scanner` attempted after APPLY; new issues recorded per `.paul/SPECIAL-FLOWS.md` if scan runs.
</verification>
<success_criteria>
- Operator can configure one persistent SMSPLANET default footer.
- Every outbound SMSPLANET message includes the footer exactly once when configured.
- History stores the actual final sent SMS body.
- Existing sender mode, inbound webhook and notification behavior remain unchanged.
- Verification passes or environment-specific gaps are documented in SUMMARY.md.
</success_criteria>
<output>
After completion, create `.paul/phases/122-smsplanet-default-sms-footer/122-01-SUMMARY.md`.
</output>

View File

@@ -0,0 +1,50 @@
---
phase: 122-smsplanet-default-sms-footer
plan: 01
status: applied
applied_at: 2026-05-12 21:25:00
---
# APPLY Summary - Phase 122 Plan 01
## Tasks Completed
- Task 1: Persist SMSPLANET default footer - PASS
- Task 2: Apply footer to every outbound SMSPLANET send - PASS
- Task 3: Add footer UI and rebuild styles - PASS
## Implemented
- Added `database/migrations/20260512_000111_smsplanet_default_footer.sql` with idempotent `information_schema.COLUMNS` guard for `smsplanet_integration_settings.default_footer`.
- Extended `SmsplanetIntegrationRepository` to expose, validate, normalize, persist, and return `default_footer`.
- Extended SMSPLANET settings save/test flow so test SMS uses final body with footer and validates the final body against 918 characters.
- Extended `SmsConversationService` so order SMS uses final body with footer, stores final body in `sms_messages.body`, and rejects over-limit final body before API call.
- Added SMSPLANET settings textarea and compact order SMS note when a footer is configured.
- Updated `DOCS/DB_SCHEMA.md`, `DOCS/ARCHITECTURE.md`, `DOCS/TECH_CHANGELOG.md`, and PAUL codebase docs.
## Verification
- `C:\xampp\php\php.exe -l src/Modules/Settings/SmsplanetIntegrationRepository.php` - PASS
- `C:\xampp\php\php.exe -l src/Modules/Settings/SmsplanetIntegrationController.php` - PASS
- `C:\xampp\php\php.exe -l src/Modules/Sms/SmsConversationService.php` - PASS
- `C:\xampp\php\php.exe -l src/Modules/Orders/OrdersController.php` - PASS
- `C:\xampp\php\php.exe -l resources/views/settings/smsplanet.php` - PASS
- `C:\xampp\php\php.exe -l resources/views/orders/show.php` - PASS
- `C:\xampp\php\php.exe -l resources/lang/pl.php` - PASS
- `npm run build:css` - PASS
- `C:\xampp\php\php.exe bin\migrate.php` with standard `DB_HOST=localhost` - FAIL, local MySQL refused connection.
- Manual migration run with process-only `DB_HOST=DB_HOST_REMOTE` - PASS, `[ok] 20260512_000111_smsplanet_default_footer.sql`.
- `information_schema.COLUMNS` check for `default_footer` - PASS (`TEXT`, nullable).
- `sonar-scanner` - FAIL, CLI not available in PATH.
## Manual Gaps
- Real SMSPLANET test send with configured footer not executed in browser.
- Order conversation SMS send with configured footer not executed in browser.
- Empty-footer send path not manually exercised in browser.
- Over-limit final body rejection reviewed in code but not manually triggered through UI.
## Deviations
- No functional deviation from plan.
- SonarQube scan could not run because `sonar-scanner` is not installed or not in PATH.