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:
@@ -22,6 +22,9 @@ use App\Modules\Automation\AutomationService;
|
||||
use App\Modules\Settings\ShopproApiClient;
|
||||
use App\Modules\Settings\ShopproIntegrationsRepository;
|
||||
use App\Modules\Shipments\ShipmentPackageRepository;
|
||||
use App\Modules\Sms\SmsConversationService;
|
||||
use App\Modules\Sms\SmsMessageRepository;
|
||||
use Throwable;
|
||||
|
||||
final class OrdersController
|
||||
{
|
||||
@@ -41,7 +44,9 @@ final class OrdersController
|
||||
private readonly ?ShopproIntegrationsRepository $shopproIntegrations = null,
|
||||
private readonly ?AutomationService $automation = null,
|
||||
private readonly ?InvoiceRepository $invoiceRepo = null,
|
||||
private readonly ?InvoiceConfigRepository $invoiceConfigRepo = null
|
||||
private readonly ?InvoiceConfigRepository $invoiceConfigRepo = null,
|
||||
private readonly ?SmsMessageRepository $smsMessages = null,
|
||||
private readonly ?SmsConversationService $smsConversation = null
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -247,6 +252,9 @@ final class OrdersController
|
||||
$flashError = (string) Flash::get('order.error', '');
|
||||
|
||||
$customerRiskInfo = $this->buildCustomerRiskInfo($order, $orderId);
|
||||
$smsMessages = $this->smsMessages !== null ? $this->smsMessages->findByOrderId($orderId) : [];
|
||||
$smsPhone = $this->resolveSmsPhone($order, $addresses);
|
||||
$smsDefaultFooterConfigured = $this->smsConversation !== null && $this->smsConversation->hasDefaultFooter();
|
||||
|
||||
$html = $this->template->render('orders/show', [
|
||||
'title' => $this->translator->get('orders.details.title') . ' #' . $orderId,
|
||||
@@ -279,11 +287,50 @@ final class OrdersController
|
||||
'emailTemplates' => $emailTemplates,
|
||||
'emailMailboxes' => $emailMailboxes,
|
||||
'customerRiskInfo' => $customerRiskInfo,
|
||||
'smsMessages' => $smsMessages,
|
||||
'smsPhone' => $smsPhone,
|
||||
'smsDefaultFooterConfigured' => $smsDefaultFooterConfigured,
|
||||
], 'layouts/app');
|
||||
|
||||
return Response::html($html);
|
||||
}
|
||||
|
||||
public function sendSms(Request $request): Response
|
||||
{
|
||||
$orderId = max(0, (int) $request->input('id', 0));
|
||||
$redirectTo = '/orders/' . $orderId . '?tab=sms';
|
||||
if (!Csrf::validate((string) $request->input('_token', ''))) {
|
||||
Flash::set('order.error', $this->translator->get('auth.errors.csrf_expired'));
|
||||
return Response::redirect($redirectTo);
|
||||
}
|
||||
|
||||
if ($orderId <= 0 || $this->smsConversation === null) {
|
||||
Flash::set('order.error', 'Modul SMS nie jest dostepny.');
|
||||
return Response::redirect($redirectTo);
|
||||
}
|
||||
|
||||
try {
|
||||
$user = $this->auth->user();
|
||||
$userId = is_array($user) ? (int) ($user['id'] ?? 0) : 0;
|
||||
$result = $this->smsConversation->sendFromOrder(
|
||||
$orderId,
|
||||
(string) $request->input('phone', ''),
|
||||
(string) $request->input('message', ''),
|
||||
$userId > 0 ? $userId : null
|
||||
);
|
||||
|
||||
if ($result['ok']) {
|
||||
Flash::set('order.success', 'SMS zostal wyslany.');
|
||||
} else {
|
||||
Flash::set('order.error', 'Nie udalo sie wyslac SMS: ' . $result['message']);
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
Flash::set('order.error', 'Nie udalo sie wyslac SMS: ' . $exception->getMessage());
|
||||
}
|
||||
|
||||
return Response::redirect($redirectTo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sklada informacje o historii zwrotow klienta biezacego zamowienia.
|
||||
*
|
||||
@@ -315,6 +362,32 @@ final class OrdersController
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $order
|
||||
* @param array<int, array<string, mixed>> $addresses
|
||||
*/
|
||||
private function resolveSmsPhone(array $order, array $addresses): string
|
||||
{
|
||||
$buyerPhone = trim((string) ($order['buyer_phone'] ?? ''));
|
||||
if ($buyerPhone !== '') {
|
||||
return $buyerPhone;
|
||||
}
|
||||
|
||||
foreach (['customer', 'delivery', 'invoice'] as $wantedType) {
|
||||
foreach ($addresses as $address) {
|
||||
if ((string) ($address['address_type'] ?? '') !== $wantedType) {
|
||||
continue;
|
||||
}
|
||||
$phone = trim((string) ($address['phone'] ?? ''));
|
||||
if ($phone !== '') {
|
||||
return $phone;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function composeCustomerRiskText(int $count, string $email, string $phone, string $name): string
|
||||
{
|
||||
if ($count <= 0) {
|
||||
|
||||
Reference in New Issue
Block a user