Files

197 lines
6.9 KiB
PHP
Raw Permalink Normal View History

2026-08-23 14:16:00 +03:30
<?php
namespace App\Services\Automation;
use App\Models\Automation;
use App\Models\AutomationSend;
use App\Models\Customer;
use App\Models\Message;
2026-08-23 17:13:30 +03:30
use App\Services\ConfigService;
2026-08-23 14:16:00 +03:30
use App\Services\CustomerAppNotifyService;
use App\Services\SmsBilling\SmsGatewayService;
use App\Exceptions\SmsBlockedException;
use Illuminate\Support\Facades\Log;
class AutomationSender
{
public function __construct(
private readonly SmsGatewayService $sms,
private readonly CustomerAppNotifyService $notify,
2026-08-23 17:13:30 +03:30
private readonly ConfigService $config,
2026-08-23 14:16:00 +03:30
) {
}
public function send(Automation $auto, Customer $customer, string $channel, bool $force = false): bool
2026-08-23 14:16:00 +03:30
{
$skipReason = $this->skipReason($auto, $customer, $channel, $force);
if ($skipReason !== null) {
$this->log($auto, $customer, $channel, 'skipped', $skipReason);
2026-08-23 14:16:00 +03:30
return false;
}
$title = $this->render((string) $auto->title, $customer);
$body = $this->render((string) $auto->body, $customer);
try {
if ($channel === AutomationCatalog::CHANNEL_SMS) {
$mobile = trim((string) $customer->mobile);
if ($mobile === '') {
throw new \RuntimeException('موبایل خالی است');
}
2026-08-23 17:13:30 +03:30
$text = $this->ensureAppNameFooter(trim($title."\n".$body));
2026-08-23 14:16:00 +03:30
$result = $this->sms->sendOrThrow($mobile, $text, 'automation');
if (! $result->sent) {
throw new \RuntimeException($result->errorMessage ?: 'ارسال پیامک ناموفق');
}
Message::create([
'sms' => $text,
'receiver' => $mobile,
'platforms' => ($result->provider ?: 'sms').':'.$mobile,
]);
} else {
$row = $this->notify->notifyCustomer((int) $customer->id, $title, $body, 'info');
if (! $row) {
throw new \RuntimeException('اعلان ساخته نشد');
}
}
$this->log($auto, $customer, $channel, 'sent');
$auto->increment('sent_count');
return true;
} catch (SmsBlockedException $e) {
$this->log($auto, $customer, $channel, 'failed', 'اعتبار پیامک کافی نیست یا مسدود است');
$auto->increment('failed_count');
Log::warning('Automation SMS blocked', ['automation_id' => $auto->id]);
return false;
} catch (\Throwable $e) {
$this->log($auto, $customer, $channel, 'failed', $e->getMessage());
$auto->increment('failed_count');
return false;
}
}
public function maySend(Automation $auto, Customer $customer, string $channel, bool $force = false): bool
{
return $this->skipReason($auto, $customer, $channel, $force) === null;
}
private function skipReason(Automation $auto, Customer $customer, string $channel, bool $force = false): ?string
2026-08-23 14:16:00 +03:30
{
if (! $auto->usesChannel($channel)) {
return 'کانال غیرفعال';
2026-08-23 14:16:00 +03:30
}
if ($force) {
return null;
}
2026-08-23 14:16:00 +03:30
$max = $auto->max_sends;
if ($max !== null && (int) $max > 0) {
$count = AutomationSend::query()
->where('automation_id', $auto->id)
->where('customer_id', $customer->id)
->where('channel', $channel)
->where('status', 'sent')
->count();
if ($count >= (int) $max) {
return 'حداکثر ارسال برای این مشتری';
2026-08-23 14:16:00 +03:30
}
}
$cooldown = max(0, (int) $auto->cooldown_hours);
if ($cooldown > 0) {
$recent = AutomationSend::query()
->where('automation_id', $auto->id)
->where('customer_id', $customer->id)
->where('channel', $channel)
->where('status', 'sent')
->where('created_at', '>=', now()->subHours($cooldown))
->exists();
if ($recent) {
return "فاصلهٔ حداقل {$cooldown} ساعت از آخرین ارسال";
2026-08-23 14:16:00 +03:30
}
}
return null;
2026-08-23 14:16:00 +03:30
}
2026-08-23 17:13:30 +03:30
private function ensureAppNameFooter(string $message): string
{
$appName = $this->resolveAppDisplayName();
2026-08-23 17:13:30 +03:30
$trimmed = rtrim($message);
if ($appName === '') {
return $trimmed;
}
if ($this->messageContainsAppName($trimmed, $appName)) {
2026-08-23 17:13:30 +03:30
return $trimmed;
}
return $trimmed === '' ? $appName : ($trimmed."\n".$appName);
}
private function resolveAppDisplayName(): string
{
foreach (['name_fa', 'name', 'name_en'] as $key) {
$value = trim($this->config->getString($key));
if ($value !== '') {
return $value;
}
}
return '';
}
private function messageContainsAppName(string $message, string $appName): bool
{
$lines = preg_split('/\R/u', $message) ?: [];
foreach ($lines as $line) {
if (trim((string) $line) === $appName) {
return true;
}
}
return false;
}
2026-08-23 14:16:00 +03:30
private function render(string $text, Customer $customer): string
{
$name = trim((string) ($customer->name ?: trim(($customer->fname ?? '').' '.($customer->lname ?? ''))));
2026-08-23 17:13:30 +03:30
$fname = (string) ($customer->fname ?? '');
$mobile = (string) ($customer->mobile ?? '');
$appName = $this->resolveAppDisplayName();
2026-08-23 14:16:00 +03:30
2026-08-23 17:13:30 +03:30
// سازگاری با قالب قدیمی {{name}}
$out = strtr($text, [
2026-08-23 14:16:00 +03:30
'{{name}}' => $name,
2026-08-23 17:13:30 +03:30
'{{fname}}' => $fname,
'{{mobile}}' => $mobile,
'{{app_name}}' => $appName,
2026-08-23 14:16:00 +03:30
]);
2026-08-23 17:13:30 +03:30
// الگوی پیامک/تنظیمات: %نام ، ٪نام ، % name ، %name%
// نام_کوچک قبل از نام — وگرنه %نام داخل %نام_کوچک زودتر match می‌شود
2026-08-23 17:13:30 +03:30
$out = preg_replace('/[%٪]\s*(?:fname|نام_کوچک|نام‌کوچک)[%٪]?/iu', $fname, $out) ?? $out;
$out = preg_replace('/[%٪]\s*(?:app_name|نام_اپ|نام‌اپ)[%٪]?/iu', $appName, $out) ?? $out;
$out = preg_replace('/[%٪]\s*(?:name|نام)(?![_‌]?کوچک)(?![_‌]?اپ)[%٪]?/iu', $name, $out) ?? $out;
2026-08-23 17:13:30 +03:30
$out = preg_replace('/[%٪]\s*(?:mobile|موبایل)[%٪]?/iu', $mobile, $out) ?? $out;
return $out;
2026-08-23 14:16:00 +03:30
}
private function log(Automation $auto, Customer $customer, string $channel, string $status, ?string $error = null): void
{
AutomationSend::create([
'automation_id' => $auto->id,
'customer_id' => $customer->id,
'channel' => $channel,
'status' => $status,
'error' => $error,
]);
}
}