From 00a327a176f25063f33cf134b1e699df8c5adfe0 Mon Sep 17 00:00:00 2001 From: fomichev Date: Thu, 6 Mar 2025 17:45:03 +0300 Subject: [PATCH] =?utf8?q?=D0=9D=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B9=D0=BA?= =?utf8?q?=D0=B8=20=D0=B2=D0=BE=D1=82=D1=81=D0=B0=D0=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- erp24/commands/CronController.php | 43 +++++- ...ings_to_users_message_management_table.php | 102 +++++++++++++ erp24/records/UsersMessageManagement.php | 32 ++++- erp24/services/WhatsAppService.php | 134 +++++++++++++++--- .../views/users-message-management/index.php | 36 ++++- 5 files changed, 320 insertions(+), 27 deletions(-) create mode 100644 erp24/migrations/m250305_121956_add_column_whatsapp_settings_to_users_message_management_table.php diff --git a/erp24/commands/CronController.php b/erp24/commands/CronController.php index 7f1c12db..a95b366a 100644 --- a/erp24/commands/CronController.php +++ b/erp24/commands/CronController.php @@ -841,20 +841,59 @@ class CronController extends Controller } } else { $this->stdout( - "Нет данных для отправки второго сообщения в телеграм + "Нет данных для отправки сообщения в Whatsapp на {$kogortDate} для целевой даты {$targetDate}.\n", BaseConsole::FG_RED ); } $this->stdout( - "Отправка второго сообщения в телеграм для корорты (ватсап) + "Отправка сообщения ватсап для корорты на {$kogortDate} для целевой даты {$targetDate}.\n", BaseConsole::FG_GREEN ); return ExitCode::OK; } + public function actionGetWhatsappMessageHistory() + { + $this->stdout("Получаем историю сообщений...\n", BaseConsole::FG_GREEN); + date_default_timezone_set('Europe/Moscow'); + $currentDate = date('Y-m-d'); + + $params = [ + "offset" => 0, + "limit" => 1000, + "channelTypes" => ["WHATSAPP"], + "direction" => "OUT", + "dateFrom" => $currentDate . "T00:00:00Z", + "dateTo" => $currentDate . "T23:59:59Z", + "sort" => [ + [ + "property" => "messageId", + "direction" => "DESC" + ] + ], + "subjectId" => 11374 + ]; + + $done = WhatsAppService::processMessagesHistoryAndUpdateStatuses($params); + + if ($done) { + $this->stdout( + "Сохранение статусов успешно завершено.\n", + BaseConsole::FG_GREEN + ); + return ExitCode::OK; + } else { + $this->stdout( + "Сохранение статусов не удалось.\n", + BaseConsole::FG_RED + ); + return ExitCode::DATAERR; + } + } + public function actionGenerateCallKogorts() { $messagesSettings = UsersMessageManagement::find()->one(); diff --git a/erp24/migrations/m250305_121956_add_column_whatsapp_settings_to_users_message_management_table.php b/erp24/migrations/m250305_121956_add_column_whatsapp_settings_to_users_message_management_table.php new file mode 100644 index 00000000..cf3ceeb7 --- /dev/null +++ b/erp24/migrations/m250305_121956_add_column_whatsapp_settings_to_users_message_management_table.php @@ -0,0 +1,102 @@ +db->schema->getTableSchema(self::TABLE_NAME) === null) { + return; + } + + if ($this->db->schema->getTableSchema(self::TABLE_NAME)->getColumn('channel_name') === null) { + $this->addColumn( + self::TABLE_NAME, + 'channel_name', + $this->string()->null()->comment('Имя канала') + ); + $this->addColumn( + self::TABLE_NAME, + 'channel_id', + $this->string()->null()->comment('Идентификатор канала - подпись') + ); + $this->addColumn( + self::TABLE_NAME, + 'cascade_name', + $this->string()->null()->comment('Имя каскада') + ); + $this->addColumn( + self::TABLE_NAME, + 'channel_limit', + $this->integer()->null()->comment('Идентификатор шаблона') + ); + $this->addColumn( + self::TABLE_NAME, + 'cascade_id', + $this->integer()->null()->comment('ID каскада') + ); + $this->addColumn( + self::TABLE_NAME, + 'subject_id', + $this->integer()->null()->comment('Идентификатор подписи') + ); + $this->addColumn( + self::TABLE_NAME, + 'template_name', + $this->string()->null()->comment('Имя шаблона') + ); + $this->addColumn( + self::TABLE_NAME, + 'template_id', + $this->integer()->null()->comment('Идентификатор шаблона') + ); + $this->addColumn( + self::TABLE_NAME, + 'callback_status_url', + $this->string()->null()->comment('URL приема колбеков статусов сообщений') + ); + } + } + + /** + * {@inheritdoc} + */ + public function safeDown() + { + if ($this->db->schema->getTableSchema(self::TABLE_NAME) === null) { + return; + } + + if ($this->db->schema->getTableSchema(self::TABLE_NAME)->getColumn('channel_name') !== null) { + $this->dropColumn(self::TABLE_NAME, 'channel_name'); + $this->dropColumn(self::TABLE_NAME, 'channel_id'); + $this->dropColumn(self::TABLE_NAME, 'channel_limit'); + $this->dropColumn(self::TABLE_NAME, 'cascade_name'); + $this->dropColumn(self::TABLE_NAME, 'cascade_id'); + $this->dropColumn(self::TABLE_NAME, 'subject_id'); + $this->dropColumn(self::TABLE_NAME, 'template_name'); + $this->dropColumn(self::TABLE_NAME, 'template_id'); + $this->dropColumn(self::TABLE_NAME, 'callback_status_url'); + } + } + + /* + // Use up()/down() to run migration code without a transaction. + public function up() + { + + } + + public function down() + { + echo "m250305_121956_add_column_whatsapp_settings_to_users_message_management_table cannot be reverted.\n"; + + return false; + } + */ +} diff --git a/erp24/records/UsersMessageManagement.php b/erp24/records/UsersMessageManagement.php index a8b69e79..542dfc68 100644 --- a/erp24/records/UsersMessageManagement.php +++ b/erp24/records/UsersMessageManagement.php @@ -32,6 +32,7 @@ use Yii; * @property string|null $date_end Дата и время завершения события * @property string $test_phones_list Список тестовых телефонов * @property string $test_phones_active Активность тестового списка + * @property string $channel_name Имя канала */ class UsersMessageManagement extends \yii\db\ActiveRecord { @@ -69,16 +70,17 @@ class UsersMessageManagement extends \yii\db\ActiveRecord [['bonus', 'day_before_step1', 'day_before_step2', 'day_before_step3', 'date_start', 'offer_1', 'offer_2', 'offer_whatsapp', 'offer_text', 'date_last_scenario', 'created_at', 'created_by', 'updated_at', 'updated_by', 'hold'], 'required'], [['bonus'], 'number'], [['day_before_step1', 'day_before_step2', 'day_before_step3', 'created_by', 'updated_by', - 'hold', 'hold_active', 'day_before_step1_active', 'day_before_step2_active', 'day_before_step3_active', 'active'], 'default', 'value' => null], - [['day_before_step1', 'day_before_step2', 'day_before_step3', 'created_by', 'updated_by', - 'hold', 'hold_active', 'day_before_step1_active', 'day_before_step2_active', 'day_before_step3_active', 'active', 'test_phones_active'], 'integer'], - [['date_start', 'date_last_scenario', 'created_at', 'updated_at', 'date_end', 'test_phones_list'], 'safe'], - [['offer_1', 'offer_2', 'offer_3', 'offer_whatsapp', 'offer_text', 'date_end', 'test_phones_list'], 'string'], + 'hold', 'hold_active', 'day_before_step1_active', 'day_before_step2_active', 'day_before_step3_active', 'active', + 'cascade_id', 'subject_id', 'template_id', 'channel_limit', 'test_phones_active'], 'integer'], + [['date_start', 'date_last_scenario', 'created_at', 'updated_at', 'date_end'], 'safe'], + [['offer_1', 'offer_2', 'offer_3', 'offer_whatsapp', 'offer_text', 'test_phones_list', + 'channel_name', 'channel_id', 'cascade_name', 'template_name', 'callback_status_url'], 'string'], [['offer_1', 'offer_2'], 'string', 'max' => 10000, 'tooLong' => '{attribute} должно содержать не более 10000 символов'], [['offer_whatsapp', 'offer_text'], 'string', 'max' => 900, 'tooLong' => '{attribute} должно содержать не более 900 символов'], ]; } + public function attributes() { return array_merge(parent::attributes(), ['bonus_action']); @@ -116,6 +118,15 @@ class UsersMessageManagement extends \yii\db\ActiveRecord 'bonus_action' => 'Срок действия бонуса', 'test_phones_list' => 'Список тестовых телефонов', 'test_phones_active' => 'Активность тестовой рассылки', + 'channel_name' => 'Имя канала', + 'channel_id' => 'Идентификатор канала - подпись', + 'channel_limit' => 'Суточный лимит сообщений', + 'cascade_name' => 'Имя каскада', + 'cascade_id' => 'ID каскада', + 'subject_id' => 'Идентификатор подписи', + 'template_name' => 'Имя шаблона', + 'template_id' => 'Идентификатор шаблона', + 'callback_status_url' => 'URL приема колбеков статусов сообщений', ]; } @@ -133,9 +144,18 @@ class UsersMessageManagement extends \yii\db\ActiveRecord { $bonusActivity = $this->getBonusAction(); $step1 = $this->day_before_step1; + $step2 = $this->day_before_step2; + $startDate = date('d.m.Y', strtotime("-$step1 days", strtotime($targetDate))); $validDate = date('d.m.Y', strtotime("+$bonusActivity days", strtotime($startDate))); + $message = str_replace('[NumberOfBonuses]', $this->bonus, $message); - return str_replace('[ValidityOfBonuses]', $validDate, $message); + $message = str_replace('[ValidityOfBonuses]', $validDate, $message); + $message = str_replace('[StepTwoDaysBeforeTarget]', $step2, $message); + + // Нормализуем переводы строк: заменяем \r\n на \n + $message = str_replace("\r\n", "\n", $message); + + return $message; } } diff --git a/erp24/services/WhatsAppService.php b/erp24/services/WhatsAppService.php index fe9d0242..14f277cc 100644 --- a/erp24/services/WhatsAppService.php +++ b/erp24/services/WhatsAppService.php @@ -3,14 +3,13 @@ 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\helpers\DataHelper; use yii_app\records\UsersMessageManagement; +use yii_app\records\UsersWhatsappMessage; /** * Сервис для отправки сообщений WhatsApp через API. @@ -134,7 +133,6 @@ class WhatsAppService */ public function sendMessage($requestId, $phone, $message, $isTest = false, $startTime = null, $ttl = null) { - if (!$message) { Yii::error("Текст сообщения для WhatsApp не передан."); return null; @@ -151,22 +149,18 @@ class WhatsAppService $buttons = [ 'rows' => [ - [ 'buttons' => [ [ 'text' => 'Наш сайт', 'url' => 'https://bazacvetov24.ru', - 'urlPostfix' => '', 'type' => 'URL' ], [ 'text' => '1000 руб. забрать', 'url' => 'https://bazacvetov24.ru/akcii', - 'urlPostfix' => '', 'type' => 'URL' ] ] - ] ] ]; // Формируем содержимое WhatsApp-сообщения @@ -176,8 +170,8 @@ class WhatsAppService ]; if (!$isTest) { - $whatsappContent['keyboard'] = $buttons; - $whatsappContent['messageMatcherId'] = self::getMessageMatcherIdBySubjectId($subjectId, 'kogort_message') ?? 120669; + // $whatsappContent['keyboard'] = $buttons; + $whatsappContent['messageMatcherId'] = self::getMessageMatcherIdBySubjectId($subjectId, 'kogort_message_3') ?? 121254; } else { $whatsappContent['messageMatcherId'] = self::getMessageMatcherIdBySubjectId($subjectId, 'podrobnosti') ?? 117216; } @@ -267,9 +261,8 @@ class WhatsAppService return null; } else { self::handleErrorResponse($response); - return null; } - } catch (RequestException $e) { + } catch (Exception $e) { Yii::error("Ошибка при выполнении запроса: " . $e->getMessage()); return null; } @@ -307,9 +300,8 @@ class WhatsAppService return null; } else { self::handleErrorResponse($response); - return null; } - } catch (RequestException $e) { + } catch (Exception $e) { Yii::error("Ошибка при выполнении запроса: " . $e->getMessage()); return null; } @@ -355,9 +347,8 @@ class WhatsAppService return null; } else { self::handleErrorResponse($response); - return null; } - } catch (RequestException $e) { + } catch (Exception $e) { Yii::error("Ошибка при выполнении запроса: " . $e->getMessage()); return null; } @@ -376,13 +367,122 @@ class WhatsAppService $errorDetail = $data['detail'] ?? ''; $errorMessages = [ + // Ошибки из других методов API: 'error-subject-unknown' => 'Указанное имя подписи отсутствует.', - 'error-syntax' => 'Неверно указан тип канала.', + 'error-syntax' => 'Неверно указан тип канала.', + // Дополнительные ошибки для метода messages/history: + 'Api-key not found' => 'Указан неверный API-ключ.', + 'not-valid-request' => 'Указано пустое значение параметра address.', + 'limit-not-valid' => 'Указано значение больше 1000 в параметре limit.', + 'date-range-not-valid' => 'Диапазон значений между параметрами dateFrom и dateTo превышает 366 дней.', + 'message-matcher-subject-not-found' => 'Указан неверный идентификатор в значении параметра subjectId.', ]; $errorMessage = $errorMessages[$errorKey] ?? "Ошибка: " . $response->getStatusCode() . ". " . $errorKey . ". " . $errorDetail; + Yii::error($errorMessage); + throw new Exception($errorMessage); + } + + /** + * Получает историю исходящих сообщений по каналу WHATSAPP. + * + * @param array $params Массив параметров запроса, например: + * [ + * "offset" => 0, + * "limit" => 1000, + * "channelTypes" => ["WHATSAPP"], + * "direction" => "OUT", + * "dateFrom" => "2025-03-05T00:00:00Z", + * "dateTo" => "2025-03-05T23:59:59Z", + * "sort" => [ + * [ + * "property" => "messageId", + * "direction" => "DESC" + * ] + * ], + * "subjectId" => 11374 + * ] + * + * @return array Массив сообщений, если запрос выполнен успешно. + * + * @throws \Exception При ошибке выполнения запроса. + */ + public static function getMessagesHistoryByDate($params) + { + $apiKey = Yii::$app->params['WHATSAPP_API_KEY']; + $client = new Client(); + $url = self::$apiBaseUrl . '/messages/history'; + + try { + $response = $client->request('POST', $url, [ + 'headers' => [ + 'Content-Type' => 'application/json', + 'X-API-KEY' => $apiKey, + ], + 'json' => $params, + ]); + + if ($response->getStatusCode() == 200) { + $data = json_decode($response->getBody(), true, 512); + Yii::error("Контент 200: " . json_encode($data, JSON_PRETTY_PRINT)); + return $data['content']; + } else { + Yii::error("Контент: " . json_encode($response, JSON_PRETTY_PRINT)); + self::handleErrorResponse($response); + } + } catch (Exception $e) { + Yii::error("Ошибка при выполнении запроса: " . $e->getMessage()); + return []; + } + } + + public static function processMessagesHistoryAndUpdateStatuses($params) + { + $messagesHistory = self::getMessagesHistoryByDate($params); + if (!is_array($messagesHistory)) { + Yii::error("Некорректный формат истории сообщений"); + return false; + } + + foreach ($messagesHistory as $message) { + if ( + isset($message['address']) && + isset($message['sentOrReceivedAt']) && + isset($message['deliveryStatus']) + ) { + $phone = $message['address']; + try { + $dateTime = new \DateTime($message['sentOrReceivedAt']); + } catch (\Exception $e) { + Yii::error("Ошибка преобразования даты из сообщения: " . $e->getMessage()); + continue; + } + $dateTime->modify('+3 hours'); + $formattedTime = $dateTime->format('Y-m-d H:i'); + + $record = UsersWhatsappMessage::find() + ->where(['phone' => $phone]) + ->andWhere("TO_CHAR(created_at, 'YYYY-MM-DD HH24:MI') = :sentTime", [':sentTime' => $formattedTime]) + ->one(); + + if ($record) { + $record->status = $message['deliveryStatus']; + if (!$record->save(false)) { + Yii::error("Не удалось сохранить статус для сообщения с телефоном {$phone} и временем {$formattedTime}"); + } else { + Yii::warning("Статус для сообщения с телефоном {$phone} успешно обновлён на {$message['deliveryStatus']}"); + } + } else { + Yii::warning("Запись с телефоном {$phone} и временем {$formattedTime} не найдена."); + } + } else { + Yii::warning("Некорректный формат сообщения: " . json_encode($message)); + } + } + + return true; } -} \ No newline at end of file +} diff --git a/erp24/views/users-message-management/index.php b/erp24/views/users-message-management/index.php index 0e266940..8c53a387 100644 --- a/erp24/views/users-message-management/index.php +++ b/erp24/views/users-message-management/index.php @@ -26,6 +26,17 @@ $this->registerCss(' ?> + +
session->hasFlash('error')): ?> @@ -152,7 +163,7 @@ $this->registerCss('
- Второй этап + Второй этап [StepTwoDaysBeforeTarget]
@@ -258,7 +269,28 @@ $this->registerCss(' field($model, 'offer_whatsapp') ->textarea(['rows' => 7]) ->hint('Используйте [NumberOfBonuses] для отображения количества бонусов. -
Для даты окончания действия бонусов - [ValidityOfBonuses].') ?> +
Для даты окончания действия бонусов - [ValidityOfBonuses]. +
Для дней до окончания акции [StepTwoDaysBeforeTarget]') ?> + +
+ +
+
+ field($model, 'channel_name')->textInput()->label('Имя канала') ?> + field($model, 'channel_id')->textInput()->label('Идентификатор канала - подпись') ?> + field($model, 'channel_limit')->textInput(['type' => 'number'])->label('Суточный лимит сообщений') ?> + field($model, 'cascade_name')->textInput()->label('Имя каскада') ?> + field($model, 'cascade_id')->textInput(['type' => 'number'])->label('ID каскада') ?> + field($model, 'subject_id')->textInput(['type' => 'number'])->label('Идентификатор подписи') ?> + field($model, 'template_name')->textInput()->label('Имя шаблона') ?> + field($model, 'template_id')->textInput(['type' => 'number'])->label('Идентификатор шаблона') ?> + field($model, 'callback_status_url')->textInput()->label('URL приема колбеков статусов сообщений') ?> +
+
+
field($model, 'offer_text') -- 2.39.5