Files
boursecu_server/app/Services/Automation/AutomationSender.php
T
mohmdsdgandCursor f5e66bf951 fix(automations): replace %نام_کوچک before generic %نام in SMS templates
The name placeholder regex was matching the prefix of %نام_کوچک first, leaving _کوچک in the rendered message.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-24 21:51:57 +03:30

164 lines
5.7 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Services\Automation;
use App\Models\Automation;
use App\Models\AutomationSend;
use App\Models\Customer;
use App\Models\Message;
use App\Services\ConfigService;
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,
private readonly ConfigService $config,
) {
}
public function send(Automation $auto, Customer $customer, string $channel, bool $force = false): bool
{
if (! $this->maySend($auto, $customer, $channel, $force)) {
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('موبایل خالی است');
}
$text = $this->ensureAppNameFooter(trim($title."\n".$body));
$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
{
if (! $auto->usesChannel($channel)) {
return false;
}
if ($force) {
return true;
}
$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 false;
}
}
$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 false;
}
}
return true;
}
private function ensureAppNameFooter(string $message): string
{
$appName = trim($this->config->getString('name_fa'));
$trimmed = rtrim($message);
if ($appName === '') {
return $trimmed;
}
$lines = preg_split('/\R/u', $trimmed) ?: [];
$last = trim((string) end($lines));
if ($last === $appName) {
return $trimmed;
}
return $trimmed === '' ? $appName : ($trimmed."\n".$appName);
}
private function render(string $text, Customer $customer): string
{
$name = trim((string) ($customer->name ?: trim(($customer->fname ?? '').' '.($customer->lname ?? ''))));
$fname = (string) ($customer->fname ?? '');
$mobile = (string) ($customer->mobile ?? '');
// سازگاری با قالب قدیمی {{name}}
$out = strtr($text, [
'{{name}}' => $name,
'{{fname}}' => $fname,
'{{mobile}}' => $mobile,
]);
// الگوی پیامک/تنظیمات: %نام ، ٪نام ، % name ، %name%
// نام_کوچک قبل از نام — وگرنه %نام داخل %نام_کوچک زودتر match می‌شود
$out = preg_replace('/[%٪]\s*(?:fname|نام_کوچک|نام‌کوچک)[%٪]?/iu', $fname, $out) ?? $out;
$out = preg_replace('/[%٪]\s*(?:name|نام)(?![_‌]?کوچک)[%٪]?/iu', $name, $out) ?? $out;
$out = preg_replace('/[%٪]\s*(?:mobile|موبایل)[%٪]?/iu', $mobile, $out) ?? $out;
return $out;
}
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,
]);
}
}