]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
Отправка сообщений
authorfomichev <vladimir.fomichev@erp-flowers.ru>
Tue, 4 Mar 2025 14:25:54 +0000 (17:25 +0300)
committerfomichev <vladimir.fomichev@erp-flowers.ru>
Tue, 4 Mar 2025 14:25:54 +0000 (17:25 +0300)
erp24/commands/CronController.php
erp24/config/params.php
erp24/controllers/UsersWhatsappMessageController.php
erp24/jobs/SendWhatsappMessageJob.php [new file with mode: 0644]
erp24/media/controllers/WhatsappMessageStatus.php [new file with mode: 0644]
erp24/services/WhatsAppMessageResponse.php [new file with mode: 0644]
erp24/services/WhatsAppService.php [new file with mode: 0644]
erp24/views/users-whatsapp-message/index.php
erp24/views/users-whatsapp-message/test.php [new file with mode: 0644]

index 45e98faf88a4e369cfd23533eb429d048bcd0c9f..1d81070361f07e052a43854f088fe4a98d21b121 100644 (file)
@@ -4,6 +4,7 @@ namespace yii_app\commands;
 
 use app\jobs\SendTelegramMessageDBJob;
 use app\jobs\SendTelegramMessageJob;
+use app\jobs\SendWhatsappMessageJob;
 use DateTime;
 use DateTimeZone;
 use Yii;
@@ -707,6 +708,127 @@ class CronController extends Controller
         return ExitCode::OK;
     }
 
