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:
@@ -4,14 +4,18 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Modules\Email;
|
||||
|
||||
use App\Modules\Shipments\DeliveryStatus;
|
||||
use App\Modules\Shipments\ShipmentPackageRepository;
|
||||
use App\Modules\Sms\SmsVariableResolver;
|
||||
|
||||
final class VariableResolver
|
||||
{
|
||||
private readonly SmsVariableResolver $inner;
|
||||
|
||||
public function __construct(
|
||||
private readonly ShipmentPackageRepository $shipmentPackageRepository
|
||||
ShipmentPackageRepository $shipmentPackageRepository,
|
||||
?SmsVariableResolver $inner = null
|
||||
) {
|
||||
$this->inner = $inner ?? new SmsVariableResolver($shipmentPackageRepository);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -22,95 +26,14 @@ final class VariableResolver
|
||||
*/
|
||||
public function buildVariableMap(array $order, array $addresses, array $companySettings): array
|
||||
{
|
||||
$customerAddress = $this->findAddress($addresses, 'customer');
|
||||
$deliveryAddress = $this->findAddress($addresses, 'delivery') ?? $customerAddress;
|
||||
|
||||
$buyerName = (string) ($customerAddress['name'] ?? '');
|
||||
$buyerEmail = (string) ($customerAddress['email'] ?? '');
|
||||
$buyerPhone = (string) ($customerAddress['phone'] ?? '');
|
||||
|
||||
$totalFormatted = number_format((float) ($order['total_with_tax'] ?? 0), 2, ',', ' ');
|
||||
$orderedAt = (string) ($order['ordered_at'] ?? '');
|
||||
if ($orderedAt !== '' && ($ts = strtotime($orderedAt)) !== false) {
|
||||
$orderedAt = date('Y-m-d', $ts);
|
||||
}
|
||||
|
||||
$baseVariables = [
|
||||
'zamowienie.numer' => (string) ($order['internal_order_number'] ?? $order['id'] ?? ''),
|
||||
'zamowienie.numer_zewnetrzny' => (string) ($order['external_order_id'] ?? $order['source_order_id'] ?? ''),
|
||||
'zamowienie.zrodlo' => ucfirst((string) ($order['source'] ?? '')),
|
||||
'zamowienie.kwota' => $totalFormatted,
|
||||
'zamowienie.waluta' => (string) ($order['currency'] ?? 'PLN'),
|
||||
'zamowienie.data' => $orderedAt,
|
||||
'kupujacy.imie_nazwisko' => $buyerName,
|
||||
'kupujacy.email' => $buyerEmail,
|
||||
'kupujacy.telefon' => $buyerPhone,
|
||||
'kupujacy.login' => (string) ($order['customer_login'] ?? ''),
|
||||
'adres.ulica' => trim(($deliveryAddress['street_name'] ?? '') . ' ' . ($deliveryAddress['street_number'] ?? '')),
|
||||
'adres.miasto' => (string) ($deliveryAddress['city'] ?? ''),
|
||||
'adres.kod_pocztowy' => (string) ($deliveryAddress['zip_code'] ?? ''),
|
||||
'adres.kraj' => (string) ($deliveryAddress['country'] ?? ''),
|
||||
'firma.nazwa' => (string) ($companySettings['company_name'] ?? ''),
|
||||
'firma.nip' => (string) ($companySettings['tax_number'] ?? ''),
|
||||
];
|
||||
|
||||
return $baseVariables + $this->resolveShipmentVariables($order);
|
||||
return $this->inner->buildVariableMap($order, $addresses, $companySettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $variableMap
|
||||
*/
|
||||
public function resolve(string $template, array $variableMap): string
|
||||
{
|
||||
return preg_replace_callback(
|
||||
'/\{\{([a-z_]+\.[a-z_]+)\}\}/',
|
||||
static fn(array $m): string => $variableMap[$m[1]] ?? '',
|
||||
$template
|
||||
) ?? $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $addresses
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
private function findAddress(array $addresses, string $type): ?array
|
||||
{
|
||||
foreach ($addresses as $addr) {
|
||||
if (($addr['address_type'] ?? '') === $type) {
|
||||
return $addr;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $order
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function resolveShipmentVariables(array $order): array
|
||||
{
|
||||
$orderId = (int) ($order['id'] ?? 0);
|
||||
if ($orderId <= 0) {
|
||||
return [
|
||||
'przesylka.numer' => '',
|
||||
'przesylka.link_sledzenia' => '',
|
||||
];
|
||||
}
|
||||
|
||||
$latestPackage = $this->shipmentPackageRepository->findLatestByOrderId($orderId);
|
||||
if (!is_array($latestPackage)) {
|
||||
return [
|
||||
'przesylka.numer' => '',
|
||||
'przesylka.link_sledzenia' => '',
|
||||
];
|
||||
}
|
||||
|
||||
$trackingNumber = trim((string) ($latestPackage['tracking_number'] ?? ''));
|
||||
$provider = trim((string) ($latestPackage['provider'] ?? ''));
|
||||
$carrierId = trim((string) ($latestPackage['carrier_id'] ?? ''));
|
||||
$trackingUrl = DeliveryStatus::trackingUrl($provider, $trackingNumber, $carrierId) ?? '';
|
||||
|
||||
return [
|
||||
'przesylka.numer' => $trackingNumber,
|
||||
'przesylka.link_sledzenia' => $trackingUrl,
|
||||
];
|
||||
return $this->inner->resolve($template, $variableMap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,11 @@ use App\Modules\Automation\AutomationService;
|
||||
use App\Modules\Settings\ShopproApiClient;
|
||||
use App\Modules\Settings\ShopproIntegrationsRepository;
|
||||
use App\Modules\Shipments\ShipmentPackageRepository;
|
||||
use App\Modules\Settings\CompanySettingsRepository;
|
||||
use App\Modules\Sms\SmsConversationService;
|
||||
use App\Modules\Sms\SmsMessageRepository;
|
||||
use App\Modules\Sms\SmsTemplateRepository;
|
||||
use App\Modules\Sms\SmsVariableResolver;
|
||||
use Throwable;
|
||||
|
||||
final class OrdersController
|
||||
@@ -46,7 +49,10 @@ final class OrdersController
|
||||
private readonly ?InvoiceRepository $invoiceRepo = null,
|
||||
private readonly ?InvoiceConfigRepository $invoiceConfigRepo = null,
|
||||
private readonly ?SmsMessageRepository $smsMessages = null,
|
||||
private readonly ?SmsConversationService $smsConversation = null
|
||||
private readonly ?SmsConversationService $smsConversation = null,
|
||||
private readonly ?SmsTemplateRepository $smsTemplates = null,
|
||||
private readonly ?SmsVariableResolver $smsVariableResolver = null,
|
||||
private readonly ?CompanySettingsRepository $companySettingsRepo = null
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -255,6 +261,7 @@ final class OrdersController
|
||||
$smsMessages = $this->smsMessages !== null ? $this->smsMessages->findByOrderId($orderId) : [];
|
||||
$smsPhone = $this->resolveSmsPhone($order, $addresses);
|
||||
$smsDefaultFooterConfigured = $this->smsConversation !== null && $this->smsConversation->hasDefaultFooter();
|
||||
$smsTemplates = $this->smsTemplates !== null ? $this->smsTemplates->listActive() : [];
|
||||
|
||||
$html = $this->template->render('orders/show', [
|
||||
'title' => $this->translator->get('orders.details.title') . ' #' . $orderId,
|
||||
@@ -290,6 +297,7 @@ final class OrdersController
|
||||
'smsMessages' => $smsMessages,
|
||||
'smsPhone' => $smsPhone,
|
||||
'smsDefaultFooterConfigured' => $smsDefaultFooterConfigured,
|
||||
'smsTemplates' => $smsTemplates,
|
||||
], 'layouts/app');
|
||||
|
||||
return Response::html($html);
|
||||
@@ -331,6 +339,44 @@ final class OrdersController
|
||||
return Response::redirect($redirectTo);
|
||||
}
|
||||
|
||||
public function smsTemplate(Request $request): Response
|
||||
{
|
||||
$orderId = max(0, (int) $request->input('id', 0));
|
||||
$templateId = max(0, (int) $request->input('template_id', 0));
|
||||
|
||||
if ($orderId <= 0 || $templateId <= 0) {
|
||||
return Response::json(['ok' => false, 'error' => 'Nieprawidlowe parametry.'], 400);
|
||||
}
|
||||
if ($this->smsTemplates === null || $this->smsVariableResolver === null) {
|
||||
return Response::json(['ok' => false, 'error' => 'Modul szablonow SMS nie jest dostepny.'], 500);
|
||||
}
|
||||
|
||||
$template = $this->smsTemplates->findById($templateId);
|
||||
if ($template === null || (int) ($template['is_active'] ?? 0) !== 1) {
|
||||
return Response::json(['ok' => false, 'error' => 'Szablon nie istnieje albo jest nieaktywny.'], 404);
|
||||
}
|
||||
|
||||
$details = $this->orders->findDetails($orderId);
|
||||
if ($details === null) {
|
||||
return Response::json(['ok' => false, 'error' => 'Zamowienie nie znalezione.'], 404);
|
||||
}
|
||||
|
||||
$order = is_array($details['order'] ?? null) ? $details['order'] : [];
|
||||
$addresses = is_array($details['addresses'] ?? null) ? $details['addresses'] : [];
|
||||
$companySettings = $this->companySettingsRepo !== null
|
||||
? $this->companySettingsRepo->getSettings()
|
||||
: [];
|
||||
|
||||
$variableMap = $this->smsVariableResolver->buildVariableMap($order, $addresses, $companySettings);
|
||||
$resolvedBody = $this->smsVariableResolver->resolve((string) ($template['body'] ?? ''), $variableMap);
|
||||
|
||||
return Response::json([
|
||||
'ok' => true,
|
||||
'body' => $resolvedBody,
|
||||
'name' => (string) ($template['name'] ?? ''),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sklada informacje o historii zwrotow klienta biezacego zamowienia.
|
||||
*
|
||||
|
||||
219
src/Modules/Settings/SmsTemplateController.php
Normal file
219
src/Modules/Settings/SmsTemplateController.php
Normal file
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Modules\Settings;
|
||||
|
||||
use App\Modules\Auth\AuthService;
|
||||
use App\Core\Http\Request;
|
||||
use App\Core\Http\Response;
|
||||
use App\Core\Security\Csrf;
|
||||
use App\Core\Support\Flash;
|
||||
use App\Core\View\Template;
|
||||
use App\Core\I18n\Translator;
|
||||
use App\Modules\Sms\SmsTemplateRepository;
|
||||
use Throwable;
|
||||
|
||||
final class SmsTemplateController
|
||||
{
|
||||
private const VARIABLE_GROUPS = [
|
||||
'zamowienie' => [
|
||||
'label' => 'Zamowienie',
|
||||
'vars' => [
|
||||
'numer' => 'Numer wewnetrzny (OP...)',
|
||||
'numer_zewnetrzny' => 'Numer z platformy',
|
||||
'zrodlo' => 'Zrodlo (Allegro/shopPRO/...)',
|
||||
'kwota' => 'Kwota brutto',
|
||||
'waluta' => 'Waluta (PLN/EUR/...)',
|
||||
'data' => 'Data zamowienia',
|
||||
],
|
||||
],
|
||||
'kupujacy' => [
|
||||
'label' => 'Kupujacy',
|
||||
'vars' => [
|
||||
'imie_nazwisko' => 'Imie i nazwisko',
|
||||
'email' => 'Adres e-mail',
|
||||
'telefon' => 'Telefon',
|
||||
'login' => 'Login platformy',
|
||||
],
|
||||
],
|
||||
'adres' => [
|
||||
'label' => 'Adres dostawy',
|
||||
'vars' => [
|
||||
'ulica' => 'Ulica z numerem',
|
||||
'miasto' => 'Miasto',
|
||||
'kod_pocztowy' => 'Kod pocztowy',
|
||||
'kraj' => 'Kraj',
|
||||
],
|
||||
],
|
||||
'firma' => [
|
||||
'label' => 'Firma',
|
||||
'vars' => [
|
||||
'nazwa' => 'Nazwa firmy',
|
||||
'nip' => 'NIP',
|
||||
],
|
||||
],
|
||||
'przesylka' => [
|
||||
'label' => 'Przesylka',
|
||||
'vars' => [
|
||||
'numer' => 'Numer przesylki (tracking)',
|
||||
'link_sledzenia' => 'Link sledzenia zalezny od kuriera',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private readonly Template $template,
|
||||
private readonly Translator $translator,
|
||||
private readonly AuthService $auth,
|
||||
private readonly SmsTemplateRepository $repository
|
||||
) {
|
||||
}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$templates = $this->repository->listAll();
|
||||
|
||||
$html = $this->template->render('settings/sms-templates', [
|
||||
'title' => 'Szablony SMS',
|
||||
'activeMenu' => 'settings',
|
||||
'activeSettings' => 'sms-templates',
|
||||
'user' => $this->auth->user(),
|
||||
'csrfToken' => Csrf::token(),
|
||||
'templates' => $templates,
|
||||
'successMessage' => Flash::get('settings.sms_templates.success', ''),
|
||||
'errorMessage' => Flash::get('settings.sms_templates.error', ''),
|
||||
], 'layouts/app');
|
||||
|
||||
return Response::html($html);
|
||||
}
|
||||
|
||||
public function create(Request $request): Response
|
||||
{
|
||||
return $this->renderForm(null);
|
||||
}
|
||||
|
||||
public function edit(Request $request): Response
|
||||
{
|
||||
$id = (int) $request->input('id', '0');
|
||||
$template = $id > 0 ? $this->repository->findById($id) : null;
|
||||
|
||||
if ($template === null) {
|
||||
Flash::set('settings.sms_templates.error', 'Nie znaleziono szablonu');
|
||||
return Response::redirect('/settings/sms-templates');
|
||||
}
|
||||
|
||||
return $this->renderForm($template);
|
||||
}
|
||||
|
||||
public function save(Request $request): Response
|
||||
{
|
||||
$templateId = (int) $request->input('id', '0');
|
||||
$formPath = $this->buildFormPath($templateId > 0 ? $templateId : null);
|
||||
|
||||
if (!Csrf::validate((string) $request->input('_token', ''))) {
|
||||
Flash::set('settings.sms_templates.error', 'Nieprawidlowy token CSRF');
|
||||
return Response::redirect($formPath);
|
||||
}
|
||||
|
||||
$name = trim((string) $request->input('name', ''));
|
||||
$body = (string) $request->input('body', '');
|
||||
|
||||
if ($name === '' || trim($body) === '') {
|
||||
Flash::set('settings.sms_templates.error', 'Nazwa i tresc sa wymagane');
|
||||
return Response::redirect($formPath);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->repository->save([
|
||||
'id' => $request->input('id', ''),
|
||||
'name' => $name,
|
||||
'body' => $body,
|
||||
'is_active' => $request->input('is_active', null),
|
||||
]);
|
||||
|
||||
Flash::set('settings.sms_templates.success', 'Szablon SMS zapisany');
|
||||
} catch (Throwable $exception) {
|
||||
Flash::set('settings.sms_templates.error', 'Blad zapisu szablonu: ' . $exception->getMessage());
|
||||
return Response::redirect($formPath);
|
||||
}
|
||||
|
||||
return Response::redirect('/settings/sms-templates');
|
||||
}
|
||||
|
||||
public function delete(Request $request): Response
|
||||
{
|
||||
if (!Csrf::validate((string) $request->input('_token', ''))) {
|
||||
Flash::set('settings.sms_templates.error', 'Nieprawidlowy token CSRF');
|
||||
return Response::redirect('/settings/sms-templates');
|
||||
}
|
||||
|
||||
$id = (int) $request->input('id', '0');
|
||||
if ($id <= 0) {
|
||||
Flash::set('settings.sms_templates.error', 'Nieprawidlowy identyfikator szablonu');
|
||||
return Response::redirect('/settings/sms-templates');
|
||||
}
|
||||
|
||||
try {
|
||||
$this->repository->delete($id);
|
||||
Flash::set('settings.sms_templates.success', 'Szablon SMS usuniety');
|
||||
} catch (Throwable) {
|
||||
Flash::set('settings.sms_templates.error', 'Blad usuwania szablonu');
|
||||
}
|
||||
|
||||
return Response::redirect('/settings/sms-templates');
|
||||
}
|
||||
|
||||
public function toggleStatus(Request $request): Response
|
||||
{
|
||||
if (!Csrf::validate((string) $request->input('_token', ''))) {
|
||||
return Response::json(['success' => false, 'message' => 'Nieprawidlowy token CSRF'], 403);
|
||||
}
|
||||
|
||||
$id = (int) $request->input('id', '0');
|
||||
if ($id <= 0) {
|
||||
return Response::json(['success' => false, 'message' => 'Nieprawidlowy identyfikator'], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->repository->toggleStatus($id);
|
||||
return Response::json(['success' => true]);
|
||||
} catch (Throwable) {
|
||||
return Response::json(['success' => false, 'message' => 'Blad zmiany statusu'], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function getVariables(Request $request): Response
|
||||
{
|
||||
return Response::json([
|
||||
'success' => true,
|
||||
'groups' => self::VARIABLE_GROUPS,
|
||||
]);
|
||||
}
|
||||
|
||||
private function renderForm(?array $template): Response
|
||||
{
|
||||
$html = $this->template->render('settings/sms-templates-form', [
|
||||
'title' => $template !== null ? 'Edytuj szablon SMS' : 'Nowy szablon SMS',
|
||||
'activeMenu' => 'settings',
|
||||
'activeSettings' => 'sms-templates',
|
||||
'user' => $this->auth->user(),
|
||||
'csrfToken' => Csrf::token(),
|
||||
'template' => $template,
|
||||
'variableGroups' => self::VARIABLE_GROUPS,
|
||||
'successMessage' => Flash::get('settings.sms_templates.success', ''),
|
||||
'errorMessage' => Flash::get('settings.sms_templates.error', ''),
|
||||
], 'layouts/app');
|
||||
|
||||
return Response::html($html);
|
||||
}
|
||||
|
||||
private function buildFormPath(?int $templateId): string
|
||||
{
|
||||
if ($templateId !== null && $templateId > 0) {
|
||||
return '/settings/sms-templates/edit?id=' . $templateId;
|
||||
}
|
||||
|
||||
return '/settings/sms-templates/create';
|
||||
}
|
||||
}
|
||||
121
src/Modules/Sms/SmsTemplateRepository.php
Normal file
121
src/Modules/Sms/SmsTemplateRepository.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Modules\Sms;
|
||||
|
||||
use App\Core\Http\ToggleableRepositoryTrait;
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
|
||||
final class SmsTemplateRepository
|
||||
{
|
||||
use ToggleableRepositoryTrait;
|
||||
|
||||
public function __construct(
|
||||
private readonly PDO $pdo
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
public function listAll(): array
|
||||
{
|
||||
$statement = $this->pdo->prepare(
|
||||
'SELECT id, name, body, is_active, created_at, updated_at
|
||||
FROM sms_templates
|
||||
ORDER BY name ASC'
|
||||
);
|
||||
$statement->execute();
|
||||
$rows = $statement->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
return is_array($rows) ? $rows : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
public function listActive(): array
|
||||
{
|
||||
$statement = $this->pdo->prepare(
|
||||
'SELECT id, name, body
|
||||
FROM sms_templates
|
||||
WHERE is_active = 1
|
||||
ORDER BY name ASC'
|
||||
);
|
||||
$statement->execute();
|
||||
$rows = $statement->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
return is_array($rows) ? $rows : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
public function findById(int $id): ?array
|
||||
{
|
||||
$statement = $this->pdo->prepare(
|
||||
'SELECT id, name, body, is_active, created_at, updated_at
|
||||
FROM sms_templates
|
||||
WHERE id = :id'
|
||||
);
|
||||
$statement->execute(['id' => $id]);
|
||||
$row = $statement->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
return is_array($row) ? $row : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
*/
|
||||
public function save(array $data): int
|
||||
{
|
||||
$name = trim((string) ($data['name'] ?? ''));
|
||||
$body = (string) ($data['body'] ?? '');
|
||||
if ($name === '') {
|
||||
throw new RuntimeException('Nazwa szablonu jest wymagana.');
|
||||
}
|
||||
if (trim($body) === '') {
|
||||
throw new RuntimeException('Tresc szablonu jest wymagana.');
|
||||
}
|
||||
|
||||
$id = isset($data['id']) && $data['id'] !== '' ? (int) $data['id'] : null;
|
||||
$params = [
|
||||
'name' => $name,
|
||||
'body' => $body,
|
||||
'is_active' => isset($data['is_active']) && $data['is_active'] ? 1 : 0,
|
||||
];
|
||||
|
||||
if ($id !== null) {
|
||||
$params['id'] = $id;
|
||||
$statement = $this->pdo->prepare(
|
||||
'UPDATE sms_templates
|
||||
SET name = :name, body = :body, is_active = :is_active
|
||||
WHERE id = :id'
|
||||
);
|
||||
$statement->execute($params);
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
$statement = $this->pdo->prepare(
|
||||
'INSERT INTO sms_templates (name, body, is_active)
|
||||
VALUES (:name, :body, :is_active)'
|
||||
);
|
||||
$statement->execute($params);
|
||||
|
||||
return (int) $this->pdo->lastInsertId();
|
||||
}
|
||||
|
||||
public function delete(int $id): void
|
||||
{
|
||||
$statement = $this->pdo->prepare('DELETE FROM sms_templates WHERE id = :id');
|
||||
$statement->execute(['id' => $id]);
|
||||
}
|
||||
|
||||
public function toggleStatus(int $id): void
|
||||
{
|
||||
$this->toggleActive('sms_templates', $id);
|
||||
}
|
||||
}
|
||||
119
src/Modules/Sms/SmsVariableResolver.php
Normal file
119
src/Modules/Sms/SmsVariableResolver.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Modules\Sms;
|
||||
|
||||
use App\Modules\Shipments\DeliveryStatus;
|
||||
use App\Modules\Shipments\ShipmentPackageRepository;
|
||||
|
||||
final class SmsVariableResolver
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ShipmentPackageRepository $shipmentPackageRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $order
|
||||
* @param array<int, array<string, mixed>> $addresses
|
||||
* @param array<string, mixed> $companySettings
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function buildVariableMap(array $order, array $addresses, array $companySettings): array
|
||||
{
|
||||
$customerAddress = $this->findAddress($addresses, 'customer');
|
||||
$deliveryAddress = $this->findAddress($addresses, 'delivery') ?? $customerAddress;
|
||||
|
||||
$buyerName = (string) ($customerAddress['name'] ?? '');
|
||||
$buyerEmail = (string) ($customerAddress['email'] ?? '');
|
||||
$buyerPhone = (string) ($customerAddress['phone'] ?? '');
|
||||
|
||||
$totalFormatted = number_format((float) ($order['total_with_tax'] ?? 0), 2, ',', ' ');
|
||||
$orderedAt = (string) ($order['ordered_at'] ?? '');
|
||||
if ($orderedAt !== '' && ($ts = strtotime($orderedAt)) !== false) {
|
||||
$orderedAt = date('Y-m-d', $ts);
|
||||
}
|
||||
|
||||
$baseVariables = [
|
||||
'zamowienie.numer' => (string) ($order['internal_order_number'] ?? $order['id'] ?? ''),
|
||||
'zamowienie.numer_zewnetrzny' => (string) ($order['external_order_id'] ?? $order['source_order_id'] ?? ''),
|
||||
'zamowienie.zrodlo' => ucfirst((string) ($order['source'] ?? '')),
|
||||
'zamowienie.kwota' => $totalFormatted,
|
||||
'zamowienie.waluta' => (string) ($order['currency'] ?? 'PLN'),
|
||||
'zamowienie.data' => $orderedAt,
|
||||
'kupujacy.imie_nazwisko' => $buyerName,
|
||||
'kupujacy.email' => $buyerEmail,
|
||||
'kupujacy.telefon' => $buyerPhone,
|
||||
'kupujacy.login' => (string) ($order['customer_login'] ?? ''),
|
||||
'adres.ulica' => trim(($deliveryAddress['street_name'] ?? '') . ' ' . ($deliveryAddress['street_number'] ?? '')),
|
||||
'adres.miasto' => (string) ($deliveryAddress['city'] ?? ''),
|
||||
'adres.kod_pocztowy' => (string) ($deliveryAddress['zip_code'] ?? ''),
|
||||
'adres.kraj' => (string) ($deliveryAddress['country'] ?? ''),
|
||||
'firma.nazwa' => (string) ($companySettings['company_name'] ?? ''),
|
||||
'firma.nip' => (string) ($companySettings['tax_number'] ?? ''),
|
||||
];
|
||||
|
||||
return $baseVariables + $this->resolveShipmentVariables($order);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string> $variableMap
|
||||
*/
|
||||
public function resolve(string $template, array $variableMap): string
|
||||
{
|
||||
return preg_replace_callback(
|
||||
'/\{\{([a-z_]+\.[a-z_]+)\}\}/',
|
||||
static fn(array $m): string => $variableMap[$m[1]] ?? '',
|
||||
$template
|
||||
) ?? $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $addresses
|
||||
* @return array<string, mixed>|null
|
||||
*/
|
||||
private function findAddress(array $addresses, string $type): ?array
|
||||
{
|
||||
foreach ($addresses as $addr) {
|
||||
if (($addr['address_type'] ?? '') === $type) {
|
||||
return $addr;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $order
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function resolveShipmentVariables(array $order): array
|
||||
{
|
||||
$orderId = (int) ($order['id'] ?? 0);
|
||||
if ($orderId <= 0) {
|
||||
return [
|
||||
'przesylka.numer' => '',
|
||||
'przesylka.link_sledzenia' => '',
|
||||
];
|
||||
}
|
||||
|
||||
$latestPackage = $this->shipmentPackageRepository->findLatestByOrderId($orderId);
|
||||
if (!is_array($latestPackage)) {
|
||||
return [
|
||||
'przesylka.numer' => '',
|
||||
'przesylka.link_sledzenia' => '',
|
||||
];
|
||||
}
|
||||
|
||||
$trackingNumber = trim((string) ($latestPackage['tracking_number'] ?? ''));
|
||||
$provider = trim((string) ($latestPackage['provider'] ?? ''));
|
||||
$carrierId = trim((string) ($latestPackage['carrier_id'] ?? ''));
|
||||
$trackingUrl = DeliveryStatus::trackingUrl($provider, $trackingNumber, $carrierId) ?? '';
|
||||
|
||||
return [
|
||||
'przesylka.numer' => $trackingNumber,
|
||||
'przesylka.link_sledzenia' => $trackingUrl,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user