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:
@@ -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:** 62 | **Engine:** InnoDB | **Charset:** utf8mb4_unicode_ci
|
||||
|
||||
---
|
||||
|
||||
@@ -243,7 +243,7 @@ UNIQUE: `(integration_id, external_product_id, external_variant_id)`
|
||||
**orders** — Imported orders from sales channels
|
||||
| Column | Type | Nullable | Notes |
|
||||
|--------|------|----------|-------|
|
||||
| `id` | INT UNSIGNED | NO | PK |
|
||||
| `id` | BIGINT UNSIGNED | NO | PK |
|
||||
| `internal_order_number` | VARCHAR(11) | YES | UNIQUE, auto-assigned |
|
||||
| `integration_id` | INT UNSIGNED | NO | FK → integrations(id) CASCADE |
|
||||
| `external_order_id` | VARCHAR(64) | NO | |
|
||||
@@ -558,6 +558,23 @@ UNIQUE: `(type, name)`
|
||||
|
||||
---
|
||||
|
||||
**fakturownia_integration_settings** - Fakturownia account credentials (Phase 118; fixed 1 row)
|
||||
| Column | Type | Nullable | Notes |
|
||||
|--------|------|----------|-------|
|
||||
| `id` | INT UNSIGNED | NO | PK, always 1 after `20260512_000109_fakturownia_single_instance.sql` |
|
||||
| `integration_id` | INT UNSIGNED | NO | UNIQUE, FK -> integrations(id) CASCADE; single `integrations.type='fakturownia'` row |
|
||||
| `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 |
|
||||
| `default_kind` | VARCHAR(32) | NO | DEFAULT `vat` |
|
||||
| `default_payment_to_days` | TINYINT UNSIGNED | NO | DEFAULT 7 |
|
||||
| `created_at` | DATETIME | NO | |
|
||||
| `updated_at` | DATETIME | NO | |
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
**hostedsms_integration_settings** - HostedSMS account credentials (Phase 116; fixed 1 row)
|
||||
| Column | Type | Nullable | Notes |
|
||||
|--------|------|----------|-------|
|
||||
@@ -583,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 | |
|
||||
|
||||
@@ -593,6 +613,44 @@ UNIQUE: `(integration_id)` - one global SMSPLANET settings row.
|
||||
|
||||
---
|
||||
|
||||
**sms_messages** - SMSPLANET inbound/outbound conversation history (Phase 121)
|
||||
| Column | Type | Nullable | Notes |
|
||||
|--------|------|----------|-------|
|
||||
| `id` | BIGINT UNSIGNED | NO | PK |
|
||||
| `direction` | VARCHAR(16) | NO | `inbound` or `outbound` |
|
||||
| `provider` | VARCHAR(32) | NO | DEFAULT `smsplanet` |
|
||||
| `order_id` | BIGINT UNSIGNED | YES | FK -> orders(id) SET NULL |
|
||||
| `from_phone` | VARCHAR(64) | NO | Original sender value |
|
||||
| `from_phone_normalized` | VARCHAR(32) | NO | Digits-only sender for matching/indexes |
|
||||
| `to_phone` | VARCHAR(64) | NO | Original recipient value |
|
||||
| `to_phone_normalized` | VARCHAR(32) | NO | Digits-only recipient for matching/indexes |
|
||||
| `body` | TEXT | NO | SMS body |
|
||||
| `message_id` | VARCHAR(128) | YES | Provider message id |
|
||||
| `status` | VARCHAR(32) | NO | `received`, `sent`, `failed` |
|
||||
| `raw_payload_json` | JSON | YES | Webhook payload or send result snapshot |
|
||||
| `created_by` | INT UNSIGNED | YES | FK -> users(id) SET NULL |
|
||||
| `created_at` | DATETIME | NO | |
|
||||
| `updated_at` | DATETIME | NO | |
|
||||
|
||||
Indexes: `sms_messages_order_created_idx`, `sms_messages_from_normalized_idx`, `sms_messages_to_normalized_idx`, `sms_messages_provider_message_idx`
|
||||
|
||||
**notifications** - Global operator notification center (Phase 121)
|
||||
| Column | Type | Nullable | Notes |
|
||||
|--------|------|----------|-------|
|
||||
| `id` | BIGINT UNSIGNED | NO | PK |
|
||||
| `type` | VARCHAR(64) | NO | e.g. `sms_inbound` |
|
||||
| `title` | VARCHAR(190) | NO | |
|
||||
| `body` | VARCHAR(500) | NO | Concise notification text |
|
||||
| `target_url` | VARCHAR(500) | YES | Deep link, usually `/orders/{id}?tab=sms` |
|
||||
| `related_order_id` | BIGINT UNSIGNED | YES | FK -> orders(id) SET NULL |
|
||||
| `related_sms_message_id` | BIGINT UNSIGNED | YES | FK -> sms_messages(id) SET NULL |
|
||||
| `read_at` | DATETIME | YES | NULL means unread |
|
||||
| `created_at` | DATETIME | NO | |
|
||||
|
||||
Indexes: `notifications_unread_created_idx`, `notifications_order_idx`, `notifications_sms_message_idx`
|
||||
|
||||
---
|
||||
|
||||
## Accounting / Receipts
|
||||
|
||||
**receipt_configs** — Receipt generation configurations
|
||||
@@ -640,6 +698,60 @@ UNIQUE: `(config_id, year, month)`
|
||||
|
||||
---
|
||||
|
||||
## Invoices
|
||||
|
||||
**invoice_configs** - Invoice generation configurations
|
||||
| Column | Type | Nullable | Notes |
|
||||
|--------|------|----------|-------|
|
||||
| `id` | BIGINT UNSIGNED | NO | PK |
|
||||
| `name` | VARCHAR(128) | NO | |
|
||||
| `integration_id` | INT UNSIGNED | YES | FK -> integrations(id) SET NULL; delegated configs always point to the single global Fakturownia row |
|
||||
| `is_delegated` | TINYINT(1) | NO | DEFAULT 0 |
|
||||
| `is_active` | TINYINT(1) | NO | DEFAULT 1 |
|
||||
| `number_format` | VARCHAR(64) | NO | DEFAULT `FV/%N/%M/%Y` |
|
||||
| `numbering_type` | ENUM('monthly','yearly') | NO | DEFAULT `monthly` |
|
||||
| `sale_date_source` | ENUM('order_date','payment_date','issue_date') | NO | DEFAULT `issue_date` |
|
||||
| `order_reference` | ENUM('none','orderpro','integration') | NO | DEFAULT `none` |
|
||||
| `payment_to_days` | TINYINT UNSIGNED | NO | DEFAULT 7 |
|
||||
| `default_kind` | VARCHAR(32) | NO | DEFAULT `vat` |
|
||||
| `created_at` | DATETIME | NO | |
|
||||
| `updated_at` | DATETIME | NO | |
|
||||
|
||||
**invoices** - Generated invoices
|
||||
| Column | Type | Nullable | Notes |
|
||||
|--------|------|----------|-------|
|
||||
| `id` | BIGINT UNSIGNED | NO | PK |
|
||||
| `order_id` | BIGINT UNSIGNED | NO | FK -> orders(id) CASCADE |
|
||||
| `config_id` | INT UNSIGNED | NO | FK -> invoice_configs(id) RESTRICT |
|
||||
| `invoice_number` | VARCHAR(64) | NO | UNIQUE |
|
||||
| `issue_date` | DATETIME | NO | |
|
||||
| `sale_date` | DATETIME | NO | |
|
||||
| `payment_due_date` | DATETIME | YES | |
|
||||
| `seller_data_json` | JSON | NO | Snapshot of company data at issue time |
|
||||
| `buyer_data_json` | JSON | YES | |
|
||||
| `items_json` | JSON | NO | |
|
||||
| `total_net` | DECIMAL(12,2) | NO | DEFAULT 0.00 |
|
||||
| `total_gross` | DECIMAL(12,2) | NO | DEFAULT 0.00 |
|
||||
| `order_reference_value` | VARCHAR(128) | YES | |
|
||||
| `external_invoice_id` | VARCHAR(128) | YES | Fakturownia invoice id for delegated invoices |
|
||||
| `external_pdf_url` | VARCHAR(500) | YES | Fakturownia PDF URL for delegated invoices |
|
||||
| `kind` | VARCHAR(32) | NO | DEFAULT `vat` |
|
||||
| `created_by` | INT UNSIGNED | YES | |
|
||||
| `created_at` | DATETIME | NO | |
|
||||
|
||||
**invoice_number_counters** - Sequential numbering per config/period
|
||||
| Column | Type | Nullable | Notes |
|
||||
|--------|------|----------|-------|
|
||||
| `id` | INT UNSIGNED | NO | PK |
|
||||
| `config_id` | INT UNSIGNED | NO | FK -> invoice_configs(id) CASCADE |
|
||||
| `year` | SMALLINT UNSIGNED | NO | |
|
||||
| `month` | TINYINT UNSIGNED | YES | NULL for yearly numbering |
|
||||
| `last_number` | INT UNSIGNED | NO | DEFAULT 0 |
|
||||
|
||||
UNIQUE: `(config_id, year, month)`
|
||||
|
||||
---
|
||||
|
||||
## Email
|
||||
|
||||
**email_mailboxes** — SMTP mailbox configurations
|
||||
|
||||
Reference in New Issue
Block a user