feat: Add tagging system for SMS logs and templates
- Introduced a `tag` field in the `SmsLog` entity to categorize SMS messages. - Updated the `SmsService` to handle the new `tag` parameter during SMS dispatch. - Implemented a `SmsTextResolver` service to resolve SMS message templates based on tags. - Created a new `SmsMessageTemplate` entity for editable SMS templates with placeholders. - Added endpoints for managing SMS message templates in the admin panel. - Enhanced existing SMS dispatching methods across various controllers to utilize the tagging system. - Migrated the database to include the new `tag` field and created a seeding command for default SMS templates. - Updated admin API to filter SMS logs by tag and include tag information in responses.
This commit is contained in:
@@ -1264,12 +1264,17 @@ class AdminApiController extends BaseController
|
||||
{
|
||||
$page = max(1, (int) $request->query->get('page', 1));
|
||||
$limit = min(100, max(1, (int) $request->query->get('limit', 15)));
|
||||
$tag = trim((string) $request->query->get('tag', ''));
|
||||
|
||||
$qb = $this->em->createQueryBuilder()
|
||||
->select('s.uuid, s.mobile, s.message, s.provider, s.success, s.createdAt')
|
||||
->select('s.uuid, s.mobile, s.message, s.provider, s.success, s.tag, s.createdAt')
|
||||
->from(SmsLog::class, 's')
|
||||
->orderBy('s.createdAt', 'DESC');
|
||||
|
||||
if ($tag !== '') {
|
||||
$qb->andWhere('s.tag = :tag')->setParameter('tag', $tag);
|
||||
}
|
||||
|
||||
$total = (clone $qb)->select('COUNT(s.id)')->getQuery()->getSingleScalarResult();
|
||||
|
||||
$rows = $qb->setFirstResult(($page - 1) * $limit)->setMaxResults($limit)
|
||||
@@ -1281,6 +1286,7 @@ class AdminApiController extends BaseController
|
||||
'message' => $l['message'],
|
||||
'status' => $l['success'] ? 'sent' : 'failed',
|
||||
'provider' => $l['provider'],
|
||||
'tag' => $l['tag'],
|
||||
'sent_at' => date('c', (int) $l['createdAt']),
|
||||
'created_at' => date('c', (int) $l['createdAt']),
|
||||
], $rows);
|
||||
|
||||
@@ -26,6 +26,7 @@ class NotificationMobileController extends BaseController
|
||||
private readonly DoctorRepository $doctorRepo,
|
||||
private readonly ClinicRepository $clinicRepo,
|
||||
private readonly SmsService $smsService,
|
||||
private readonly \App\Sms\Service\SmsTextResolver $smsText,
|
||||
) {}
|
||||
|
||||
// ── Request OTP ───────────────────────────────────────────────────────────
|
||||
@@ -60,9 +61,13 @@ class NotificationMobileController extends BaseController
|
||||
$this->em->flush();
|
||||
|
||||
// ارسال SMS
|
||||
$message = $this->smsText->resolve(\App\Sms\Entity\SmsLog::TAG_NOTIFICATION_MOBILE, [
|
||||
'code' => $otp->getOtpCode(),
|
||||
]);
|
||||
$this->smsService->dispatchAsync(
|
||||
$mobile,
|
||||
"کد تأیید شماره اعلان شما: {$otp->getOtpCode()}\nاعتبار: ۵ دقیقه"
|
||||
$message,
|
||||
tag: \App\Sms\Entity\SmsLog::TAG_NOTIFICATION_MOBILE,
|
||||
);
|
||||
|
||||
return $this->success([
|
||||
|
||||
@@ -33,6 +33,7 @@ class PreRegistrationController extends BaseController
|
||||
private readonly ClinicRepository $clinicRepo,
|
||||
private readonly UserPasswordHasherInterface $hasher,
|
||||
private readonly SmsService $sms,
|
||||
private readonly \App\Sms\Service\SmsTextResolver $smsText,
|
||||
private readonly LoggerInterface $logger,
|
||||
) {}
|
||||
|
||||
@@ -154,13 +155,15 @@ class PreRegistrationController extends BaseController
|
||||
$this->em->flush();
|
||||
|
||||
try {
|
||||
$message = $this->smsText->resolve(\App\Sms\Entity\SmsLog::TAG_PRE_REGISTRATION, [
|
||||
'username' => $preReg->getMobile(),
|
||||
'password' => $password,
|
||||
'link' => 'https://clinic-pro.ddev.site/admin',
|
||||
]);
|
||||
$this->sms->dispatchAsync(
|
||||
$preReg->getMobile(),
|
||||
sprintf(
|
||||
'به کلینیک پرو خوش آمدید! شمارهکاربری: %s | رمز عبور: %s | لینک ورود: https://clinic-pro.ddev.site/admin',
|
||||
$preReg->getMobile(),
|
||||
$password
|
||||
)
|
||||
$message,
|
||||
tag: \App\Sms\Entity\SmsLog::TAG_PRE_REGISTRATION,
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->warning('PreRegistration SMS failed', ['uuid' => $uuid, 'error' => $e->getMessage()]);
|
||||
|
||||
@@ -4,18 +4,20 @@ namespace App\Auth\Service;
|
||||
|
||||
use App\Shared\Constant\ErrorCodes;
|
||||
use App\Shared\Exception\AppException;
|
||||
use App\Shared\Message\SendSmsMessage;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use App\Sms\Entity\SmsLog;
|
||||
use App\Sms\Service\SmsService;
|
||||
use App\Sms\Service\SmsTextResolver;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
use Symfony\Contracts\Cache\CacheInterface;
|
||||
|
||||
class OtpService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly CacheInterface $cache,
|
||||
private readonly MessageBusInterface $bus,
|
||||
private readonly int $otpTtl = 1200,
|
||||
private readonly string $appEnv = 'dev',
|
||||
private readonly CacheInterface $cache,
|
||||
private readonly SmsService $sms,
|
||||
private readonly SmsTextResolver $smsText,
|
||||
private readonly int $otpTtl = 1200,
|
||||
private readonly string $appEnv = 'dev',
|
||||
) {}
|
||||
|
||||
private function key(string $uuid): string
|
||||
@@ -37,7 +39,8 @@ class OtpService
|
||||
$this->cache->save($item);
|
||||
|
||||
if ($this->appEnv !== 'dev') {
|
||||
$this->bus->dispatch(new SendSmsMessage($mobile, "کد تأیید شما: {$code}"));
|
||||
$message = $this->smsText->resolve(SmsLog::TAG_OTP, ['code' => $code]);
|
||||
$this->sms->dispatchAsync($mobile, $message, tag: SmsLog::TAG_OTP);
|
||||
}
|
||||
|
||||
return $uuid;
|
||||
|
||||
@@ -17,6 +17,7 @@ class ClinicInvitationService
|
||||
private readonly ClinicDoctorInvitationRepository $repo,
|
||||
private readonly DoctorRepository $doctorRepo,
|
||||
private readonly SmsService $smsService,
|
||||
private readonly \App\Sms\Service\SmsTextResolver $smsText,
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly string $appUrl,
|
||||
) {}
|
||||
@@ -112,10 +113,11 @@ class ClinicInvitationService
|
||||
$clinicName = $clinic->getName() ?? 'کلینیک';
|
||||
$link = rtrim($this->appUrl, '/') . '/clinic-invitation/' . $inv->getToken();
|
||||
|
||||
$message = "دکتر گرامی، کلینیک {$clinicName} شما را برای همکاری دعوت کرده است.\n"
|
||||
. "برای بررسی: {$link}\n"
|
||||
. "این لینک تا ۷۲ ساعت معتبر است.";
|
||||
$message = $this->smsText->resolve(\App\Sms\Entity\SmsLog::TAG_CLINIC_INVITATION, [
|
||||
'clinic' => $clinicName,
|
||||
'link' => $link,
|
||||
]);
|
||||
|
||||
$this->smsService->dispatchAsync($inv->getMobile(), $message);
|
||||
$this->smsService->dispatchAsync($inv->getMobile(), $message, tag: \App\Sms\Entity\SmsLog::TAG_CLINIC_INVITATION);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ class PaymentController extends BaseController
|
||||
private readonly SubscriptionService $subscriptionService,
|
||||
private readonly SmsWalletService $smsWalletService,
|
||||
private readonly SmsService $smsService,
|
||||
private readonly \App\Sms\Service\SmsTextResolver $smsText,
|
||||
private readonly DoctorRepository $doctorRepo,
|
||||
private readonly ClinicRepository $clinicRepo,
|
||||
private readonly SiteConfigRepository $configRepo,
|
||||
@@ -635,9 +636,14 @@ class PaymentController extends BaseController
|
||||
$mobile = $appointment->getPatientMobile();
|
||||
if ($mobile) {
|
||||
$when = date('Y-m-d H:i', $appointment->getSlotStart());
|
||||
$message = $this->smsText->resolve(\App\Sms\Entity\SmsLog::TAG_PAYMENT, [
|
||||
'doctor' => $appointment->getDoctor()->getName(),
|
||||
'date' => $when,
|
||||
]);
|
||||
$this->smsService->dispatchAsync(
|
||||
$mobile,
|
||||
sprintf('نوبت شما با %s در تاریخ %s ثبت و تأیید شد.', $appointment->getDoctor()->getName(), $when)
|
||||
$message,
|
||||
tag: \App\Sms\Entity\SmsLog::TAG_PAYMENT,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Sms\Command;
|
||||
|
||||
use App\Sms\Entity\SmsMessageTemplate;
|
||||
use App\Sms\Repository\SmsMessageTemplateRepository;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'app:seed-sms-message-templates',
|
||||
description: 'متن پیشفرض پیامکهای سیستمی را برای تگهایی که هنوز رکورد ندارند میسازد',
|
||||
)]
|
||||
class SeedSmsMessageTemplatesCommand extends Command
|
||||
{
|
||||
public function __construct(private readonly SmsMessageTemplateRepository $repo)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$created = 0;
|
||||
foreach (SmsMessageTemplate::DEFAULTS as $tag => $def) {
|
||||
if ($this->repo->findByTag($tag) !== null) {
|
||||
continue;
|
||||
}
|
||||
$this->repo->save(
|
||||
new SmsMessageTemplate($tag, $def['title'], $def['body'], $def['variables']),
|
||||
false,
|
||||
);
|
||||
$created++;
|
||||
}
|
||||
$this->repo->getEntityManager()->flush();
|
||||
|
||||
$output->writeln(sprintf('Seeded %d sms message templates.', $created));
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@ class SmsController extends BaseController
|
||||
return $this->error(ErrorCodes::ERR_VALIDATION_002, 'mobile و message الزامی است', 422);
|
||||
}
|
||||
|
||||
$this->smsService->dispatchAsync($mobile, $message, $provider);
|
||||
$this->smsService->dispatchAsync($mobile, $message, $provider, tag: \App\Sms\Entity\SmsLog::TAG_USER_TEMPLATE);
|
||||
|
||||
return $this->success(['message' => 'پیامک در صف ارسال قرار گرفت']);
|
||||
}
|
||||
@@ -483,7 +483,7 @@ class SmsController extends BaseController
|
||||
$message = $template->renderBody($vars);
|
||||
$this->smsService->dispatchAsync(
|
||||
$mobile, $message, $provider, $template->getUuid(),
|
||||
$vars, $template->getProviderCode()
|
||||
$vars, $template->getProviderCode(), \App\Sms\Entity\SmsLog::TAG_USER_TEMPLATE
|
||||
);
|
||||
|
||||
return $this->success(['message' => 'پیامک در صف ارسال قرار گرفت']);
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace App\Sms\Controller;
|
||||
|
||||
use App\Shared\Constant\ErrorCodes;
|
||||
use App\Shared\Controller\BaseController;
|
||||
use App\Sms\Entity\SmsMessageTemplate;
|
||||
use App\Sms\Repository\SmsMessageTemplateRepository;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
|
||||
/**
|
||||
* مدیریت متن ویرایشپذیر پیامکهای سیستمی (بر اساس تگ).
|
||||
*/
|
||||
#[OA\Tag(name: 'SMS')]
|
||||
#[IsGranted('ROLE_ADMIN')]
|
||||
class SmsMessageController extends BaseController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly SmsMessageTemplateRepository $repo,
|
||||
) {}
|
||||
|
||||
#[OA\Get(
|
||||
path: '/api/v1/admin/sms/messages',
|
||||
summary: 'لیست متنهای سیستمی پیامک (بر اساس تگ)',
|
||||
security: [['bearerAuth' => []]],
|
||||
responses: [new OA\Response(response: 200, description: 'لیست متنها')]
|
||||
)]
|
||||
#[Route('/api/v1/admin/sms/messages', methods: ['GET'])]
|
||||
public function list(): JsonResponse
|
||||
{
|
||||
$existing = [];
|
||||
foreach ($this->repo->findAll() as $tpl) {
|
||||
$existing[$tpl->getTag()] = $tpl->toArray();
|
||||
}
|
||||
|
||||
// تگهای پیشفرضی که هنوز در DB رکورد ندارند را هم با مقدار پیشفرض نشان بده.
|
||||
$items = [];
|
||||
foreach (SmsMessageTemplate::DEFAULTS as $tag => $def) {
|
||||
$items[] = $existing[$tag] ?? [
|
||||
'tag' => $tag,
|
||||
'title' => $def['title'],
|
||||
'body' => $def['body'],
|
||||
'variables' => $def['variables'],
|
||||
'updated_at' => null,
|
||||
];
|
||||
}
|
||||
|
||||
return $this->success(['data' => $items]);
|
||||
}
|
||||
|
||||
#[OA\Patch(
|
||||
path: '/api/v1/admin/sms/messages/{tag}',
|
||||
summary: 'ویرایش متن یک پیامک سیستمی',
|
||||
security: [['bearerAuth' => []]],
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
required: ['body'],
|
||||
properties: [new OA\Property(property: 'body', type: 'string')]
|
||||
)
|
||||
),
|
||||
responses: [
|
||||
new OA\Response(response: 200, description: 'متن بهروزرسانی شد'),
|
||||
new OA\Response(response: 404, description: 'تگ ناشناخته'),
|
||||
new OA\Response(response: 422, description: 'placeholder نامعتبر'),
|
||||
]
|
||||
)]
|
||||
#[Route('/api/v1/admin/sms/messages/{tag}', methods: ['PATCH'])]
|
||||
public function update(string $tag, Request $request): JsonResponse
|
||||
{
|
||||
if (!isset(SmsMessageTemplate::DEFAULTS[$tag])) {
|
||||
return $this->error(ErrorCodes::ERR_NOT_FOUND_001, 'تگ پیامک ناشناخته است', 404);
|
||||
}
|
||||
|
||||
$data = json_decode($request->getContent(), true) ?? [];
|
||||
$body = trim((string) ($data['body'] ?? ''));
|
||||
if ($body === '') {
|
||||
return $this->error(ErrorCodes::ERR_VALIDATION_002, 'متن پیامک الزامی است', 422);
|
||||
}
|
||||
|
||||
$allowed = SmsMessageTemplate::DEFAULTS[$tag]['variables'];
|
||||
preg_match_all('/\{([a-zA-Z0-9_]+)\}/', $body, $m);
|
||||
$used = array_unique($m[1]);
|
||||
$unknown = array_diff($used, $allowed);
|
||||
if (!empty($unknown)) {
|
||||
return $this->error(
|
||||
ErrorCodes::ERR_VALIDATION_001,
|
||||
'placeholder نامعتبر: ' . implode(', ', $unknown) . ' — مجاز: ' . implode(', ', $allowed),
|
||||
422,
|
||||
'body',
|
||||
);
|
||||
}
|
||||
|
||||
$tpl = $this->repo->findByTag($tag);
|
||||
if ($tpl === null) {
|
||||
$def = SmsMessageTemplate::DEFAULTS[$tag];
|
||||
$tpl = new SmsMessageTemplate($tag, $def['title'], $body, $def['variables']);
|
||||
} else {
|
||||
$tpl->setBody($body);
|
||||
}
|
||||
$this->repo->save($tpl);
|
||||
|
||||
return $this->success(['data' => $tpl->toArray()]);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,24 @@ use Symfony\Component\Uid\Uuid;
|
||||
#[ORM\Index(columns: ['mobile', 'created_at'], name: 'idx_sms_logs_mobile')]
|
||||
class SmsLog
|
||||
{
|
||||
public const TAG_GLOBAL = 'global';
|
||||
public const TAG_OTP = 'otp';
|
||||
public const TAG_PAYMENT = 'payment';
|
||||
public const TAG_CLINIC_INVITATION = 'clinic_invitation';
|
||||
public const TAG_PRE_REGISTRATION = 'pre_registration';
|
||||
public const TAG_NOTIFICATION_MOBILE = 'notification_mobile';
|
||||
public const TAG_USER_TEMPLATE = 'user_template';
|
||||
|
||||
public const TAGS = [
|
||||
self::TAG_GLOBAL,
|
||||
self::TAG_OTP,
|
||||
self::TAG_PAYMENT,
|
||||
self::TAG_CLINIC_INVITATION,
|
||||
self::TAG_PRE_REGISTRATION,
|
||||
self::TAG_NOTIFICATION_MOBILE,
|
||||
self::TAG_USER_TEMPLATE,
|
||||
];
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
@@ -33,20 +51,26 @@ class SmsLog
|
||||
#[ORM\Column(name: 'template_uuid', type: 'string', length: 36, nullable: true)]
|
||||
private ?string $templateUuid = null;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 30, options: ['default' => self::TAG_GLOBAL])]
|
||||
private string $tag = self::TAG_GLOBAL;
|
||||
|
||||
#[ORM\Column(name: 'created_at', type: 'integer')]
|
||||
private int $createdAt;
|
||||
|
||||
public function __construct(string $mobile, string $message, string $provider, bool $success)
|
||||
public function __construct(string $mobile, string $message, string $provider, bool $success, string $tag = self::TAG_GLOBAL)
|
||||
{
|
||||
$this->uuid = Uuid::v4()->toRfc4122();
|
||||
$this->mobile = $mobile;
|
||||
$this->message = $message;
|
||||
$this->provider = $provider;
|
||||
$this->success = $success;
|
||||
$this->tag = $tag;
|
||||
$this->createdAt = time();
|
||||
}
|
||||
|
||||
public function setTemplateUuid(?string $v): self { $this->templateUuid = $v; return $this; }
|
||||
public function getTag(): string { return $this->tag; }
|
||||
public function setTag(string $v): self { $this->tag = $v; return $this; }
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
@@ -57,6 +81,7 @@ class SmsLog
|
||||
'provider' => $this->provider,
|
||||
'success' => $this->success,
|
||||
'template_uuid' => $this->templateUuid,
|
||||
'tag' => $this->tag,
|
||||
'created_at' => $this->createdAt,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace App\Sms\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
/**
|
||||
* متن ویرایشپذیرِ پیامکهای سیستمی، کلیددار با تگ (SmsLog::TAG_*).
|
||||
* body شامل placeholderهای {key} است که هنگام ارسال جایگزین میشوند.
|
||||
*/
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'sms_message_templates')]
|
||||
class SmsMessageTemplate
|
||||
{
|
||||
/**
|
||||
* متن و متادیتای پیشفرض هر تگ — مرجع برای seed و fallback.
|
||||
* @var array<string, array{title: string, body: string, variables: string[]}>
|
||||
*/
|
||||
public const DEFAULTS = [
|
||||
SmsLog::TAG_OTP => [
|
||||
'title' => 'کد تأیید ورود',
|
||||
'body' => 'کد تأیید شما: {code}',
|
||||
'variables' => ['code'],
|
||||
],
|
||||
SmsLog::TAG_PAYMENT => [
|
||||
'title' => 'تأیید پرداخت و نوبت',
|
||||
'body' => 'نوبت شما با {doctor} در تاریخ {date} ثبت و تأیید شد.',
|
||||
'variables' => ['doctor', 'date'],
|
||||
],
|
||||
SmsLog::TAG_CLINIC_INVITATION => [
|
||||
'title' => 'دعوت پزشک به کلینیک',
|
||||
'body' => "دکتر گرامی، کلینیک {clinic} شما را برای همکاری دعوت کرده است.\nبرای بررسی: {link}\nاین لینک تا ۷۲ ساعت معتبر است.",
|
||||
'variables' => ['clinic', 'link'],
|
||||
],
|
||||
SmsLog::TAG_PRE_REGISTRATION => [
|
||||
'title' => 'پیشثبتنام',
|
||||
'body' => 'به کلینیک پرو خوش آمدید! شمارهکاربری: {username} | رمز عبور: {password} | لینک ورود: {link}',
|
||||
'variables' => ['username', 'password', 'link'],
|
||||
],
|
||||
SmsLog::TAG_NOTIFICATION_MOBILE => [
|
||||
'title' => 'تأیید شماره اعلان',
|
||||
'body' => "کد تأیید شماره اعلان شما: {code}\nاعتبار: ۵ دقیقه",
|
||||
'variables' => ['code'],
|
||||
],
|
||||
];
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 30, unique: true)]
|
||||
private string $tag;
|
||||
|
||||
#[ORM\Column(type: 'string', length: 100)]
|
||||
private string $title;
|
||||
|
||||
#[ORM\Column(type: 'text')]
|
||||
private string $body;
|
||||
|
||||
#[ORM\Column(type: 'json')]
|
||||
private array $variables = [];
|
||||
|
||||
#[ORM\Column(name: 'updated_at', type: 'integer')]
|
||||
private int $updatedAt;
|
||||
|
||||
public function __construct(string $tag, string $title, string $body, array $variables = [])
|
||||
{
|
||||
$this->tag = $tag;
|
||||
$this->title = $title;
|
||||
$this->body = $body;
|
||||
$this->variables = $variables;
|
||||
$this->updatedAt = time();
|
||||
}
|
||||
|
||||
public function getId(): ?int { return $this->id; }
|
||||
public function getTag(): string { return $this->tag; }
|
||||
public function getTitle(): string { return $this->title; }
|
||||
public function getBody(): string { return $this->body; }
|
||||
public function getVariables(): array { return $this->variables; }
|
||||
public function getUpdatedAt(): int { return $this->updatedAt; }
|
||||
|
||||
public function setTitle(string $v): self { $this->title = $v; $this->updatedAt = time(); return $this; }
|
||||
public function setBody(string $v): self { $this->body = $v; $this->updatedAt = time(); return $this; }
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'tag' => $this->tag,
|
||||
'title' => $this->title,
|
||||
'body' => $this->body,
|
||||
'variables' => $this->variables,
|
||||
'updated_at' => $this->updatedAt,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -11,5 +11,6 @@ final class SendSmsMessage
|
||||
public readonly ?string $templateUuid = null,
|
||||
public readonly array $templateVars = [],
|
||||
public readonly ?string $templateCode = null,
|
||||
public readonly string $tag = \App\Sms\Entity\SmsLog::TAG_GLOBAL,
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Sms\Repository;
|
||||
|
||||
use App\Sms\Entity\SmsMessageTemplate;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
class SmsMessageTemplateRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, SmsMessageTemplate::class);
|
||||
}
|
||||
|
||||
public function findByTag(string $tag): ?SmsMessageTemplate
|
||||
{
|
||||
return $this->findOneBy(['tag' => $tag]);
|
||||
}
|
||||
|
||||
public function save(SmsMessageTemplate $entity, bool $flush = true): void
|
||||
{
|
||||
$this->getEntityManager()->persist($entity);
|
||||
if ($flush) {
|
||||
$this->getEntityManager()->flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,9 +33,10 @@ class SmsService
|
||||
?string $templateUuid = null,
|
||||
array $templateVars = [],
|
||||
?string $templateCode = null,
|
||||
string $tag = SmsLog::TAG_GLOBAL,
|
||||
): void {
|
||||
$this->bus->dispatch(new SendSmsMessage(
|
||||
$mobile, $message, $provider, $templateUuid, $templateVars, $templateCode
|
||||
$mobile, $message, $provider, $templateUuid, $templateVars, $templateCode, $tag
|
||||
));
|
||||
}
|
||||
|
||||
@@ -47,7 +48,7 @@ class SmsService
|
||||
? $provider->sendTemplate($msg->mobile, $msg->templateCode, $msg->templateVars)
|
||||
: $provider->send($msg->mobile, $msg->message);
|
||||
|
||||
$log = new SmsLog($msg->mobile, $msg->message, $provider->getName(), $success);
|
||||
$log = new SmsLog($msg->mobile, $msg->message, $provider->getName(), $success, $msg->tag);
|
||||
if ($msg->templateUuid) $log->setTemplateUuid($msg->templateUuid);
|
||||
$this->logRepo->save($log);
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Sms\Service;
|
||||
|
||||
use App\Sms\Entity\SmsMessageTemplate;
|
||||
use App\Sms\Repository\SmsMessageTemplateRepository;
|
||||
|
||||
/**
|
||||
* متن پیامک سیستمی را بر اساس تگ resolve میکند: body ویرایششده از DB، با جایگزینی
|
||||
* placeholderهای {key}؛ اگر رکوردی نبود به متن پیشفرضِ SmsMessageTemplate::DEFAULTS برمیگردد.
|
||||
*/
|
||||
class SmsTextResolver
|
||||
{
|
||||
public function __construct(
|
||||
private readonly SmsMessageTemplateRepository $repo,
|
||||
) {}
|
||||
|
||||
/** @param array<string,string|int> $vars */
|
||||
public function resolve(string $tag, array $vars = []): string
|
||||
{
|
||||
$template = $this->repo->findByTag($tag);
|
||||
$body = $template?->getBody()
|
||||
?? SmsMessageTemplate::DEFAULTS[$tag]['body']
|
||||
?? '';
|
||||
|
||||
foreach ($vars as $key => $value) {
|
||||
$body = str_replace('{' . $key . '}', (string) $value, $body);
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user