]> gitweb.erp-flowers.ru Git - erp24_rep/yii-erp24/.git/commitdiff
Отправка почты
authorVladimir Fomichev <vladimir.fomichev@erp-flowers.ru>
Mon, 19 Jan 2026 11:09:47 +0000 (14:09 +0300)
committerVladimir Fomichev <vladimir.fomichev@erp-flowers.ru>
Mon, 19 Jan 2026 11:09:47 +0000 (14:09 +0300)
erp24/.env.example
erp24/config/console.php
erp24/config/params.php
erp24/config/web.php
erp24/services/OrderControlReportService.php
erp24/services/dto/OrderIssue.php

index 5e391bb098e1895416d4a0b189d63a508e5a29ea..25a6251fc2cdc785680afee58ef1a307432eec64 100644 (file)
@@ -1,3 +1,19 @@
 APP_ENV=development
 SERVER_NAME=local-fomichev
-RABBIT_HOST=rabbitmq-yii_erp24
\ No newline at end of file
+RABBIT_HOST=rabbitmq-yii_erp24
+
+# === SMTP Configuration ===
+# Для отправки email из консольных команд (отчёты, уведомления)
+MAIL_SCHEME=smtp
+MAIL_HOST=smtp.yandex.ru
+MAIL_PORT=465
+MAIL_USERNAME=noreply@bazacvetov24.ru
+MAIL_PASSWORD=your_smtp_password_here
+MAIL_ENCRYPTION=ssl
+
+# === Order Control Report (ERP-36J) ===
+# Telegram chat_id для dev/prod
+TELEGRAM_ORDER_CONTROL_CHAT_ID_DEV=-1001861631125
+TELEGRAM_ORDER_CONTROL_CHAT_ID_PROD=
+# Email получатели (через запятую)
+ORDER_CONTROL_EMAIL_RECIPIENTS=ekaterina.geldak@bazacvetov24.ru,irina.rogacheva@bazacvetov24.ru,alena.chelyshkina@bazacvetov24.ru
\ No newline at end of file
index 5b0fcb51dfd590054ebb441da2225dc3b341d39a..648d4f34bf0e0fd191c81e80d4d9765b59070ef1 100755 (executable)
@@ -53,6 +53,23 @@ $config = [
         'cache' => [
             'class' => 'yii\caching\FileCache',
         ],
+        // Mailer для отправки email (консольные команды)
+        'mailer' => [
+            'class' => \yii\symfonymailer\Mailer::class,
+            'viewPath' => '@app/mail',
+            // В dev-режиме письма сохраняются в файл, в prod — отправляются через SMTP
+            //'useFileTransport' => getenv('APP_ENV') === 'development',
+            'useFileTransport' => false,
+            // SMTP-транспорт для production (настраивается через .env)
+            'transport' => [
+                'scheme' => getenv('MAIL_SCHEME') ?: 'smtp',
+                'host' => getenv('MAIL_HOST') ?: 'smtp.yandex.ru',
+                'port' => (int)(getenv('MAIL_PORT') ?: 465),
+                'username' => getenv('MAIL_USERNAME') ?: 'flow@bazacvetov24.ru',
+                'password' => getenv('MAIL_PASSWORD') ?: 'ctqamxqeshgxwsgn',
+                'encryption' => getenv('MAIL_ENCRYPTION') ?: 'ssl',
+            ],
+        ],
         'log' => [
             'targets' => [
                 [
index 8997e90821d8e16a2e03a184fbec663a66d59b17..b1b88e098b218a7897bbe36a4da7c7400591031a 100644 (file)
@@ -18,16 +18,21 @@ return [
     'YANDEX_MARKET_API_KEY' => 'ACMA:r3sa2VyjkgcO0aOxGoyAWuGH15g5mWAqXRMuylVA:a0bccb7e',
     'RABBIT_HOST' => getenv('RABBIT_HOST') ?: 'localhost',
 
-    // Отчёт о непробитых чеках маркетплейсов
-    'MARKETPLACE_UNCHECKED_ORDERS_REPORT' => [
+    // Отчёт контроля статусов заказов маркетплейсов (ТЗ ERP-36J)
+    // Расписание: 08:00 и 20:00 MSK
+    // Типы проблем: "Завис в доставке", "Успех без чека", "Отмена без обработки"
+    'MARKETPLACE_ORDER_CONTROL_REPORT' => [
         'period_hours' => 12,
         'timezone' => 'Europe/Moscow',
         'max_retries' => 3,
         'retry_delay_seconds' => 5,
         'telegram_max_message_length' => 4000,
-        'telegram_chat_id_dev' => getenv('TELEGRAM_UNCHECKED_ORDERS_CHAT_ID_DEV') ?: '-1001861631125',
-        'telegram_chat_id_prod' => getenv('TELEGRAM_UNCHECKED_ORDERS_CHAT_ID_PROD') ?: '4886272326',
-        'email_recipients' => array_filter(explode(',', getenv('UNCHECKED_ORDERS_EMAIL_RECIPIENTS') ?: 'vladimir.fomichev@erp-flowers.ru')),
-        'email_subject' => 'Отчёт о заказах с непробитыми чеками',
+        // Telegram: chat_id канала (получить через @userinfobot или API после вступления бота в канал)
+        // Канал по ТЗ: https://t.me/+wHh_lW83AvVlYWNi
+        'telegram_chat_id_dev' => getenv('TELEGRAM_ORDER_CONTROL_CHAT_ID_DEV') ?: '-1001861631125',
+        'telegram_chat_id_prod' => getenv('TELEGRAM_ORDER_CONTROL_CHAT_ID_PROD') ?: '4886272326',
+        // Email получатели по ТЗ
+        'email_recipients' => array_filter(explode(',', getenv('ORDER_CONTROL_EMAIL_RECIPIENTS') ?: 'vladimir.fomichev@erp-flowers.ru,ekaterina.geldak@bazacvetov24.ru,irina.rogacheva@bazacvetov24.ru,alena.chelyshkina@bazacvetov24.ru')),
+        'email_subject' => '[Контроль MP] Отчёт о расхождениях статусов заказов',
     ],
 ];
index e58b6228d003f6dd64e6ee3e8f174a3bb82f4536..4cb53a6b3b5bb718e52e43f824ef531a0d7cfa5b 100644 (file)
@@ -68,11 +68,21 @@ $config = [
         'errorHandler' => [
             'errorAction' => 'site/error',
         ],
+        // Mailer для отправки email
         'mailer' => [
             'class' => \yii\symfonymailer\Mailer::class,
             'viewPath' => '@app/mail',
-            // send all mails to a file by default.
-            'useFileTransport' => true,
+            // В dev-режиме письма сохраняются в файл, в prod — отправляются через SMTP
+            'useFileTransport' => getenv('APP_ENV') === 'development',
+            // SMTP-транспорт для production (настраивается через .env)
+            'transport' => [
+                'scheme' => getenv('MAIL_SCHEME') ?: 'smtp',
+                'host' => getenv('MAIL_HOST') ?: 'smtp.yandex.ru',
+                'port' => (int)(getenv('MAIL_PORT') ?: 465),
+                'username' => getenv('MAIL_USERNAME') ?: '',
+                'password' => getenv('MAIL_PASSWORD') ?: '',
+                'encryption' => getenv('MAIL_ENCRYPTION') ?: 'ssl',
+            ],
         ],
         'log' => [
             'traceLevel' => 3,
index 3adaa31e02bdf6df37565e76dd724163e4cd7684..7e702fed34fbca06ef943b27e36f42299259061f 100644 (file)
@@ -191,17 +191,17 @@ class OrderControlReportService
                 mo.total,
                 mo.creation_date,
                 mo.status_processing_1c as rmk_status_id,
-                mocs.name as rmk_status,
+                mocs.status as rmk_status,
                 most.code as mp_status_code,
                 mosub.code as mp_substatus_code,
                 most.name as mp_status_name
             FROM marketplace_orders mo
             LEFT JOIN city_store cs ON cs.id = mo.store_id
-            LEFT JOIN marketplace_order_1c_statuses mocs ON mocs.id::text = mo.status_processing_1c
+            LEFT JOIN marketplace_order_1c_statuses mocs ON mocs.id = mo.status_processing_1c::integer
             LEFT JOIN marketplace_order_status_types most ON most.id = mo.status_id
             LEFT JOIN marketplace_order_status_types mosub ON mosub.id = mo.substatus_id
             WHERE mo.fake = 0
-              AND mo.status_processing_1c IN (:rmk_1004, :rmk_1011)
+              AND mo.status_processing_1c::integer IN (:rmk_1004, :rmk_1011)
               AND mo.updated_at >= :start_date
               AND (
                   most.code IS NULL
@@ -211,8 +211,8 @@ class OrderControlReportService
         ";
 
         $orders = Yii::$app->db->createCommand($sql, [
-            ':rmk_1004' => '1004',
-            ':rmk_1011' => '1011',
+            ':rmk_1004' => 1004,
+            ':rmk_1011' => 1011,
             ':start_date' => $startDateStr,
             ':delivered' => MarketplaceOrderStatusTypes::DELIVERED_CODE,
             ':delivery_service_delivered' => MarketplaceOrderStatusTypes::DELIVERY_SERVICE_DELIVERED_CODE,
@@ -359,13 +359,13 @@ class OrderControlReportService
                 mo.total,
                 mo.creation_date,
                 mo.status_processing_1c as rmk_status_id,
-                mocs.name as rmk_status,
+                mocs.status as rmk_status,
                 most.code as mp_status_code,
                 mosub.code as mp_substatus_code,
                 most.name as mp_status_name
             FROM marketplace_orders mo
             LEFT JOIN city_store cs ON cs.id = mo.store_id
-            LEFT JOIN marketplace_order_1c_statuses mocs ON mocs.id::text = mo.status_processing_1c
+            LEFT JOIN marketplace_order_1c_statuses mocs ON mocs.id = mo.status_processing_1c::integer
             LEFT JOIN marketplace_order_status_types most ON most.id = mo.status_id
             LEFT JOIN marketplace_order_status_types mosub ON mosub.id = mo.substatus_id
             WHERE mo.fake = 0
@@ -376,7 +376,7 @@ class OrderControlReportService
               )
               AND (
                   mo.status_processing_1c IS NULL
-                  OR mo.status_processing_1c NOT IN (:rmk_1005, :rmk_1012)
+                  OR mo.status_processing_1c::integer NOT IN (:rmk_1005, :rmk_1012)
               )
             ORDER BY cs.name ASC, mo.creation_date DESC
         ";
@@ -385,8 +385,8 @@ class OrderControlReportService
             ':start_date' => $startDateStr,
             ':delivered' => MarketplaceOrderStatusTypes::DELIVERED_CODE,
             ':delivery_service_delivered' => MarketplaceOrderStatusTypes::DELIVERY_SERVICE_DELIVERED_CODE,
-            ':rmk_1005' => '1005',
-            ':rmk_1012' => '1012',
+            ':rmk_1005' => 1005,
+            ':rmk_1012' => 1012,
         ])->queryAll();
 
         $issues = [];
@@ -428,13 +428,13 @@ class OrderControlReportService
                 mo.total,
                 mo.creation_date,
                 mo.status_processing_1c as rmk_status_id,
-                mocs.name as rmk_status,
+                mocs.status as rmk_status,
                 most.code as mp_status_code,
                 mosub.code as mp_substatus_code,
                 most.name as mp_status_name
             FROM marketplace_orders mo
             LEFT JOIN city_store cs ON cs.id = mo.store_id
-            LEFT JOIN marketplace_order_1c_statuses mocs ON mocs.id::text = mo.status_processing_1c
+            LEFT JOIN marketplace_order_1c_statuses mocs ON mocs.id = mo.status_processing_1c::integer
             LEFT JOIN marketplace_order_status_types most ON most.id = mo.status_id
             LEFT JOIN marketplace_order_status_types mosub ON mosub.id = mo.substatus_id
             WHERE mo.fake = 0
@@ -442,7 +442,7 @@ class OrderControlReportService
               AND most.code = :cancelled
               AND (
                   mo.status_processing_1c IS NULL
-                  OR mo.status_processing_1c NOT IN (:rmk_1006, :rmk_1013)
+                  OR mo.status_processing_1c::integer NOT IN (:rmk_1006, :rmk_1013)
               )
             ORDER BY cs.name ASC, mo.creation_date DESC
         ";
@@ -450,8 +450,8 @@ class OrderControlReportService
         $orders = Yii::$app->db->createCommand($sql, [
             ':start_date' => $startDateStr,
             ':cancelled' => MarketplaceOrderStatusTypes::CANSELLED_CODE,
-            ':rmk_1006' => '1006',
-            ':rmk_1013' => '1013',
+            ':rmk_1006' => 1006,
+            ':rmk_1013' => 1013,
         ])->queryAll();
 
         $issues = [];
@@ -855,25 +855,43 @@ class OrderControlReportService
             return false;
         }
 
+        // Диагностика конфигурации mailer
+        $this->logMailerDiagnostics($validRecipients);
+
         $sent = false;
+        $lastError = null;
         $maxRetries = $this->config['max_retries'] ?? self::MAX_RETRIES;
         $retryDelay = $this->config['retry_delay_seconds'] ?? self::RETRY_DELAY_SECONDS;
         $subject = $this->config['email_subject'] ?? 'Контроль статусов заказов МП';
 
         for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
             try {
-                $sent = Yii::$app->mailer->compose()
+                $message = Yii::$app->mailer->compose()
                     ->setTo($validRecipients)
                     ->setSubject($subject)
-                    ->setHtmlBody($html)
-                    ->send();
+                    ->setHtmlBody($html);
+
+                // Устанавливаем отправителя, если настроен
+                $fromEmail = getenv('MAIL_USERNAME') ?: 'noreply@bazacvetov24.ru';
+                $message->setFrom([$fromEmail => 'ERP24 Контроль МП']);
+
+                $sent = $message->send();
 
                 if ($sent) {
                     $this->logInfo('Email отправлен на: ' . implode(', ', $validRecipients));
                     break;
+                } else {
+                    $lastError = 'Метод send() вернул false без исключения';
+                    $this->logWarning("Email попытка {$attempt}/{$maxRetries}: {$lastError}");
                 }
             } catch (\Exception $e) {
-                $this->logWarning("Email попытка {$attempt}/{$maxRetries}: {$e->getMessage()}");
+                $lastError = $e->getMessage();
+                $this->logError("Email попытка {$attempt}/{$maxRetries}", [
+                    'error' => $e->getMessage(),
+                    'code' => $e->getCode(),
+                    'file' => $e->getFile() . ':' . $e->getLine(),
+                    'trace' => array_slice(explode("\n", $e->getTraceAsString()), 0, 5),
+                ]);
             }
 
             if ($attempt < $maxRetries) {
@@ -882,12 +900,52 @@ class OrderControlReportService
         }
 
         if (!$sent) {
-            $this->logError('Не удалось отправить email после ' . $maxRetries . ' попыток');
+            $this->logError('Не удалось отправить email после ' . $maxRetries . ' попыток', [
+                'last_error' => $lastError,
+                'recipients' => $validRecipients,
+            ]);
         }
 
         return $sent;
     }
 
+    /**
+     * Логирует диагностику конфигурации mailer
+     *
+     * @param array $recipients Получатели
+     */
+    private function logMailerDiagnostics(array $recipients): void
+    {
+        $mailer = Yii::$app->mailer;
+
+        $diagnostics = [
+            'mailer_class' => get_class($mailer),
+            'use_file_transport' => $mailer->useFileTransport ?? 'не определено',
+            'recipients' => $recipients,
+            'env' => [
+                'YII_ENV' => YII_ENV,
+                'YII_ENV_DEV' => YII_ENV_DEV ? 'true' : 'false',
+                'MAIL_HOST' => getenv('MAIL_HOST') ?: '(не задан)',
+                'MAIL_PORT' => getenv('MAIL_PORT') ?: '(не задан)',
+                'MAIL_USERNAME' => getenv('MAIL_USERNAME') ? '***настроен***' : '(не задан)',
+                'MAIL_PASSWORD' => getenv('MAIL_PASSWORD') ? '***настроен***' : '(не задан)',
+                'MAIL_ENCRYPTION' => getenv('MAIL_ENCRYPTION') ?: '(не задан)',
+            ],
+        ];
+
+        // Получаем конфигурацию транспорта, если доступна
+        if (method_exists($mailer, 'getTransport')) {
+            try {
+                $transport = $mailer->getTransport();
+                $diagnostics['transport_class'] = get_class($transport);
+            } catch (\Exception $e) {
+                $diagnostics['transport_error'] = $e->getMessage();
+            }
+        }
+
+        $this->logInfo('Email диагностика mailer', $diagnostics);
+    }
+
     /**
      * Разбивает длинное сообщение на части для Telegram
      *
index c4b57e409973a0137c9f495bee76f5190908a7cf..9a732f8c5a353a79afc1027d8e6eccdbc60701a6 100644 (file)
@@ -169,7 +169,7 @@ class OrderIssue
         );
 
         $issue->rmkStatus = $orderData['rmk_status'] ?? null;
-        $issue->rmkStatusId = $orderData['rmk_status_id'] ?? null;
+        $issue->rmkStatusId = isset($orderData['rmk_status_id']) ? (string)$orderData['rmk_status_id'] : null;
         $issue->mpStatus = $orderData['mp_status_name'] ?? null;
         $issue->mpStatusCode = $orderData['mp_status_code'] ?? null;
         $issue->mpSubstatusCode = $orderData['mp_substatus_code'] ?? null;