Розробка блоку "хіти продажів" 1С-Бітрікс
«Хіти продажів» — блок із товарами, які купують найчастіше. Працює на головній сторінці як вітрина популярності, у каталозі — для орієнтації покупця, у картці товару — як сигнал довіри («інші обирають це»). У Бітрікс немає вбудованого автоматичного механізму розрахунку хітів — ручна позначка властивості HIT не масштабується. Потрібна система, яка рахує за даними.
Джерела даних для розрахунку
Хіти визначаються за реальними продажами — з b_sale_basket + b_sale_order. Додатково можна враховувати перегляди карток (якщо ведеться трекінг) із меншою вагою.
-- Топ товарів, що продаються, за 30 днів
SELECT
b.product_id,
SUM(b.quantity) AS total_qty,
COUNT(DISTINCT b.order_id) AS total_orders,
SUM(b.price * b.quantity) AS total_revenue
FROM b_sale_basket b
JOIN b_sale_order o ON b.order_id = o.id
WHERE
o.canceled = 'N'
AND o.date_insert >= DATE_SUB(NOW(), INTERVAL 30 DAY)
AND b.product_id IS NOT NULL
GROUP BY b.product_id
ORDER BY total_orders DESC, total_qty DESC
LIMIT 100;
Ранжування за total_orders (кількість замовлень), а не за total_qty — інакше одне замовлення на 100 одиниць підніме товар вище, ніж 50 різних замовлень на 1 одиницю. Кількість унікальних замовлень об'єктивніше відображає популярність.
Багаторівневі хіти: вагова формула
Для точнішого ранжування — формула з кількома факторами:
function calculateHitScore(array $stats, int $windowDays = 30): float {
$ordersWeight = 0.5;
$revenueWeight = 0.3;
$viewsWeight = 0.2;
// Нормалізація: ділимо на максимальне значення у вибірці
$normOrders = $stats['total_orders'] / ($stats['max_orders'] ?: 1);
$normRevenue = $stats['total_revenue'] / ($stats['max_revenue'] ?: 1);
$normViews = $stats['total_views'] / ($stats['max_views'] ?: 1);
// Часовий коефіцієнт: нещодавні продажі цінніші
$recencyBoost = 1.0;
if ($stats['last_sale_days_ago'] <= 7) {
$recencyBoost = 1.2;
} elseif ($stats['last_sale_days_ago'] <= 14) {
$recencyBoost = 1.1;
}
return ($normOrders * $ordersWeight + $normRevenue * $revenueWeight + $normViews * $viewsWeight)
* $recencyBoost;
}
Таблиця хітів і агент перерахунку
CREATE TABLE custom_hits (
product_id INT NOT NULL PRIMARY KEY,
score FLOAT NOT NULL,
total_orders INT DEFAULT 0,
total_qty INT DEFAULT 0,
total_revenue DECIMAL(12,2) DEFAULT 0,
category_rank INT, -- ранг усередині категорії
is_hit TINYINT DEFAULT 1,
calculated_at DATETIME DEFAULT NOW(),
INDEX idx_score (score DESC),
INDEX idx_category_rank (category_rank)
);
Агент перераховує таблицю раз на добу:
function RecalcHitsAgent(): string {
$connection = \Bitrix\Main\Application::getConnection();
// Очищаємо таблицю і заповнюємо заново
$connection->truncateTable('custom_hits');
$data = calcSalesStats(30); // за 30 днів
$max = getMaxValues($data);
foreach ($data as $productId => $stats) {
$stats = array_merge($stats, $max);
$score = calculateHitScore($stats);
$connection->add('custom_hits', [
'product_id' => $productId,
'score' => $score,
'total_orders' => $stats['total_orders'],
'total_qty' => $stats['total_qty'],
'total_revenue' => $stats['total_revenue'],
'is_hit' => $score > 0.1 ? 1 : 0,
'calculated_at' => new \Bitrix\Main\Type\DateTime(),
]);
}
// Розраховуємо ранг усередині кожної категорії
updateCategoryRanks();
return 'RecalcHitsAgent();';
}
Категоріальні хіти
Окрім загального рейтингу — хіти по розділах каталогу. У картці товару блок показує «Хіти в цій категорії»:
function getCategoryHits(int $sectionId, int $limit = 8, int $excludeId = 0): array {
// Отримуємо товари розділу, відсортовані за рангом усередині категорії
$hitIds = \Bitrix\Main\Application::getConnection()->query("
SELECT ch.product_id
FROM custom_hits ch
JOIN b_iblock_element ie ON ch.product_id = ie.id
WHERE ie.iblock_section_id = {$sectionId}
AND ie.active = 'Y'
AND ch.is_hit = 1
AND ch.product_id != {$excludeId}
ORDER BY ch.score DESC
LIMIT {$limit}
")->fetchAll();
return getProductsByIds(array_column($hitIds, 'product_id'));
}
Компонент і кешування
// company:catalog.hits — component.php
$cacheKey = "hits_{$arParams['SECTION_ID']}_{$arParams['LIMIT']}";
$cache = \Bitrix\Main\Data\Cache::createInstance();
if ($cache->initCache(3600 * 6, $cacheKey, '/catalog/hits')) {
$arResult = $cache->getVars();
} elseif ($cache->startDataCache()) {
$arResult = getCategoryHits(
(int)$arParams['SECTION_ID'],
(int)$arParams['LIMIT'],
(int)$arParams['EXCLUDE_ID']
);
$cache->endDataCache($arResult);
}
Кеш на 6 годин — дані про хіти змінюються раз на добу, часте оновлення кешу не потрібне.
Ручне управління: позначка хітів редактором
Крім автоматичних хітів — можливість ручної позначки. Менеджер заходить у картку товару і ставить прапорець «Редакційний хіт». Такі товари завжди показуються в блоку, незалежно від статистики. Використовується для акційних і нових товарів.
// Властивість інфоблоку EDITORIAL_HIT (тип: список, значення: Y/N)
// При формуванні блоку ручні хіти йдуть першими
$manualHits = getEditorialHits($sectionId, $limit);
$autoHits = getCategoryHits($sectionId, $limit - count($manualHits), $excludeIds);
$result = array_merge($manualHits, $autoHits);
Відображення значка «Хіт» на картках
У шаблоні картки товару та в лістингу додаємо мітку:
// У template.php лістингу
if (!empty($arItem['PROPERTIES']['HIT']['VALUE'])) {
echo '<span class="product-badge product-badge--hit">Хіт продажів</span>';
}
// Або через таблицю хітів — не потребує властивості в інфоблоці
$isHit = isProductHit($arItem['ID']); // читає з custom_hits
Терміни
| Етап | Термін |
|---|---|
| SQL розрахунку + таблиця хітів | 1–2 дні |
| Агент перерахунку + вагова формула | 2–3 дні |
| Компонент (загальний + категоріальний) | 2–3 дні |
| Ручна позначка в адміністративній частині | 1–2 дні |
| Значки на картках і в лістингу | 1 день |
| Тестування | 1–2 дні |
Разом: 1–1.5 тижні.







