return $name;
}
+ /**
+ * Тестовый метод для проверки обработки артикулов
+ */
+ public static function testArticleProcessing()
+ {
+ $testCases = [
+ // Тесты для normalizeArticleInName (убираем дефис)
+ [
+ 'input' => 'Букет из 11 розовых пионов (FW-0076)',
+ 'expected' => 'Букет из 11 розовых пионов (FW0076)',
+ 'method' => 'normalizeArticleInName'
+ ],
+ [
+ 'input' => 'Букет из 11 розовых пионов (FW0076)',
+ 'expected' => 'Букет из 11 розовых пионов (FW0076)',
+ 'method' => 'normalizeArticleInName'
+ ],
+ [
+ 'input' => 'Букет из 11 розовых пионов',
+ 'expected' => 'Букет из 11 розовых пионов',
+ 'method' => 'normalizeArticleInName'
+ ],
+ [
+ 'input' => 'Букет из 11 розовых пионов (ABC-123)',
+ 'expected' => 'Букет из 11 розовых пионов (ABC123)',
+ 'method' => 'normalizeArticleInName'
+ ],
+
+ // Тесты для processDisplayName (добавляем дефис)
+ [
+ 'input' => 'Букет из 11 розовых пионов (FW0076)',
+ 'expected' => 'Букет из 11 розовых пионов (FW-0076)',
+ 'method' => 'processDisplayName'
+ ],
+ [
+ 'input' => 'Букет из 11 розовых пионов (FW-0076)',
+ 'expected' => 'Букет из 11 розовых пионов (FW-0076)',
+ 'method' => 'processDisplayName'
+ ],
+ [
+ 'input' => 'Букет из 11 розовых пионов',
+ 'expected' => 'Букет из 11 розовых пионов',
+ 'method' => 'processDisplayName'
+ ],
+ [
+ 'input' => 'Букет из 11 розовых пионов (ABC123)',
+ 'expected' => 'Букет из 11 розовых пионов (ABC-123)',
+ 'method' => 'processDisplayName'
+ ],
+ ];
+
+ $results = [];
+ foreach ($testCases as $testCase) {
+ $method = $testCase['method'];
+ $input = $testCase['input'];
+ $expected = $testCase['expected'];
+
+ $actual = self::$method($input);
+ $passed = $actual === $expected;
+
+ $results[] = [
+ 'method' => $method,
+ 'input' => $input,
+ 'expected' => $expected,
+ 'actual' => $actual,
+ 'passed' => $passed
+ ];
+ }
+
+ return $results;
+ }
+
public static function getProductImageUrl($imageId)
{
$image = Images::findOne($imageId);
$html = $message['body'];
$orderDetails = null;
$order = null;
- if (!empty($html)) {
- $html = preg_replace('/\s+/', ' ', $html);
- // Декодируем HTML-сущности
- $html = html_entity_decode($html, ENT_COMPAT, 'UTF-8');
-
- $doc = new HtmlDomParser($html);
- $orderNumber = '';
- $main = $doc->findOneOrFalse("body");
-
- if ($main !== false) {
- $orderTitleNode = $main->findOne("h1");
- if ($orderTitleNode && preg_match('/№(\d+)/', $orderTitleNode->innertext, $matches)) {
- $orderNumber = (int)$matches[1];
- }
- }
- $orderDetails['number'] = $orderNumber;
- $orderDetails['date'] = $message['date'];
- $deliveryText = '';
- $clientText = '';
- $orderItems = [];
- $totalSum = 0;
- $deliverySum = 0;
-
- $linkBlock = $main->findOneOrFalse('a:contains("Перейти в заказ ")');
- if ($linkBlock) {
- $link = $linkBlock->getAttribute('href');
- $orderDetails['orderLink'] = $link;
- }
+ // Детальная проверка HTML
+ if (empty($html)) {
+ Yii::error('HTML тело письма пустое', __METHOD__);
+ return [];
+ }
- $deliveryLabel = $main->findOne('p:contains("Доставить")');
- $pickupLabel = $main->findOne('p:contains("Самовывоз")');
+ if (empty(trim($html))) {
+ Yii::error('HTML тело письма содержит только пробелы', __METHOD__);
+ return [];
+ }
- $targetLabel = $deliveryLabel ?? $pickupLabel;
- $labelPrefix = $deliveryLabel ? 'Доставка:' : ($pickupLabel ? 'Самовывоз:' : null);
+ // Очищаем от лишних пробелов, но сохраняем структуру
+ $html = preg_replace('/\s+/', ' ', $html);
+ $html = trim($html);
- if ($targetLabel && $targetLabel->nextNonWhitespaceSibling()) {
- $infoBlock = $targetLabel->nextNonWhitespaceSibling();
- $textParts = [];
+ if (empty($html)) {
+ Yii::error('HTML тело письма стало пустым после очистки', __METHOD__);
+ return [];
+ }
- $lines = explode('<br>', $infoBlock->innerHtml());
+ // Декодируем HTML-сущности
+ $html = html_entity_decode($html, ENT_COMPAT, 'UTF-8');
- foreach ($lines as $line) {
- $clean = trim(strip_tags($line));
- if ($clean !== '') {
- $textParts[] = $clean;
- }
- }
+ // Проверяем наличие основных HTML тегов
+ if (stripos($html, '<body') === false && stripos($html, '<html') === false) {
+ Yii::warning('HTML не содержит основных тегов body или html', __METHOD__);
+ }
- if (!empty($textParts)) {
- $deliveryText = $labelPrefix . ' ' . $textParts[0];
+ // Парсим HTML и извлекаем данные заказа
+ $doc = new HtmlDomParser($html);
+ $orderNumber = '';
+ $main = $doc->findOneOrFalse("body");
- if (isset($textParts[1])) {
- $deliveryText .= ' ' . $textParts[1];
- }
+ // Если не найден body, пробуем найти по другим селекторам
+ if ($main === false) {
+ Yii::warning('Тег body не найден, пробуем найти контент по другим селекторам', __METHOD__);
- if (isset($textParts[2])) {
- $deliveryText .= ', ' . $textParts[2];
- }
- }
- }
+ // Пробуем найти div или другой контейнер с контентом
+ $main = $doc->findOneOrFalse("div") ?: $doc->findOneOrFalse("*");
- if (!empty($deliveryText)) {
- $deliveryText = preg_replace('/\s+/', ' ', $deliveryText);
- $deliveryText = trim($deliveryText);
- $orderDetails['delivery'] = $deliveryText;
+ if ($main === false) {
+ Yii::error('Не удалось найти подходящий контейнер для парсинга HTML', __METHOD__);
+ return [];
}
+ }
+
+ $orderTitleNode = $main->findOne("h1");
+ if ($orderTitleNode && preg_match('/№(\d+)/', $orderTitleNode->innertext, $matches)) {
+ $orderNumber = (int)$matches[1];
+ } elseif (!$orderTitleNode) {
+ Yii::warning('Не найден заголовок заказа (h1) в HTML', __METHOD__);
+ }
+
+ $orderDetails['number'] = $orderNumber;
+ $orderDetails['date'] = $message['date'];
+ $deliveryText = '';
+ $clientText = '';
+ $orderItems = [];
+ $totalSum = 0;
+ $deliverySum = 0;
+
+ $linkBlock = $main->findOneOrFalse('a:contains("Перейти в заказ ")');
+ if ($linkBlock) {
+ $link = $linkBlock->getAttribute('href');
+ $orderDetails['orderLink'] = $link;
+ }
+
+ $deliveryLabel = $main->findOne('p:contains("Доставить")');
+ $pickupLabel = $main->findOne('p:contains("Самовывоз")');
- $commentBlock = $main->findOne('p:contains("Комментарий")');
- if ($commentBlock && $commentBlock->nextNonWhitespaceSibling()) {
- $commentBlock = $commentBlock->nextNonWhitespaceSibling();
- $commentText = preg_replace('/\s+/', ' ', $commentBlock->innerText());
- $orderDetails['comment'] = trim($commentText);
+ $targetLabel = $deliveryLabel ?? $pickupLabel;
+ $labelPrefix = $deliveryLabel ? 'Доставка:' : ($pickupLabel ? 'Самовывоз:' : null);
+
+ if ($targetLabel && $targetLabel->nextNonWhitespaceSibling()) {
+ $infoBlock = $targetLabel->nextNonWhitespaceSibling();
+ $textParts = [];
+
+ $lines = explode('<br>', $infoBlock->innerHtml());
+
+ foreach ($lines as $line) {
+ $clean = trim(strip_tags($line));
+ if ($clean !== '') {
+ $textParts[] = $clean;
+ }
}
- $clientBlock = $main->findOne('p:contains("Клиент")');
- $senderBlock = $main->findOne('p:contains("Отправитель")');
+ if (!empty($textParts)) {
+ $deliveryText = $labelPrefix . ' ' . $textParts[0];
- if ($clientBlock && $clientBlock->nextNonWhitespaceSibling()) {
- $clientBlock = $clientBlock->nextNonWhitespaceSibling();
- $clientText = "Клиент: " . strip_tags($clientBlock->innerText());
- $phoneLink = $clientBlock->find('a', 0);
- if ($phoneLink) {
- $clientText .= ' ' . preg_replace('/tel:/', ' ', $phoneLink->getAttribute('href'));
+ if (isset($textParts[1])) {
+ $deliveryText .= ' ' . $textParts[1];
}
- } elseif ($senderBlock && $senderBlock->nextNonWhitespaceSibling()) {
- $senderBlock = $senderBlock->nextNonWhitespaceSibling();
- $clientText = "Отправитель: " . strip_tags($senderBlock->innerText());
- $phoneLink = $senderBlock->find('a', 0);
- if ($phoneLink) {
- $clientText .= ' ' . preg_replace('/tel:/', ' ', $phoneLink->getAttribute('href'));
+
+ if (isset($textParts[2])) {
+ $deliveryText .= ', ' . $textParts[2];
}
}
+ }
- if ($clientText) {
- $orderDetails['client'] = str_replace('Позвонить', '', $clientText);
- }
+ if (!empty($deliveryText)) {
+ $deliveryText = preg_replace('/\s+/', ' ', $deliveryText);
+ $deliveryText = trim($deliveryText);
+ $orderDetails['delivery'] = $deliveryText;
+ }
- $recipientBlock = $main->findOne('p:contains("Получатель")');
- if ($recipientBlock && $recipientBlock->nextNonWhitespaceSibling()) {
- $recipientBlock = $recipientBlock->nextNonWhitespaceSibling();
- $recipientText = strip_tags(
- str_replace('Позвонить', '', $recipientBlock->innerText())
- ) . ' ' . preg_replace('/tel:/', ' ', $recipientBlock->find('a', 0)->getAttribute('href'));
- $orderDetails['recipient'] = $recipientText;
- }
+ $commentBlock = $main->findOne('p:contains("Комментарий")');
+ if ($commentBlock && $commentBlock->nextNonWhitespaceSibling()) {
+ $commentBlock = $commentBlock->nextNonWhitespaceSibling();
+ $commentText = preg_replace('/\s+/', ' ', $commentBlock->innerText());
+ $orderDetails['comment'] = trim($commentText);
+ }
+
+ $clientBlock = $main->findOne('p:contains("Клиент")');
+ $senderBlock = $main->findOne('p:contains("Отправитель")');
- $itemsBlock = false;
- if ($main->findOneOrFalse('table h2:contains("Детали заказа")') != false) {
- $itemsBlock = $main->findOneOrFalse('table h2:contains("Детали заказа")');
- } elseif ($main->findOneOrFalse('table p:contains("Детали заказа")') != false) {
- $itemsBlock = $main->findOneOrFalse('table p:contains("Детали заказа")');
+ if ($clientBlock && $clientBlock->nextNonWhitespaceSibling()) {
+ $clientBlock = $clientBlock->nextNonWhitespaceSibling();
+ $clientText = "Клиент: " . strip_tags($clientBlock->innerText());
+ $phoneLink = $clientBlock->find('a', 0);
+ if ($phoneLink) {
+ $clientText .= ' ' . preg_replace('/tel:/', ' ', $phoneLink->getAttribute('href'));
}
+ } elseif ($senderBlock && $senderBlock->nextNonWhitespaceSibling()) {
+ $senderBlock = $senderBlock->nextNonWhitespaceSibling();
+ $clientText = "Отправитель: " . strip_tags($senderBlock->innerText());
+ $phoneLink = $senderBlock->find('a', 0);
+ if ($phoneLink) {
+ $clientText .= ' ' . preg_replace('/tel:/', ' ', $phoneLink->getAttribute('href'));
+ }
+ }
- if ($itemsBlock) {
- $itemsTable = $itemsBlock->parentNode();
+ if ($clientText) {
+ $orderDetails['client'] = str_replace('Позвонить', '', $clientText);
+ }
- $itemsRows = $itemsTable->find('tr');
- foreach ($itemsRows as $itemsRow) {
- $itemData = [
- 'name' => '',
- 'count' => '',
- 'price' => '',
- ];
+ $recipientBlock = $main->findOne('p:contains("Получатель")');
+ if ($recipientBlock && $recipientBlock->nextNonWhitespaceSibling()) {
+ $recipientBlock = $recipientBlock->nextNonWhitespaceSibling();
+ $recipientText = strip_tags(
+ str_replace('Позвонить', '', $recipientBlock->innerText())
+ ) . ' ' . preg_replace('/tel:/', ' ', $recipientBlock->find('a', 0)->getAttribute('href'));
+ $orderDetails['recipient'] = $recipientText;
+ }
- // Извлекаем название и количество из второго <td>
- $tds = $itemsRow->find('td');
- if (count($tds) >= 2) {
- $rawName = trim(str_replace("\u{00A0}", ' ', strip_tags(preg_replace('/\s+/', ' ', $tds[1]->find('p', 0)->innerText()))));
- $itemData['name'] = self::normalizeArticleInName($rawName);
- $itemData['count'] = trim(str_replace(["\u{00A0}", 'шт.'], '', strip_tags(preg_replace('/\s+/', '', $tds[1]->find('p', 1)->innerText()))));
- }
- // Извлекаем цену из третьего <td>
- 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;
- }
+ $itemsBlock = false;
+ if ($main->findOneOrFalse('table h2:contains("Детали заказа")') != false) {
+ $itemsBlock = $main->findOneOrFalse('table h2:contains("Детали заказа")');
+ } elseif ($main->findOneOrFalse('p:contains("Детали заказа")') != false) {
+ $itemsBlock = $main->findOneOrFalse('p:contains("Детали заказа")');
+ }
+
+ if ($itemsBlock) {
+ $itemsTable = $itemsBlock->parentNode();
+
+ $itemsRows = $itemsTable->find('tr');
+ foreach ($itemsRows as $itemsRow) {
+ $itemData = [
+ 'name' => '',
+ 'count' => '',
+ 'price' => '',
+ ];
- $sumBlock = $main->findOneOrFalse('p:contains("Итого оплачено")');
+ // Извлекаем название и количество из второго <td>
+ $tds = $itemsRow->find('td');
+ if (count($tds) >= 2) {
+ $rawName = trim(str_replace("\u{00A0}", ' ', strip_tags(preg_replace('/\s+/', ' ', $tds[1]->find('p', 0)->innerText()))));
+ $itemData['name'] = self::normalizeArticleInName($rawName);
+ $itemData['count'] = trim(str_replace(["\u{00A0}", 'шт.'], '', strip_tags(preg_replace('/\s+/', '', $tds[1]->find('p', 1)->innerText()))));
+ }
+ // Извлекаем цену из третьего <td>
+ 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;
+ }
- if ($sumBlock) {
- $sumBlock = $sumBlock->parentNode()->nextNonWhitespaceSibling();
- $totalSum = (float)trim
- (
- str_replace(
- ["\u{00A0}", '₽', ' '],
- '',
- strip_tags
+ $sumBlock = $main->findOneOrFalse('p:contains("Итого оплачено")');
+
+ if ($sumBlock) {
+ $sumBlock = $sumBlock->parentNode()->nextNonWhitespaceSibling();
+ $totalSum = (float)trim
+ (
+ str_replace(
+ ["\u{00A0}", '₽', ' '],
+ '',
+ strip_tags
+ (
+ preg_replace
(
- preg_replace
- (
- '/\s+/',
- ' ',
- $sumBlock->innerText()
- )
+ '/\s+/',
+ ' ',
+ $sumBlock->innerText()
)
)
- );
- }
+ )
+ );
+ }
- $devSumBlock = $main->findOneOrFalse('p:contains("Доставка")');
+ $devSumBlock = $main->findOneOrFalse('p:contains("Доставка")');
- if ($devSumBlock) {
- $devSumBlock = $devSumBlock->parentNode()->nextNonWhitespaceSibling();
- $deliverySum = (float)trim(
- str_replace(["\u{00A0}", '₽', ' '],
- '',
- strip_tags(preg_replace('/\s+/', ' ', $devSumBlock->innerText())))
- );
- }
+ if ($devSumBlock) {
+ $devSumBlock = $devSumBlock->parentNode()->nextNonWhitespaceSibling();
+ $deliverySum = (float)trim(
+ str_replace(["\u{00A0}", '₽', ' '],
+ '',
+ strip_tags(preg_replace('/\s+/', ' ', $devSumBlock->innerText())))
+ );
}
- $orderDetails['items'] = $orderItems;
- $orderDetails['deliverySum'] = $deliverySum;
- $orderDetails['totalSum'] = $totalSum;
- $order[$orderNumber] = $orderDetails;
- return $order;
}
+ $orderDetails['items'] = $orderItems;
+ $orderDetails['deliverySum'] = $deliverySum;
+ $orderDetails['totalSum'] = $totalSum;
+ $order[$orderNumber] = $orderDetails;
+
+ if (empty($order)) {
+ Yii::warning('Не удалось извлечь данные заказа из HTML', __METHOD__);
+ } else {
+ Yii::info('Успешно распарен заказ №' . $orderNumber, __METHOD__);
+ }
+
+ return $order;
+
+ // Если дошли до сюда, значит HTML не содержит валидный контент для парсинга
+ Yii::error('HTML не содержит валидный контент для парсинга заказов', __METHOD__);
return [];
}
--- /dev/null
+<?php
+
+use yii\helpers\Html;
+
+/* @var $this yii\web\View */
+/* @var $testResults array */
+
+$this->title = 'Тестирование обработки артикулов';
+$this->params['breadcrumbs'][] = ['label' => 'Заказы маркетплейсов', 'url' => ['index']];
+$this->params['breadcrumbs'][] = ['label' => 'Тестирование разбора заказов', 'url' => ['test-order-parsing']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+<div class="marketplace-orders-test-article-processing p-4">
+
+ <h1><?= Html::encode($this->title) ?></h1>
+
+ <p>
+ Результаты тестирования методов обработки артикулов в названиях товаров.
+ </p>
+
+ <div class="row">
+ <div class="col-md-12">
+ <?php
+ $normalizeTests = array_filter($testResults, function($test) {
+ return $test['method'] === 'normalizeArticleInName';
+ });
+ $processTests = array_filter($testResults, function($test) {
+ return $test['method'] === 'processDisplayName';
+ });
+
+ $allPassed = array_reduce($testResults, function($carry, $test) {
+ return $carry && $test['passed'];
+ }, true);
+ ?>
+
+ <div class="alert alert-<?= $allPassed ? 'success' : 'warning' ?>">
+ <strong>Общий результат:</strong>
+ <?= $allPassed ? 'Все тесты пройдены!' : 'Некоторые тесты не пройдены.' ?>
+ </div>
+
+ <div class="accordion" id="testResultsAccordion">
+
+ <!-- Тесты для normalizeArticleInName -->
+ <div class="accordion-item">
+ <h2 class="accordion-header">
+ <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#normalizeTests">
+ Метод normalizeArticleInName (убирает дефис из артикула при разборе заказов)
+ <span class="badge bg-<?= count(array_filter($normalizeTests, fn($t) => $t['passed'])) === count($normalizeTests) ? 'success' : 'warning' ?> ms-2">
+ <?= count(array_filter($normalizeTests, fn($t) => $t['passed'])) ?>/<?= count($normalizeTests) ?> пройдено
+ </span>
+ </button>
+ </h2>
+ <div id="normalizeTests" class="accordion-collapse collapse show" data-bs-parent="#testResultsAccordion">
+ <div class="accordion-body">
+ <div class="table-responsive">
+ <table class="table table-striped">
+ <thead>
+ <tr>
+ <th>Входные данные</th>
+ <th>Ожидаемый результат</th>
+ <th>Фактический результат</th>
+ <th>Статус</th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($normalizeTests as $test): ?>
+ <tr class="<?= $test['passed'] ? 'table-success' : 'table-danger' ?>">
+ <td><code><?= Html::encode($test['input']) ?></code></td>
+ <td><code><?= Html::encode($test['expected']) ?></code></td>
+ <td><code><?= Html::encode($test['actual']) ?></code></td>
+ <td>
+ <?php if ($test['passed']): ?>
+ <span class="badge bg-success">✓ Пройден</span>
+ <?php else: ?>
+ <span class="badge bg-danger">✗ Не пройден</span>
+ <?php endif; ?>
+ </td>
+ </tr>
+ <?php endforeach; ?>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- Тесты для processDisplayName -->
+ <div class="accordion-item">
+ <h2 class="accordion-header">
+ <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#processTests">
+ Метод processDisplayName (добавляет дефис в артикул при формировании ответа)
+ <span class="badge bg-<?= count(array_filter($processTests, fn($t) => $t['passed'])) === count($processTests) ? 'success' : 'warning' ?> ms-2">
+ <?= count(array_filter($processTests, fn($t) => $t['passed'])) ?>/<?= count($processTests) ?> пройдено
+ </span>
+ </button>
+ </h2>
+ <div id="processTests" class="accordion-collapse collapse" data-bs-parent="#testResultsAccordion">
+ <div class="accordion-body">
+ <div class="table-responsive">
+ <table class="table table-striped">
+ <thead>
+ <tr>
+ <th>Входные данные</th>
+ <th>Ожидаемый результат</th>
+ <th>Фактический результат</th>
+ <th>Статус</th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($processTests as $test): ?>
+ <tr class="<?= $test['passed'] ? 'table-success' : 'table-danger' ?>">
+ <td><code><?= Html::encode($test['input']) ?></code></td>
+ <td><code><?= Html::encode($test['expected']) ?></code></td>
+ <td><code><?= Html::encode($test['actual']) ?></code></td>
+ <td>
+ <?php if ($test['passed']): ?>
+ <span class="badge bg-success">✓ Пройден</span>
+ <?php else: ?>
+ <span class="badge bg-danger">✗ Не пройден</span>
+ <?php endif; ?>
+ </td>
+ </tr>
+ <?php endforeach; ?>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ </div>
+
+ <div class="mt-4">
+ <h4>Описание методов:</h4>
+ <ul class="list-group">
+ <li class="list-group-item">
+ <strong>normalizeArticleInName()</strong> - используется при разборе заказов из email.
+ Если в названии товара есть артикул в скобках с дефисом (FW-0076), убирает дефис (FW0076).
+ Это обеспечивает корректное сопоставление с товарами в базе данных.
+ </li>
+ <li class="list-group-item">
+ <strong>processDisplayName()</strong> - используется при формировании ответа API.
+ Если в названии товара есть артикул в скобках без дефиса (FW0076), добавляет дефис (FW-0076).
+ Это обеспечивает красивый формат отображения для внешних систем.
+ </li>
+ </ul>
+ </div>
+
+ <div class="mt-4">
+ <?= Html::a('Вернуться к тестированию разбора заказов', ['test-order-parsing'], ['class' => 'btn btn-secondary']) ?>
+ <?= Html::a('Вернуться к списку заказов', ['index'], ['class' => 'btn btn-primary']) ?>
+ </div>
+ </div>
+ </div>
+
+</div>
+
--- /dev/null
+<?php
+
+use yii\helpers\Html;
+use yii\widgets\ActiveForm;
+
+/* @var $this yii\web\View */
+/* @var $model \yii\base\DynamicModel */
+/* @var $parsedOrder array */
+/* @var $htmlBody string */
+/* @var $parsingError string */
+
+$this->title = 'Тестирование разбора заказов';
+$this->params['breadcrumbs'][] = ['label' => 'Заказы маркетплейсов', 'url' => ['index']];
+$this->params['breadcrumbs'][] = $this->title;
+?>
+
+<div class="marketplace-orders-test-order-parsing p-4">
+
+ <h1><?= Html::encode($this->title) ?></h1>
+
+ <p>
+ Этот инструмент позволяет протестировать обработку артикулов при разборе заказов.
+ Вставьте HTML тела письма с заказом и нажмите "Разобрать заказ".
+ </p>
+
+ <div class="row">
+ <div class="col-md-6">
+ <?php $form = ActiveForm::begin(['method' => 'post']); ?>
+
+ <?= $form->field($model, 'html_body')->textarea([
+ 'rows' => 20,
+ 'placeholder' => 'Вставьте HTML тела письма с заказом...'
+ ])->label('HTML тела письма') ?>
+
+ <div class="form-group">
+ <?= Html::submitButton('Разобрать заказ', ['class' => 'btn btn-success']) ?>
+ </div>
+
+ <?php ActiveForm::end(); ?>
+ </div>
+
+ <div class="col-md-6">
+ <?php if (Yii::$app->request->isPost): ?>
+ <?php if ($parsingError): ?>
+ <div class="alert alert-danger">
+ <strong>Ошибка разбора:</strong> <?= Html::encode($parsingError) ?>
+ </div>
+ <?php elseif ($parsedOrder && !empty($parsedOrder)): ?>
+ <div class="alert alert-success">
+ Заказ успешно разобран!
+ </div>
+
+ <h4>Информация о заказе:</h4>
+ <pre style="background: #f8f9fa; padding: 10px; border-radius: 4px; font-size: 12px; overflow-x: auto; max-height: 400px;">
+<?= Html::encode(json_encode($parsedOrder, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)) ?>
+ </pre>
+
+ <?php
+ $firstOrder = reset($parsedOrder);
+ if (isset($firstOrder['items']) && is_array($firstOrder['items'])):
+ ?>
+ <h4>Элементы заказа:</h4>
+ <div class="table-responsive">
+ <table class="table table-striped table-bordered">
+ <thead>
+ <tr>
+ <th>Название товара</th>
+ <th>Количество</th>
+ <th>Цена</th>
+ </tr>
+ </thead>
+ <tbody>
+ <?php foreach ($firstOrder['items'] as $item): ?>
+ <tr>
+ <td>
+ <?= Html::encode($item['name']) ?>
+ <?php
+ // Проверяем, есть ли артикул в названии
+ if (preg_match('/\(([^)]+)\)/', $item['name'], $matches)) {
+ $article = $matches[1];
+ echo '<br><small class="text-muted">Артикул: ' . Html::encode($article) . '</small>';
+ }
+ ?>
+ </td>
+ <td><?= Html::encode($item['count']) ?></td>
+ <td><?= Html::encode($item['price']) ?> ₽</td>
+ </tr>
+ <?php endforeach; ?>
+ </tbody>
+ </table>
+ </div>
+ <?php endif; ?>
+ <?php else: ?>
+ <div class="alert alert-warning">
+ Заказы не найдены в предоставленном HTML. Возможно, структура HTML не соответствует ожидаемой или заказы отсутствуют.
+ </div>
+ <?php endif; ?>
+ <?php else: ?>
+ <div class="alert alert-info">
+ Заполните форму слева и нажмите "Разобрать заказ" для тестирования.
+ </div>
+ <?php endif; ?>
+ </div>
+ </div>
+
+ <hr>
+
+ <div class="row">
+ <div class="col-md-12">
+ <h3>Примеры HTML для тестирования:</h3>
+ <div class="accordion" id="examplesAccordion">
+
+ <div class="accordion-item">
+ <h2 class="accordion-header">
+ <button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#example1">
+ Пример 1: Реальная структура Flowwow (с артикулом FW-0076)
+ </button>
+ </h2>
+ <div id="example1" class="accordion-collapse collapse show" data-bs-parent="#examplesAccordion">
+ <div class="accordion-body">
+ <button class="btn btn-sm btn-outline-primary mb-2" onclick="copyToTextarea(this.nextElementSibling)">Копировать в форму</button>
+ <pre style="background: #f8f9fa; padding: 10px; border-radius: 4px; font-size: 11px; overflow-x: auto; white-space: pre-wrap;"><code><html>
+<body>
+<table>
+<p style="font-weight: 700;">Детали заказа </p>
+<tr style="margin-bottom: 12px;">
+<td style="width: 56px; padding-right:12px;">
+<img src="https://example.com/image.jpg" width="" alt="img" border="0" style="max-width:56px;">
+</td>
+<td>
+<p style="margin-bottom: 4px;">Букет из 11 розовых пионов (FW-0076)</p>
+<p style="color: #8C8C8C;">1 шт. </p>
+</td>
+<td>
+<p style="text-align: right;">4500₽ </p>
+</td>
+</tr>
+</table>
+</body>
+</html></code></pre>
+ </div>
+ </div>
+ </div>
+
+ <div class="accordion-item">
+ <h2 class="accordion-header">
+ <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#example2">
+ Пример 2: С артикулом FW0076 (уже без разделителя)
+ </button>
+ </h2>
+ <div id="example2" class="accordion-collapse collapse" data-bs-parent="#examplesAccordion">
+ <div class="accordion-body">
+ <button class="btn btn-sm btn-outline-primary mb-2" onclick="copyToTextarea(this.nextElementSibling)">Копировать в форму</button>
+ <pre style="background: #f8f9fa; padding: 10px; border-radius: 4px; font-size: 11px; overflow-x: auto; white-space: pre-wrap;"><code><html>
+<body>
+<table>
+<p style="font-weight: 700;">Детали заказа </p>
+<tr style="margin-bottom: 12px;">
+<td style="width: 56px; padding-right:12px;">
+<img src="https://example.com/image.jpg" width="" alt="img" border="0" style="max-width:56px;">
+</td>
+<td>
+<p style="margin-bottom: 4px;">Букет из 11 розовых пионов (FW0076)</p>
+<p style="color: #8C8C8C;">1 шт. </p>
+</td>
+<td>
+<p style="text-align: right;">4500₽ </p>
+</td>
+</tr>
+</table>
+</body>
+</html></code></pre>
+ </div>
+ </div>
+ </div>
+
+ <div class="accordion-item">
+ <h2 class="accordion-header">
+ <button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#example3">
+ Пример 3: Без артикула
+ </button>
+ </h2>
+ <div id="example3" class="accordion-collapse collapse" data-bs-parent="#examplesAccordion">
+ <div class="accordion-body">
+ <button class="btn btn-sm btn-outline-primary mb-2" onclick="copyToTextarea(this.nextElementSibling)">Копировать в форму</button>
+ <pre style="background: #f8f9fa; padding: 10px; border-radius: 4px; font-size: 11px; overflow-x: auto; white-space: pre-wrap;"><code><html>
+<body>
+<table>
+<p style="font-weight: 700;">Детали заказа </p>
+<tr style="margin-bottom: 12px;">
+<td style="width: 56px; padding-right:12px;">
+<img src="https://example.com/image.jpg" width="" alt="img" border="0" style="max-width:56px;">
+</td>
+<td>
+<p style="margin-bottom: 4px;">Букет из 11 розовых пионов</p>
+<p style="color: #8C8C8C;">1 шт. </p>
+</td>
+<td>
+<p style="text-align: right;">4500₽ </p>
+</td>
+</tr>
+</table>
+</body>
+</html></code></pre>
+ </div>
+ </div>
+ </div>
+
+ </div>
+ </div>
+ </div>
+
+ <div class="mt-4 text-center">
+ <?= Html::a('Посмотреть результаты тестирования методов обработки артикулов', ['test-article-processing'], ['class' => 'btn btn-info']) ?>
+ <?= Html::a('Вернуться к списку заказов', ['index'], ['class' => 'btn btn-primary ms-2']) ?>
+ </div>
+
+</div>
+
+<script>
+function copyToTextarea(preElement) {
+ const codeElement = preElement.querySelector('code');
+ const text = codeElement.textContent || codeElement.innerText;
+ document.querySelector('textarea[name="DynamicModel[html_body]"]').value = text;
+}
+</script>