Files
boursecu_server/app/Services/Automation/AutomationSender.php
T
mohmdsdgandCursor 995f6984ed feat(automations): add run-now test endpoint and sends log table
Enable immediate automation test runs without waiting for the scheduled hour, expose send history for debugging, and return all SuperAdmin overlay keys on refresh instead of only enable_automation.

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

163 lines
5.5 KiB
PHP

<?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%
$out = preg_replace('/[%٪]\s*(?:name|نام)[%٪]?/iu', $name, $out) ?? $out;
$out = preg_replace('/[%٪]\s*(?:fname|نام_کوچک|نام‌کوچک)[%٪]?/iu', $fname, $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,
]);
}
}