From 9b91214f59afbed22668469caa40f857a7294d86 Mon Sep 17 00:00:00 2001 From: fomichev Date: Wed, 19 Mar 2025 18:21:27 +0300 Subject: [PATCH] =?utf8?q?=D0=9E=D1=82=D0=BB=D0=B0=D0=B4=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- erp24/commands/MarketplaceController.php | 6 +- erp24/media/controllers/FlowwowController.php | 31 ++- erp24/services/MarketplaceService.php | 248 ++++++++++++------ 3 files changed, 196 insertions(+), 89 deletions(-) diff --git a/erp24/commands/MarketplaceController.php b/erp24/commands/MarketplaceController.php index 6dd62ca2..56c90b7e 100644 --- a/erp24/commands/MarketplaceController.php +++ b/erp24/commands/MarketplaceController.php @@ -73,10 +73,10 @@ class MarketplaceController extends Controller public function actionGetFlowwowOrders() { - $orders = MarketplaceService::getFlowwowOrdersFromMail(); - Yii::warning("Заказы " . json_encode($orders, JSON_UNESCAPED_UNICODE)); + $messages = MarketplaceService::getFlowwowOrdersFromMail(); + Yii::warning("Заказы " . json_encode($messages, JSON_UNESCAPED_UNICODE)); - $count = MarketplaceService::processFlowwowOrders($orders); + $count = MarketplaceService::processMessages($messages); $this->stdout( "Удалось сохранить {$count} новых заказов из почты.\n", diff --git a/erp24/media/controllers/FlowwowController.php b/erp24/media/controllers/FlowwowController.php index 49539eb7..1f26ee4e 100644 --- a/erp24/media/controllers/FlowwowController.php +++ b/erp24/media/controllers/FlowwowController.php @@ -2,6 +2,7 @@ namespace app\controllers; + use voku\helper\HtmlDomParser; use voku\helper\SimpleHtmlDom; use Yii; @@ -112,7 +113,24 @@ class FlowwowController extends Controller } } } - + $htmlMessage = html_entity_decode($htmlMessage, ENT_QUOTES, 'UTF-8'); + // $htmlMessage = str_replace('\"', '"', $htmlMessage); + // $htmlMessage = str_replace(['\r', '\n', '\r\n'], '', $htmlMessage); + $htmlMessage = preg_replace('/\s+/', ' ', $htmlMessage); + // $htmlMessage = stripslashes($htmlMessage); + return $htmlMessage; + $doc = new HtmlDomParser($htmlMessage); + $orderNumber = ''; + $main = $doc->findOneOrFalse("body"); + + if ($main !== false) { + $orderTitleNode = $main->findOne("h1"); + + if ($orderTitleNode && preg_match('/№(\d+)/', $orderTitleNode->innertext, $matches)) { + $orderNumber = (int)$matches[1]; + } + } + return $orderNumber; $messages[] = [ 'subject' => $subject, 'subject_index' => $subjectIndex, @@ -326,12 +344,12 @@ class FlowwowController extends Controller set_time_limit(300); Yii::$app->response->format = Response::FORMAT_JSON; - $html = file_get_contents('./runtime/file.html'); - + $html = ''; + //$html = quoted_printable_decode($html); if (!empty($html)) { - $html = html_entity_decode($html, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + $html = html_entity_decode($html, ENT_COMPAT, 'UTF-8'); - $doc = new HtmlDomParser($html); + $doc = new HtmlDomParser($html); $main = $doc->findOneOrFalse("body"); @@ -351,6 +369,8 @@ class FlowwowController extends Controller $deliveryBlock = $main->findOne('p:contains("Доставить")'); + + $pickupBlock = $main->findOne('p:contains("Самовывоз")'); if ($deliveryBlock && $deliveryBlock->nextNonWhitespaceSibling()) { @@ -411,6 +431,7 @@ class FlowwowController extends Controller $itemsBlock = $main->findOneOrFalse('h2:contains("Детали заказа")') ? $main->findOne('h2:contains("Детали заказа")') : $main->findOneOrFalse('p:contains("Детали заказа")'); + if ($itemsBlock) { $itemsTable = $itemsBlock->parentNode()->find('table', 2); diff --git a/erp24/services/MarketplaceService.php b/erp24/services/MarketplaceService.php index 8b3dbfb7..aededd7b 100644 --- a/erp24/services/MarketplaceService.php +++ b/erp24/services/MarketplaceService.php @@ -77,12 +77,19 @@ class MarketplaceService ] ]; + const SUBJECT_NEW = '/^Новый_оплаченный_заказ$/'; + const SUBJECT_APPROVED = '/^Заказ_№\d+_принят!$/'; + const SUBJECT_CANCELLED = '/^Заказ_№\d+_отменён$/'; + const SUBJECT_CHANGED = '/^Изменения_в_заказе_№\d+$/'; + const SUBJECT_DELIVERED = '/^Flowwow. Заказ_выполнен._Напишите_отзыв_о_клиенте$/'; + + const SUBJECT_INDEX = [ - '/^Новый_оплаченный_заказ$/' => 1, - '/^Заказ_№\d+_принят!$/' => 2, - '/^Заказ_№\d+_отменён$/' => 3, - '/^Изменения_в_заказе_№\d+$/' => 4, - '/^Flowwow. Заказ_выполнен._Напишите_отзыв_о_клиенте$/' => 5, + self::SUBJECT_NEW => 1, + self::SUBJECT_APPROVED => 2, + self::SUBJECT_CHANGED => 3, + self::SUBJECT_CANCELLED => 4, + self::SUBJECT_DELIVERED => 5, ]; public static function infoForMarketplace(int $marketId) { @@ -1342,6 +1349,10 @@ class MarketplaceService $username = 'Zakaz-bazacvetov24@yandex.ru'; $password = 'jyxnwwwvgpwhzbdu'; + /*$hostname = '{imap.yandex.ru:993/imap/ssl}INBOX'; + $username = 'flow@bazacvetov24.ru'; + $password = 'ctqamxqeshgxwsgn';*/ + // Устанавливаем таймауты IMAP imap_timeout(IMAP_OPENTIMEOUT, 120); imap_timeout(IMAP_READTIMEOUT, 120); @@ -1356,15 +1367,16 @@ class MarketplaceService //$inboxInfo = imap_mailboxmsginfo($inbox); //Yii::warning(' Состояние ящика: ' . print_r($inboxInfo, true), __METHOD__); $emails = imap_search($inbox, 'ON "14-Feb-2025" FROM "info@flowwow.com" '); + //$emails = imap_search($inbox, 'ON "18-Mar-2025" FROM "info@flowwow.com" '); $messages = []; $subjectPatterns = [ - '/^Новый_оплаченный_заказ$/', - '/^Заказ_№\d+_принят!$/', - '/^Заказ_№\d+_отменён$/', - '/^Изменения_в_заказе_№\d+$/', - '/^Flowwow. Заказ_выполнен._Напишите_отзыв_о_клиенте$/', + self::SUBJECT_NEW, + self::SUBJECT_APPROVED, + self::SUBJECT_CHANGED, + self::SUBJECT_CANCELLED, + self::SUBJECT_DELIVERED, ]; if ($emails) { @@ -1383,7 +1395,7 @@ class MarketplaceService foreach ($structure->parts as $partNum => $part) { if ($part->subtype == 'HTML') { $htmlMessage = imap_fetchbody($inbox, $email_number, $partNum + 1); - $htmlMessage = quoted_printable_decode($htmlMessage); + // $htmlMessage = quoted_printable_decode($htmlMessage); break; } } @@ -1413,42 +1425,52 @@ class MarketplaceService $count = 0; if ($messages) { foreach ($messages as $message) { - if ($message['subject_index'] == 1) { - $order = self::getOrdersDataFromMessage($message); - $count += self::processFlowwowOrders($order); - - } elseif ($message['subject_index'] == 2) { - $order = self::getOrdersDataFromMessage($message); - $count += self::processFlowwowOrders($order); - - } elseif ($message['subject_index'] == 3) { - $order = self::getOrdersDataFromMessage($message); - - } elseif ($message['subject_index'] == 4) { - $order = self::getOrdersDataFromMessage($message); - - } elseif ($message['subject_index'] == 5) { - $order = self::getOrdersDataFromMessage($message); - + $store = MarketplaceStore::getWarehouseGuidByAccountEmail($message['to']) ?? 206008; + // Yii::warning('Message' . json_encode($message, JSON_UNESCAPED_UNICODE)); + $order = self::getOrdersDataFromMessage($message); + Yii::warning('Order' . json_encode($order, JSON_UNESCAPED_UNICODE)); + if ($message['subject_index'] == self::SUBJECT_INDEX[self::SUBJECT_NEW]) { + $statusCode = 'PROCESSING'; + $substatusCode = 'STARTED'; + } elseif ($message['subject_index'] == self::SUBJECT_INDEX[self::SUBJECT_APPROVED]) { + $statusCode = 'PROCESSING'; + $substatusCode = 'APPROVED'; + } elseif ($message['subject_index'] == self::SUBJECT_INDEX[self::SUBJECT_CANCELLED]) { + $statusCode = 'CANCELLED'; + $substatusCode = 'USER_CHANGED_MIND'; + } elseif ($message['subject_index'] == self::SUBJECT_INDEX[self::SUBJECT_CHANGED]) { + $statusCode = 'PROCESSING'; + $substatusCode = 'CHANGED'; + } elseif ($message['subject_index'] == self::SUBJECT_INDEX[self::SUBJECT_DELIVERED]) { + $statusCode = 'DELIVERED'; + $substatusCode = 'DELIVERY_SERVICE_DELIVERED'; + } else { + $statusCode = 'PROCESSING'; + $substatusCode = 'STARTED'; } - - - + $count += self::processFlowwowOrders($order, $store, $statusCode, $substatusCode, $message['subject_index']); } } return $count; } - public static function getOrdersDataFromMessage($message) { $html = $message['body']; + // Yii::warning('Message body' . $html); $orderDetails = null; $order = null; if (!empty($html)) { - $html = html_entity_decode($html, ENT_QUOTES | ENT_HTML5, 'UTF-8'); + $html = quoted_printable_decode($html); + $html = preg_replace('/\s+/', ' ', $html); + // Декодируем HTML-сущности + $html = html_entity_decode($html, ENT_COMPAT, 'UTF-8'); - $doc = new HtmlDomParser($html); + // Удаляем лишние пробелы и переносы строк + // $html = preg_replace('/\s+/', ' ', $html); + // Yii::warning('Message body' . $html); + $doc = new HtmlDomParser($html); + $orderNumber = ''; $main = $doc->findOneOrFalse("body"); if ($main !== false) { @@ -1459,6 +1481,7 @@ class MarketplaceService } } $orderDetails['number'] = $orderNumber; + $orderDetails['date'] = $message['date']; $deliveryText = ''; $commentText = ''; $clientText = ''; @@ -1523,10 +1546,19 @@ class MarketplaceService $orderDetails['recipient'] = $recipientText; } + $itemsBlock = false; + if ($main->findOneOrFalse('table h2:contains("Детали заказа")') != false) { + $itemsBlock = $main->findOneOrFalse('table h2:contains("Детали заказа")'); + Yii::warning('Детали заказа 1: ' . json_encode($itemsBlock->innerText(), JSON_UNESCAPED_UNICODE), __METHOD__); + } elseif ($main->findOneOrFalse('table p:contains("Детали заказа")') != false) { + $itemsBlock = $main->findOneOrFalse('table p:contains("Детали заказа")'); + Yii::warning('Детали заказа 2: ' . json_encode($itemsBlock->innerText(), JSON_UNESCAPED_UNICODE), __METHOD__); + } - $itemsBlock = $main->findOneOrFalse('h2:contains("Детали заказа")') ? $main->findOne('h2:contains("Детали заказа")') : $main->findOneOrFalse('p:contains("Детали заказа")'); if ($itemsBlock) { - $itemsTable = $itemsBlock->parentNode()->find('table', 2); + Yii::warning('Детали заказа 3: ' . json_encode($itemsBlock->parentNode()->innerText(), JSON_UNESCAPED_UNICODE), __METHOD__); + // $itemsTable = $itemsBlock->parentNode()->find('table', 2); + $itemsTable = $itemsBlock->parentNode(); $itemsRows = $itemsTable->find('tr'); foreach ($itemsRows as $itemsRow) { @@ -1537,31 +1569,26 @@ class MarketplaceService 'price' => '', ]; - // Извлекаем название и количество из второго $tds = $itemsRow->find('td'); if (count($tds) >= 2) { $itemData['name'] = trim(str_replace("\u{00A0}", ' ', strip_tags(preg_replace('/\s+/', ' ',$tds[1]->find('p', 0)->innerText())))); $itemData['count'] = trim(str_replace(["\u{00A0}", 'шт.'], '', strip_tags(preg_replace('/\s+/', '', $tds[1]->find('p', 1)->innerText())))); } - // Извлекаем цену из третьего if (count($tds) >= 3) { $itemData['price'] = (float)trim(str_replace(["\u{00A0}", '₽' , ' '], '', strip_tags(preg_replace('/\s+/', ' ', $tds[2]->find('p', 0)->innerText())))); } - - // Добавляем данные в массив $orderItems[] = $itemData; } $totalSum = 0; - $sumBlock = $itemsBlock->parentNode()->find('table', 3); + $sumBlock = $itemsBlock->parentNode()->nextNonWhitespaceSibling(); $sumRow = $sumBlock->find('tr'); $sumTds = $sumRow->find('td'); - if (count($sumTds) >= 2) { + if ($sumTds && count($sumTds) >= 2) { $totalSum = (float)trim(str_replace(["\u{00A0}", '₽' , ' '], '', strip_tags(preg_replace('/\s+/', ' ', $sumTds[1]->innerText())))); - } $orderDetails['items'] = $orderItems; $orderDetails['totalSum'] = $totalSum; @@ -1574,9 +1601,7 @@ class MarketplaceService } - - - public static function processFlowwowOrders(array $allOrders) + public static function processFlowwowOrders($order, $store, $statusCode, $substatusCode, $index) { $statuses = MarketplaceOrderStatusTypes::find() ->select(['id', 'code']) @@ -1586,39 +1611,62 @@ class MarketplaceService $statuses = ArrayHelper::map($statuses, 'code', 'id'); $statusCodes = array_unique(array_keys($statuses)); $newOrdersCount = 0; - $campaignId = 206008; + $campaignId = $store; - foreach ($allOrders as $number => $order) { - $statusCode = 'PROCESS'; - $substatusCode = 'STARTED'; + $orderNumber = key($order); + $orderDetails = reset($order); - $statusId = self::getOrCreateStatus($statusCode, $statuses, $statusCodes); - $substatusId = self::getOrCreateStatus($substatusCode, $statuses, $statusCodes); - - $marketplaceOrder = MarketplaceOrders::find() - ->where(['marketplace_order_id' => (string)$number]) - ->one(); - - if (!$marketplaceOrder) { - Yii::warning("Номер " . json_encode($number, JSON_UNESCAPED_UNICODE)); - Yii::warning("Заказ " . json_encode($order, JSON_UNESCAPED_UNICODE)); + $statusId = self::getOrCreateStatus($statusCode, $statuses, $statusCodes); + $substatusId = self::getOrCreateStatus($substatusCode, $statuses, $statusCodes); + Yii::warning("Статус " . $statusId); + Yii::warning("Субстатус " . $substatusId); + $marketplaceOrder = MarketplaceOrders::find() + ->where(['marketplace_order_id' => (string)$orderNumber]) + ->one(); - $marketplaceOrder = self::createOrder($order, $campaignId, $statusId, $substatusId); + if (!$marketplaceOrder) { + Yii::warning("Номер " . json_encode($orderNumber, JSON_UNESCAPED_UNICODE)); + Yii::warning("Заказ " . json_encode($orderDetails, JSON_UNESCAPED_UNICODE)); + if ($index == self::SUBJECT_INDEX[self::SUBJECT_NEW]) { + $marketplaceOrder = self::createOrder($orderDetails, $campaignId, $statusId, $substatusId); if ($marketplaceOrder->save()) { - $newOrdersCount += 1; - self::createStatusHistory($marketplaceOrder->id, $statusId, $substatusId); - self::saveOrderItems($order, $marketplaceOrder->id, $marketplaceOrder->warehouse_guid); - + $newOrdersCount += 1; + self::createOrUpdateStatusHistory($marketplaceOrder->id, $statusId, $substatusId); + self::saveOrderItems($orderDetails, $marketplaceOrder->id, $marketplaceOrder->warehouse_guid); } else { - Yii::error( - 'Ошибка сохранения заказа: ' . json_encode( - $marketplaceOrder->getErrors(), - JSON_UNESCAPED_UNICODE - ) - ); + Yii::error( + 'Ошибка сохранения заказа: ' . json_encode( + $marketplaceOrder->getErrors(), + JSON_UNESCAPED_UNICODE + ) + ); + } + } + } else { + if ($index == self::SUBJECT_APPROVED || $index == self::SUBJECT_CHANGED) { + //заказ принят или изменен + $marketplaceOrder->status_id = $statusId; + $marketplaceOrder->substatus_id = $substatusId; + if ($marketplaceOrder->raw_data !== json_encode($orderDetails, JSON_UNESCAPED_UNICODE)) { + $marketplaceOrder->raw_data = json_encode($orderDetails, JSON_UNESCAPED_UNICODE); + } + if ($marketplaceOrder->save()) { + self::createOrUpdateStatusHistory($marketplaceOrder->id, $statusId, $substatusId); + } else { + Yii::error('Не удалось обновить заказ' . json_encode($marketplaceOrder->getErrors(), JSON_UNESCAPED_UNICODE)); + } + } else { + // отмена или успешное выполнение + $marketplaceOrder->status_id = $statusId; + $marketplaceOrder->substatus_id = $substatusId; + if ($marketplaceOrder->save()) { + self::createOrUpdateStatusHistory($marketplaceOrder->id, $statusId, $substatusId); + } else { + Yii::error('Не удалось обновить заказ' . json_encode($marketplaceOrder->getErrors(), JSON_UNESCAPED_UNICODE)); } } } + return $newOrdersCount; } @@ -1669,18 +1717,56 @@ class MarketplaceService return $marketplaceOrder; } - private static function createStatusHistory($orderId, $statusId, $substatusId) + private static function createOrUpdateStatusHistory($orderId, $statusId, $substatusId) { - $history = new MarketplaceOrderStatusHistory(); - $history->order_id = $orderId; - $history->status_id = $statusId; - $history->substatus_id = $substatusId; - $history->active = 1; - $history->initiator = "ERP"; - $history->date_from = date('Y-m-d H:i:s'); - $history->date_end = '2100-01-01 00:00:00'; - $history->save(); + $statusHistoryRecord = MarketplaceOrderStatusHistory::find()->where(['order_id' => $orderId])->andWhere(['active' => 1])->one(); + if ( + $statusHistoryRecord && + ($statusHistoryRecord->status_id !== (int)$statusId || + $statusHistoryRecord->substatus_id !== (int)$substatusId) + ) { + $statusHistoryRecord->active = 0; + $statusHistoryRecord->date_end = date('Y-m-d H:i:s'); + $statusHistoryRecord->save(); + + $newStatusHistoryRecord = new MarketplaceOrderStatusHistory(); + $newStatusHistoryRecord->order_id = $orderId; + $newStatusHistoryRecord->status_id = (int)$statusId; + $newStatusHistoryRecord->substatus_id = (int)$substatusId; + $newStatusHistoryRecord->active = 1; + $newStatusHistoryRecord->initiator = "ERP"; + $newStatusHistoryRecord->date_from = date('Y-m-d H:i:s'); + $newStatusHistoryRecord->date_end = date('Y-m-d H:i:s', strtotime("2100-01-01")); + if (!$newStatusHistoryRecord->save()) { + Yii::error( + 'Ошибка сохранения новой истории статуса: ' . json_encode( + $newStatusHistoryRecord->getErrors(), + JSON_UNESCAPED_UNICODE + ) + ); + } + } else { + $history = new MarketplaceOrderStatusHistory(); + $history->order_id = $orderId; + $history->status_id = $statusId; + $history->substatus_id = $substatusId; + $history->active = 1; + $history->initiator = "ERP"; + $history->date_from = date('Y-m-d H:i:s'); + $history->date_end = '2100-01-01 00:00:00'; + + if (!$history->save()) { + Yii::error( + 'Ошибка сохранения новой истории статуса: ' . json_encode( + $history->getErrors(), + JSON_UNESCAPED_UNICODE + ) + ); + } + } } + + private static function saveOrderItems($order, $orderId, $warehouseGuid) { $items = $order['items']; -- 2.39.5