maySend($auto, $customer, $channel)) { 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 { if (! $auto->usesChannel($channel)) { return false; } $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, ]); } }