+
+    public function actionSendWhatsappMessage()
+    {
+        $messagesSettings = UsersMessageManagement::find()->one();
+        if (!$messagesSettings) {
+            $this->stdout(
+                "Рассылка неактивна (не найдена настройка). Отправка сообщений прервана.\n",
+                BaseConsole::FG_RED
+            );
+            return ExitCode::UNAVAILABLE;
+        }
+
+        if (!$messagesSettings->active) {
+            $this->stdout(
+                "Рассылка неактивна (поле active = 0). Отправка сообщений прервана.\n",
+                BaseConsole::FG_RED
+            );
+            return ExitCode::UNAVAILABLE;
+        }
+
+        $this->stdout("Рассылка активна. Начинаем отправку второго сообщения...\n", BaseConsole::FG_GREEN);
+        date_default_timezone_set('Europe/Moscow');
+
+        $step1 = $messagesSettings ? $messagesSettings->day_before_step1 : 10;
+        $step2 = $messagesSettings ? $messagesSettings->day_before_step2 : 4;
+        $currentDate = date('Y-m-d');
+        $targetDate = date('Y-m-d', strtotime("+$step2 days", strtotime($currentDate)));
+        $kogortDate = date('Y-m-d', strtotime("-$step1 days", strtotime($targetDate)));
+
+        $kogortPhones = SentKogort::find()
+            ->select('phone')
+            ->andWhere(['kogort_number' => 2])
+            ->andWhere(['target_date' => $targetDate])
+            ->andWhere(['purchase' => 0])
+            ->column();
+
+        if (!empty($kogortPhones)) {
+            $countPhones = count($kogortPhones);
+            $this->stdout(
+                "Всего телефонов в когорте {$countPhones} записей.\n",
+                BaseConsole::FG_GREEN
+            );
+
+            $sentStatusKogort = SentKogort::find()
+                ->select('phone')
+                ->andWhere(['kogort_number' => 2])
+                ->andWhere(['target_date' => $targetDate])
+                ->andWhere(['status' => 3])
+                ->column();
+
+            $phonesArray = array_diff($kogortPhones, $sentStatusKogort);
+            $countWhatsappPhones = count($phonesArray);
+            $this->stdout(
+                "Всего телефонов в рассылке телеграма {$countWhatsappPhones} записей.\n",
+                BaseConsole::FG_GREEN
+            );
+
+            $channel = self::getChannelByName('WABA');
+            $limit = $channel['limit'] ?? 250;
+
+            if (!empty($phonesArray)) {
+                $messageText = $messagesSettings
+                    ->replaceShortcodes($messagesSettings->offer_whatsapp, $targetDate);
+                $phonesSentArray = [];
+                foreach ($phonesArray as $index => $phone) {
+                    if ($index >= $limit) {
+                        break;
+                    }
+                    $messageData = [];
+                    $messageData['phone'] = $phone;
+                    $messageData['kogort_date'] = $kogortDate;
+                    $messageData['target_date'] = $targetDate;
+                    $messageData['message'] = $messageText;
+
+                    Yii::$app->queue->push(new SendWhatsappMessageJob([
+                        'messageData' => $messageData,
+                        'isTest' => false
+                    ]));
+                    $phonesSentArray[] = $phone;
+                }
+                //TODO - перенос в отправку
+                $updatedCount = SentKogort::updateAll(
+                    [
+                        'status' => SentKogort::STATUSES['second'], // Устанавливаем статус "вторая рассылка"
+                        'updated_at' => date('Y-m-d H:i:s'),
+                    ],
+                    [
+                        'target_date' => $targetDate,
+                        'kogort_number' => SentKogort::KOGORT_NUMBERS['whatsapp'],
+                        'phone' => $phonesSentArray,
+                    ]
+                );
+
+                if ($updatedCount) {
+                    $this->stdout(
+                        "Статус записей для когорты {$kogortDate} обновлён на 'second' для {$updatedCount} записей.\n",
+                        BaseConsole::FG_GREEN
+                    );
+                } else {
+                    $this->stdout(
+                        "Не найдено записей для обновления статуса на 'second'.\n",
+                        BaseConsole::FG_RED
+                    );
+                }
+            }
+        } else {
+            $this->stdout(
+                "Нет данных для отправки второго сообщения в телеграм
+                 на {$kogortDate} для целевой даты {$targetDate}.\n",
+                BaseConsole::FG_RED
+            );
+        }
+
+        $this->stdout(
+            "Отправка второго сообщения в телеграм для корорты (ватсап)
+             на {$kogortDate} для целевой даты {$targetDate}.\n",
+            BaseConsole::FG_GREEN
+        );
+        return ExitCode::OK;
+    }
+
     public function actionGenerateCallKogorts()
     {
         $messagesSettings = UsersMessageManagement::find()->one();
index 85aa30a09dfa7d79d85c653a86f8453ac3ab6a5e..8efe83b30a644f0ddeaa02c642b217b2adc0dbd0 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 
 return [
+    'WHATSAPP_API_KEY' => '10c81eda-1dfb-42ed-a458-944f8dae4a67',
     'API2_URL' => YII_DEBUG ? 'http://host.docker.internal:5555' : 'https://api2.bazacvetov24.ru',
     //'TELEGRAM_API_URL' => "https://api.telegram.org/bot6189425433:AAFQ91OYiMiyj2jgIgmx3O2yTBl4enywySM/",
     'TELEGRAM_API_URL' => "https://api.telegram.org/bot8063257458:AAGnMf4cxwJWlYLF1wS_arn4PrOaLs9ERQQ/",
index 21f1d0913ab5a2f4f6cb197168a61c65328e41fb..a32d713345caca461c62aaeaa8df79089da215a2 100644 (file)
@@ -2,11 +2,15 @@
 
 namespace app\controllers;
 
+use app\jobs\SendWhatsappMessageJob;
+use Yii;
+use yii\web\Response;
 use yii_app\records\UsersWhatsappMessage;
 use yii_app\records\UsersWhatsappMessageSearch;
 use yii\web\Controller;
 use yii\web\NotFoundHttpException;
 use yii\filters\VerbFilter;
+use yii_app\services\WhatsAppService;
 
 /**
  * UsersWhatsappMessageController implements the CRUD actions for UsersWhatsappMessage model.
@@ -131,4 +135,78 @@ class UsersWhatsappMessageController extends Controller
 
         throw new NotFoundHttpException('The requested page does not exist.');
     }
+
+    /**
+     * Отображает форму для тестирования отправки сообщений и вызова методов API.
+     */
+    public function actionTest()
+    {
+        return $this->render('test');
+    }
+
+    /**
+     * Обрабатывает нажатие кнопки "Отправить сообщения".
+     * Из поля ввода берутся телефоны, разделённые запятыми, и для каждого телефона
+     * ставится задача в очередь на отправку сообщения.
+     */
+    public function actionSendMessages()
+    {
+        $phonesStr = Yii::$app->request->post('phones', '');
+        $phones = array_filter(array_map('trim', explode(',', $phonesStr)));
+
+        if (empty($phones)) {
+            Yii::$app->session->setFlash('error', 'Не указаны телефоны.');
+            return $this->redirect(['index']);
+        }
+
+        foreach ($phones as $phone) {
+            $messageData = [
+                'phone' => $phone,
+                'message' => '',
+                'kogort_date' => date('Y-m-d'),
+                'target_date' => date('Y-m-d'),
+            ];
+            Yii::$app->queue->push(new SendWhatsappMessageJob([
+                'messageData' => $messageData,
+                'isTest' => true
+            ]));
+        }
+
+        Yii::$app->session->setFlash('success', 'Сообщения поставлены в очередь для отправки.');
+        return $this->redirect(['index']);
+    }
+
+    /**
+     * Вызывает метод получения канала по имени и возвращает результат в формате JSON.
+     */
+    public function actionGetChannel()
+    {
+        Yii::$app->response->format = Response::FORMAT_JSON;
+        $channelName = Yii::$app->request->post('channelName', '');
+        $result = WhatsAppService::getChannelByName($channelName);
+        return ['result' => $result];
+    }
+
+    /**
+     * Вызывает метод получения id каскада по имени и возвращает результат в формате JSON.
+     */
+    public function actionGetCascade()
+    {
+        Yii::$app->response->format = Response::FORMAT_JSON;
+        $cascadeName = Yii::$app->request->post('cascadeName', '');
+        $result = WhatsAppService::getCascadeIdByName($cascadeName);
+        return ['result' => $result];
+    }
+
+    /**
+     * Вызывает метод получения id шаблона по subjectId и имени шаблона и возвращает результат в формате JSON.
+     */
+    public function actionGetTemplate()
+    {
+        Yii::$app->response->format = Response::FORMAT_JSON;
+        $subjectId = Yii::$app->request->post('subjectId', '');
+        $templateName = Yii::$app->request->post('templateName', '');
+        $result = WhatsAppService::getMessageMatcherIdBySubjectId($subjectId, $templateName);
+        return ['result' => $result];
+    }
 }
diff --git a/erp24/jobs/SendWhatsappMessageJob.php b/erp24/jobs/SendWhatsappMessageJob.php
new file mode 100644 (file)
index 0000000..f25cfe0
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+
+namespace app\jobs;
+
+use yii\base\BaseObject;
+use yii\queue\JobInterface;
+use Yii;
+use yii_app\services\WhatsAppMessageResponse;
+use yii_app\services\WhatsAppService;
+use yii_app\records\UsersMessageManagement;
+use yii_app\records\UsersWhatsappMessage;
+
+/**
+ * Job для отправки сообщения WhatsApp.
+ *
+ * @property array $messageData Массив с данными сообщения. Ожидается наличие по крайней мере:
+ *      - phone: номер телефона получателя,
+ *      - kogort_date: дата когорты (опционально),
+ *      - target_date: целевая дата (опционально).
+ *
+ * @property bool $isTest тестовое ли сообщение
+ */
+class SendWhatsappMessageJob extends BaseObject implements JobInterface
+{
+    public $messageData;
+
+    public $isTest;
+
+    public static $messagesSent = 0;
+    public static $lastResetTime;
+
+    public function execute($queue)
+    {
+        // Rate limiting: не более 30 сообщений в секунду
+        if (!self::$lastResetTime || (microtime(true) - self::$lastResetTime) > 1) {
+            self::$lastResetTime = microtime(true);
+            self::$messagesSent = 0;
+        }
+        if (self::$messagesSent >= 30) {
+            $delay = 1 - (microtime(true) - self::$lastResetTime);
+            if ($delay > 0) {
+                usleep($delay * 1e6); // Спим оставшееся время в микросекундах
+            }
+            self::$lastResetTime = microtime(true);
+            self::$messagesSent = 0;
+        }
+
+        $phone = $this->messageData['phone'];
+
+        $apiKey    = Yii::$app->params['WHATSAPP_API_KEY'];
+        $cascadeId = WhatsAppService::getCascadeIdByName('WABA') ?? 5686;
+        $whatsappService = new WhatsAppService($apiKey, $cascadeId);
+        $requestId = uniqid();
+        try {
+
+            $message  = $this->messageData['message'];
+            if ($this->isTest) {
+                $message   = "Здравствуйте\n
+                Узнать подробности вы можете на нашем сайте https://bazacvetov24.ru.";
+            }
+            $response = $whatsappService->sendMessage($requestId, $phone, $message, $this->isTest);
+
+
+            $status = 'sent';
+            if (!$status instanceof WhatsAppMessageResponse) {
+                $status = $response ?? 'error';
+            }
+            $record = new UsersWhatsappMessage();
+            $record->request_id = $requestId;
+            $record->phone      = $phone;
+            $record->message    = $message;
+            $record->kogort_date = $this->messageData['kogort_date'];
+            $record->target_date = $this->messageData['target_date'];
+            $record->status     = $status;
+            $record->created_at = date('Y-m-d H:i:s');
+
+            if ($record->save()) {
+                Yii::warning("WhatsApp сообщение успешно отправлено и сохранено для телефона {$phone}. Request ID: " . $response->requestId, 'whatsapp');
+            } else {
+                Yii::warning("WhatsApp сообщение отправлено, но не удалось сохранить запись для телефона {$phone}.", 'whatsapp');
+            }
+        } catch (\Exception $e) {
+            Yii::error("Ошибка отправки WhatsApp сообщения для телефона {$phone}: " . $e->getMessage(), 'whatsapp');
+        }
+
+        self::$messagesSent++;
+    }
+}
\ No newline at end of file
diff --git a/erp24/media/controllers/WhatsappMessageStatus.php b/erp24/media/controllers/WhatsappMessageStatus.php
new file mode 100644 (file)
index 0000000..b11f23c
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+
+namespace app\controllers;
+
+use Yii;
+use yii\rest\Controller;
+use yii\web\BadRequestHttpException;
+use yii\web\MethodNotAllowedHttpException;
+use yii\web\ServerErrorHttpException;
+use yii_app\records\UsersWhatsappMessage;
+use yii\web\Response;
+
+class WhatsappMessageStatus extends Controller
+{
+    /**
+     * Принимает callback-статус сообщения от API edna и сохраняет новый статус в БД.
+     *
+     * Пример тела запроса:
+     * {
+     *   "requestId": "test-00135",
+     *   "cascadeId": 11,
+     *   "cascadeStageUUID": "001-test001",
+     *   "subject": "test_subject",
+     *   "subjectId": 2,
+     *   "status": "READ",
+     *   "statusAt": "2023-10-31T11:07:56Z",
+     *   "error": null,
+     *   "comment": null,
+     *   "paymentData": {
+     *      "@type": "WhatsAppConversationPaymentData",
+     *      "conversationId": "test0001",
+     *      "conversationType": "marketing",
+     *      "chargeable": true,
+     *      "type": "WHATSAPP_CONVERSATION"
+     *   }
+     * }
+     *
+     * @return array Ответ в формате JSON.
+     *
+     * @throws BadRequestHttpException Если отсутствует обязательный параметр или неверный JSON.
+     * @throws MethodNotAllowedHttpException Если запрос не методом POST.
+     * @throws ServerErrorHttpException Если не удалось сохранить запись.
+     */
+    public function actionCallbackStatus()
+    {
+        Yii::$app->response->format = Response::FORMAT_JSON;
+        $request = Yii::$app->request;
+
+        if (!$request->isPost) {
+            throw new MethodNotAllowedHttpException("Метод не разрешён. Используйте POST.");
+        }
+
+        $rawBody = $request->getRawBody();
+        $data = json_decode($rawBody, true);
+
+        if (json_last_error() !== JSON_ERROR_NONE) {
+            Yii::error("Неверный JSON: " . $rawBody, __METHOD__);
+            throw new BadRequestHttpException("Неверный формат JSON.");
+        }
+
+        if (empty($data['requestId'])) {
+            throw new BadRequestHttpException("Отсутствует обязательный параметр: requestId.");
+        }
+
+        $requestId = $data['requestId'];
+
+        $model = UsersWhatsappMessage::findOne(['request_id' => $requestId]);
+        if (!$model) {
+            Yii::warning("Запись с request_id {$requestId} не найдена.", __METHOD__);
+            return ['message' => "Запись с request_id {$requestId} не найдена."];
+        }
+
+        if (isset($data['status'])) {
+            $model->status = $data['status'];
+        } else {
+            throw new BadRequestHttpException("Отсутствует обязательный параметр: status.");
+        }
+
+        if ($model->save()) {
+            Yii::info("Статус сообщения с request_id {$requestId} обновлён на {$model->status}.", __METHOD__);
+            return ['message' => "Статус сообщения обновлён."];
+        } else {
+            Yii::error("Не удалось сохранить обновлённый статус для request_id {$requestId}.", __METHOD__);
+            throw new ServerErrorHttpException("Не удалось сохранить обновлённый статус.");
+        }
+    }
+}
\ No newline at end of file
diff --git a/erp24/services/WhatsAppMessageResponse.php b/erp24/services/WhatsAppMessageResponse.php
new file mode 100644 (file)
index 0000000..b1b4c29
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+namespace yii_app\services;
+
+/**
+ * Класс для представления ответа от API WhatsApp.
+ */
+class WhatsAppMessageResponse
+{
+    /**
+     * Идентификатор сообщения, сгенерированный на клиенте.
+     *
+     * @var string|null
+     */
+    public $requestId;
+
+    /**
+     * Конструктор на основе данных, полученных в ответе.
+     *
+     * @param array $data Ассоциативный массив с данными ответа.
+     */
+    public function __construct(array $data)
+    {
+        $this->requestId = $data['requestId'] ?? null;
+    }
+}
diff --git a/erp24/services/WhatsAppService.php b/erp24/services/WhatsAppService.php
new file mode 100644 (file)
index 0000000..7ad5f0a
--- /dev/null
@@ -0,0 +1,386 @@
+<?php
+
+namespace yii_app\services;
+
+use GuzzleHttp\Client;
+use GuzzleHttp\Exception\GuzzleException;
+use GuzzleHttp\Exception\RequestException;
+use Psr\Http\Message\ResponseInterface;
+use Yii;
+use yii\base\Arrayable;
+use yii\base\Exception;
+use yii_app\records\UsersMessageManagement;
+
+/**
+ * Сервис для отправки сообщений WhatsApp через API.
+ *
+ * Для массовой рассылки создаётся один экземпляр сервиса,
+ * а метод sendMessage принимает только номер телефона получателя.
+ * Текст сообщения (офер) берётся из таблицы UsersMessageManagement.
+ */
+class WhatsAppService
+{
+    private static $apiBaseUrl = 'https://app.edna.ru/api';
+
+    /**
+     * API-ключ для авторизации.
+     *
+     * @var string
+     */
+    private $apiKey;
+
+    /**
+     * Идентификатор каскада.
+     *
+     * @var mixed
+     */
+    private $cascadeId;
+
+    /**
+     * HTTP-клиент Guzzle.
+     *
+     * @var Client
+     */
+    private $client;
+
+    /**
+     * Конструктор.
+     *
+     * @param string $apiKey    API-ключ для авторизации.
+     * @param mixed  $cascadeId Идентификатор каскада для отправки сообщений.
+     */
+    public function __construct($apiKey, $cascadeId)
+    {
+        $this->apiKey    = $apiKey;
+        $this->cascadeId = $cascadeId;
+        $this->client    = new Client();
+    }
+
+    /**
+     *
+     * @param string $text Исходный текст.
+     *
+     * @return string Обработанный текст.
+     */
+    protected function escapeText($text)
+    {
+        return str_replace(
+            ['"', '“', '”', '‘', '’'],
+            ['\"', '\"', '\"', "\'", "\'"],
+            $text
+        );
+    }
+
+    /**
+     *
+     * @param int         $statusCode Код ответа HTTP.
+     * @param string|null $errorKey   Код ошибки, полученный из ответа API.
+     *
+     * @return string|null Описание ошибки или null, если соответствие не найдено.
+     */
+    protected function getErrorMessage($statusCode, $errorKey)
+    {
+        $errorMappings = [
+            400 => [
+                'requestId-is-not-unique' => 'Такой идентификатор запроса уже использовался. Используйте новый идентификатор для каждого запроса.',
+                'content-not-specified'   => 'Не указан тип контента и его свойства. Например, smsContent, viberContent или whatsappContent.',
+                'contentType-not-specified' => 'Не указан тип контента. Например, text или image.',
+                'text-not-specified'        => 'Не заполнено текстовое поле.',
+                'caption-not-specified'     => 'Не заполнено текстовое поле подписи.',
+                'action-not-specified'      => 'Не указано действие для кнопки.',
+                'attachmentName-not-specified' => 'Не указано имя прикрепляемого документа.',
+                'attachmentName-is-too-long'   => 'Имя прикрепляемого документа слишком длинное. Максимальная длина — 70 символов.',
+                'latitude-not-specified'    => 'Не задана широта при указании координат.',
+                'longitude-not-specified'   => 'Не задана долгота при указании координат.',
+                'cascade-not-found'         => 'Указан неверный идентификатор каскада. Проверьте корректность указанного вами идентификатора.',
+                'request-doesn’t-contain-content-for-all-cascade-stages' => 'Каскад содержит много каналов. Добавьте еще один канал в объект content запроса.',
+                'matched-template-not-found' => 'Схема тела запроса не соответствует схеме шаблона. Проверьте взаимное расположение и наличие всех свойств запроса.',
+                'cascade-scheduling-request-not-valid' => 'Переданный контент для каскада не соответствует настройкам каскада. Проверьте корректность заполнения данных.',
+                'template-parameter-is-not-valid' => 'Длина значений параметров listPicker.sections.items.title или listPicker.sections.items.subtitle превышает 24 символа с учетом пробелов.',
+                'out-of-balance'            => 'Недостаточно средств на балансе.',
+                'button-validation-error'   => 'Ошибка валидации шаблона WhatsApp с кнопками. Превышено максимальное количество кнопок или неверный тип кнопки.',
+            ],
+            401 => [
+                'auth-error' => 'Ошибка авторизации. Проверьте правильность написания и срок действия ключа API.',
+            ],
+            404 => [
+                'not-found' => 'Запрошенный URL-адрес не найден. Проверьте корректность указанного вами адреса.',
+            ],
+            405 => [
+                'method-not-allowed' => 'Метод HTTP-запроса не разрешен. Используйте POST, GET и другие запросы согласно документации.',
+            ],
+            500 => [
+                'system-error' => 'Ошибка сервера. Обратитесь в службу технической поддержки support@edna.ru.',
+            ],
+        ];
+
+        if (isset($errorMappings[$statusCode][$errorKey])) {
+            return $errorMappings[$statusCode][$errorKey];
+        }
+        return null;
+    }
+
+    /**
+     * Отправляет сообщение WhatsApp для заданного номера телефона.
+     *
+     * @param string      $phone     Номер телефона получателя (например, "79000000000").
+     * @param string|null $startTime Время отправки (ISO 8601) или null для немедленной отправки.
+     * @param string|null $ttl       Время жизни задания (например, 'PT1M').
+     *
+     * @return WhatsAppMessageResponse|null|string Объект с данными ответа API.
+     *
+
+     */
+    public function sendMessage($requestId, $phone, $message, $startTime = null, $ttl = null, $isTest = false)
+    {
+
+        if (!$message) {
+            Yii::error("Текст сообщения для WhatsApp не передан.");
+            return null;
+        }
+        $channel = self::getChannelByName('WABA');
+        $subjectId = $channel['subjectId'] ?? 11374;
+
+        $requestId = $requestId ?? uniqid();
+        // Формируем фильтр получателя по номеру телефона
+        $subscriberFilter = [
+            'address' => $phone,
+            'type'    => 'PHONE'
+        ];
+
+        $buttons =  [
+            'rows' => [
+                [
+                    'buttons' => [
+                        [
+                            'text'       => 'Наш сайт',
+                            'url'        => 'https://bazacvetov24.ru',
+                            'urlPostfix' => '',
+                            'type'       => 'URL'
+                        ],
+                        [
+                            'text'       => '1000 руб. забрать',
+                            'url'        => 'https://bazacvetov24.ru/akcii',
+                            'urlPostfix' => '',
+                            'type'       => 'URL'
+                        ]
+                    ]
+                ]
+            ]
+        ];
+        // Формируем содержимое WhatsApp-сообщения
+        $whatsappContent = [
+            'contentType' => 'TEXT',
+            'text'        => $this->escapeText($message),
+        ];
+
+        if (!$isTest) {
+            $whatsappContent['keyboard'] = $buttons;
+            $whatsappContent['messageMatcherId'] = self::getMessageMatcherIdBySubjectId($subjectId, 'kogort_message') ?? 120669;
+        } else {
+            $whatsappContent['messageMatcherId'] = self::getMessageMatcherIdBySubjectId($subjectId, 'podrobnosti') ?? 117216;
+        }
+
+
+        $payload = [
+            'requestId'        => $requestId,
+            'cascadeId'        => $this->cascadeId,
+            'subscriberFilter' => $subscriberFilter,
+            'content'          => [
+                'whatsappContent' => $whatsappContent,
+            ],
+            'errorIfNotMatched' => true,
+            'comment'          => '',
+            'priority'         => 'DEFAULT',
+        ];
+
+        if ($startTime !== null) {
+            $payload['startTime'] = $startTime;
+        }
+        if ($ttl !== null) {
+            $payload['ttl'] = $ttl;
+        }
+        $apiUrl = self::$apiBaseUrl . '/cascade/schedule';
+        try {
+            $response = $this->client->request('POST', $apiUrl, [
+                'headers' => [
+                    'Content-Type' => 'application/json',
+                    'X-API-KEY'    => $this->apiKey,
+                ],
+                'json' => $payload,
+            ]);
+
+            if ($response->getStatusCode() == 200) {
+                $data = json_decode($response->getBody(), true);
+                return new WhatsAppMessageResponse($data);
+            } else {
+                $data = json_decode($response->getBody(), true);
+                $errorKey    = $data['title'] ?? null;
+                $errorDetail = $data['detail'] ?? '';
+                $errorMessage = $this->getErrorMessage($response->getStatusCode(), $errorKey);
+                if (!$errorMessage) {
+                    $errorMessage = "Ошибка: код " . $response->getStatusCode() . ". " . $errorDetail;
+                }
+                Yii::error($errorMessage);
+                return $errorKey;
+            }
+        } catch (RequestException $e) {
+            Yii::error("Ошибка при выполнении запроса: " . $e->getMessage());
+            return null;
+        }
+    }
+
+
+    /**
+     * Получает subjectId канала по его имени.
+     *
+     * @param string $channelName Название канала (например, "WABA").
+     *
+     * @return int|null subjectId, если найден, иначе null.
+     *
+     */
+    public static function getChannelByName($channelName)
+    {
+        $apiKey = Yii::$app->params['WHATSAPP_API_KEY'];
+        $client = new Client();
+
+        $url = self::$apiBaseUrl . '/channel-profile?types=WHATSAPP';
+
+        try {
+            $response = $client->request('GET', $url, [
+                'headers' => [
+                    'Content-Type' => 'application/json',
+                    'X-API-KEY'    => $apiKey,
+                ],
+                'json' => (object)[]
+            ]);
+
+            if ($response->getStatusCode() == 200) {
+                $data = json_decode($response->getBody(), true);
+                foreach ($data as $channel) {
+                    if (isset($channel['name']) && $channel['name'] === $channelName) {
+                        return $channel ?? null;
+                    }
+                }
+                return null;
+            } else {
+                self::handleErrorResponse($response);
+                return null;
+            }
+        } catch (RequestException $e) {
+            Yii::error("Ошибка при выполнении запроса: " . $e->getMessage());
+            return null;
+        }
+    }
+
+    public static function getCascadeIdByName($cascadeName)
+    {
+        $apiKey = Yii::$app->params['WHATSAPP_API_KEY'];
+        $client = new Client();
+        $url = self::$apiBaseUrl . '/cascade/get-all';
+
+        try {
+            $response = $client->request('POST', $url, [
+                'headers' => [
+                    'Content-Type' => 'application/json',
+                    'X-API-KEY'    => $apiKey,
+                ],
+                'json' => (object)[]
+            ]);
+
+            if ($response->getStatusCode() == 200) {
+                $data = json_decode($response->getBody(), true);
+                if (!is_array($data)) {
+                    throw new Exception("Некорректный формат ответа.");
+                }
+                foreach ($data as $cascade) {
+                    if (
+                        isset($cascade['name'], $cascade['status'], $cascade['id']) &&
+                        $cascade['name'] === $cascadeName &&
+                        strtoupper($cascade['status']) === 'ACTIVE'
+                    ) {
+                        return $cascade['id'];
+                    }
+                }
+                return null;
+            } else {
+                self::handleErrorResponse($response);
+                return null;
+            }
+        } catch (RequestException $e) {
+            Yii::error("Ошибка при выполнении запроса: " . $e->getMessage());
+            return null;
+        }
+    }
+
+    public static function getMessageMatcherIdBySubjectId(
+        $subjectId,
+        $templateName,
+        $matcherTypes = ["OPERATOR", "USER", "CUSTOM"]
+    ) {
+        $apiKey = Yii::$app->params['WHATSAPP_API_KEY'];
+        $client = new Client();
+        $url = self::$apiBaseUrl . '/message-matchers/get-by-request';
+
+        try {
+            $response = $client->request('POST', $url, [
+                'headers' => [
+                    'Content-Type' => 'application/json',
+                    'X-API-KEY'    => $apiKey,
+                ],
+                'json' => [
+                    'subjectId'    => $subjectId,
+                    'matcherTypes' => $matcherTypes,
+                ]
+            ]);
+
+            if ($response->getStatusCode() == 200) {
+                $data = json_decode($response->getBody(), true);
+                if (!is_array($data)) {
+                    throw new Exception("Некорректный формат ответа.");
+                }
+
+                // Ищем шаблон с нужным именем и каналом WHATSAPP
+                foreach ($data as $matcher) {
+                    if (
+                        isset($matcher['name'], $matcher['channelType'], $matcher['id']) &&
+                        $matcher['name'] === $templateName &&
+                        strtoupper($matcher['channelType']) === 'WHATSAPP'
+                    ) {
+                        return $matcher['id'];
+                    }
+                }
+                return null;
+            } else {
+                self::handleErrorResponse($response);
+                return null;
+            }
+        } catch (RequestException $e) {
+            Yii::error("Ошибка при выполнении запроса: " . $e->getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * Обрабатывает ошибку ответа API.
+     *
+     * @param ResponseInterface $response
+     *
+     */
+    private static function handleErrorResponse($response)
+    {
+        $data = json_decode($response->getBody(), true);
+        $errorKey = $data['title'] ?? $data['error'] ?? 'unknown_error';
+        $errorDetail = $data['detail'] ?? '';
+
+        $errorMessages = [
+            'error-subject-unknown' => 'Указанное имя подписи отсутствует.',
+            'error-syntax' => 'Неверно указан тип канала.',
+        ];
+
+        $errorMessage = $errorMessages[$errorKey] ??
+            "Ошибка: " . $response->getStatusCode() . ". " . $errorKey . ". " . $errorDetail;
+        Yii::error($errorMessage);
+    }
+
+}
\ No newline at end of file
index f4579bae45514b65e60609c410d65204fb0436da..c8d3d2395bc22d81d923c40e36da5ffe836e3ffd 100644 (file)
@@ -10,15 +10,15 @@ use yii\grid\GridView;
 /** @var yii_app\records\UsersWhatsappMessageSearch $searchModel */
 /** @var yii\data\ActiveDataProvider $dataProvider */
 
-$this->title = 'Users Whatsapp Messages';
+$this->title = 'Сообщения из WA когорты';
 $this->params['breadcrumbs'][] = $this->title;
 ?>
-<div class="users-whatsapp-message-index">
+<div class="users-whatsapp-message-index p-4">
 
     <h1><?= Html::encode($this->title) ?></h1>
 
     <p>
-        <?= Html::a('Create Users Whatsapp Message', ['create'], ['class' => 'btn btn-success']) ?>
+        <?= Html::a('Тестировать', ['test'], ['class' => 'btn btn-primary']) ?>
     </p>
 
     <?php // echo $this->render('_search', ['model' => $searchModel]); ?>
diff --git a/erp24/views/users-whatsapp-message/test.php b/erp24/views/users-whatsapp-message/test.php
new file mode 100644 (file)
index 0000000..8d6de0f
--- /dev/null
@@ -0,0 +1,86 @@
+<?php
+
+use yii\helpers\Html;
+use yii\helpers\Url;
+use yii\widgets\ActiveForm;
+use yii\base\DynamicModel;
+
+$model = new DynamicModel([
+    'phones', 'channelName', 'cascadeName', 'subjectId', 'templateName'
+]);
+
+$model->addRule(['phones', 'channelName', 'cascadeName', 'subjectId', 'templateName'], 'safe');
+
+?>
+<div class="test-whatsapp col-6 p-4">
+<p>
+    <?= Html::a('Назад', ['index'], ['class' => 'btn btn-primary']) ?>
+</p>
+
+<?php $form = ActiveForm::begin(); ?>
+
+    <h3>Отправка сообщений</h3>
+<?= $form->field($model, 'phones')->textInput([
+    'maxlength' => true,
+    'placeholder' => 'Введите телефоны через запятую'
+]) ?>
+
+    <div class="form-group">
+        <?= Html::submitButton('Отправить сообщения', [
+            'class' => 'btn btn-primary',
+            'formaction' => Url::to(['users-whatsapp-message/send-messages'])
+        ]) ?>
+    </div>
+
+    <hr>
+
+    <h3>Получить канал по имени</h3>
+<?= $form->field($model, 'channelName')->textInput([
+    'maxlength' => true,
+    'placeholder' => 'Введите имя канала'
+]) ?>
+
+    <div class="form-group">
+        <?= Html::submitButton('Получить канал', [
+            'class' => 'btn btn-info',
+            'formaction' => Url::to(['users-whatsapp-message/get-channel'])
+        ]) ?>
+    </div>
+
+    <hr>
+
+    <h3>Получить каскад по имени</h3>
+<?= $form->field($model, 'cascadeName')->textInput([
+    'maxlength' => true,
+    'placeholder' => 'Введите имя каскада'
+]) ?>
+
+    <div class="form-group">
+        <?= Html::submitButton('Получить каскад', [
+            'class' => 'btn btn-info',
+            'formaction' => Url::to(['users-whatsapp-message/get-cascade'])
+        ]) ?>
+    </div>
+
+    <hr>
+
+    <h3>Получить шаблон по subjectId и имени шаблона</h3>
+<?= $form->field($model, 'subjectId')->textInput([
+    'maxlength' => true,
+    'placeholder' => 'Введите subjectId'
+]) ?>
+<?= $form->field($model, 'templateName')->textInput([
+    'maxlength' => true,
+    'placeholder' => 'Введите имя шаблона'
+]) ?>
+
+    <div class="form-group">
+        <?= Html::submitButton('Получить шаблон', [
+            'class' => 'btn btn-info',
+            'formaction' => Url::to(['users-whatsapp-message/get-template'])
+        ]) ?>
+    </div>
+
+<?php ActiveForm::end(); ?>
+
+</div>