Туроператори стикаються з проблемою: ціни змінюються щодня, дати вильотів плавають, а зовнішні системи бронювання відповідають з різною швидкістю. Одна віддає JSON за 200 мс, інша — XML за 8 секунд. Якщо спроектувати каталог як статичний інфоблок з ручним оновленням, через місяць менеджери перестануть оновлювати ціни, а клієнти бронюватимуть тури за застарілими даними. Ми — команда розробників з 10-річним досвідом в 1С-Бітрікс, спеціалізуємося на архітектурі, яка вирішує ці завдання. Наш підхід гарантує, що сайт працюватиме з реальними даними в реальному часі. За 10 років ми реалізували понад 50 проєктів для туроператорів — від невеликих вітрин до складних платформ з B2B-порталами.
Як побудувати каталог турів на інфоблоках?
Каталог будується на двох інфоблоках та одному Highload-блоці. Така архітектура забезпечує гнучкість та продуктивність навіть при тисячах турів.
Інфоблок «Напрямки» — розділи інфоблоку турів. Ієрархія: «Європа» → «Італія» → «Тоскана». Властивості розділів через UF_* поля: UF_COUNTRY_CODE (ISO 3166-1), UF_CLIMATE_INFO, UF_VISA_REQUIRED, UF_GALLERY. Розділи використовуються і для фільтрації, і для SEO-сторінок: /tours/italy/, /tours/italy/toscana/.
Інфоблок «Тури» (тип tours) — елементи всередині розділів-напрямків. Властивості включають тривалість, дати вильоту, тип туру, складність, розмір групи, включені послуги, базову ціну, зірковість готелю, зовнішній ідентифікатор системи бронювання, галерею та відео. Прив'язка до торгового каталогу (CCatalog::Add()) потрібна тільки якщо бронювання проходить через модуль sale. Якщо оплата йде на зовнішню систему (Samo.Tourvisor), каталог Бітрікс не підключається — інфоблок працює як вітрина.
Highload-блок «Цінові коефіцієнти» (PriceCoefficients) зберігає правила динамічного ціноутворення: поля TOUR_ID, DATE_FROM, DATE_TO, COEFFICIENT, RULE_TYPE (раннє бронювання, гарячий тур, сезонний, групова знижка), PRIORITY. Highload обрано тому, що записів будуть тисячі — кожен тур × кожен сезон × кожен тип правила. Вибірка через \Bitrix\Highloadblock\HighloadBlockTable::getList() з фільтром по TOUR_ID та поточній даті.
Чому фасетна фільтрація вимагає кастомного компонента?
Стандартний catalog.smart.filter не підходить для турів: дати вильоту — множинна властивість, ціна обчислюється динамічно, напрямок — ієрархія розділів. Ми розробляємо кастомний компонент project:tour.filter, який обробляє запити в 3 рази швидше стандартного на каталозі з 2000 турів.
Фільтрація за напрямком. Ієрархічний вибір: країна → регіон → курорт. При виборі країни фільтр підвантажує регіони AJAX-запитом на /api/tours/regions/?country=IT. В component.php — CIBlockSection::GetList() з кешуванням через CPHPCache з тегом iblock_id_N.
Фільтрація за датами вильоту. Клієнт обирає діапазон — наприклад, «з 1 по 30 червня». В БД дати зберігаються як множинні значення властивості. Проблема: стандартний CIBlockElement::GetList() з таким фільтром працює повільно на великих обсягах. Рішення — проміжна таблиця b_tour_departure_index, яка заповнюється агентом при оновленні туру. Фільтрація йде JOIN’ом на цю таблицю, результат — масив TOUR_ID.
Фільтрація за ціною. Ціна обчислюється в момент запиту: BASE_PRICE × коефіцієнт сезону × коефіцієнт раннього бронювання. Прямий фільтр неможливий. Є два варіанти:
- Матеріалізована ціна — агент перераховує актуальну ціну кожного туру раз на годину і записує у властивість
CURRENT_PRICE. Фільтрація по ньому стандартна. Мінус — затримка до години. - Двоетапна фільтрація — спочатку обираються тури за всіма іншими фільтрами, потім для кожного обчислюється ціна і відсікаються ті, що не потрапляють в діапазон. Працює точно, але при каталозі в 5000 турів другий етап може зайняти 200–500 мс. Вирішується кешуванням обчислених цін в Redis з TTL 15 хвилин.
На практиці обирають перший варіант — клієнт бачить ціну з похибкою до години, але фільтр працює миттєво. Точна ціна показується на детальній сторінці туру та при оформленні замовлення.
AJAX-фільтрація. Всі фільтри відправляються одним GET-запитом: /api/tours/search/?destination=IT&date_from=01.06&date_to=30.06&duration_min=7&duration_max=10&price_max=2000&type=excursion. Контролер в local/modules/project.tours/lib/controller/search.php наслідує \Bitrix\Main\Engine\Controller, валідує параметри, збирає фільтр CIBlockElement::GetList(), повертає JSON з масивом турів та метаданими фасетів (скільки турів по кожному типу при поточних фільтрах). Фасети — лічильники поруч з кожним значенням фільтра. Обчислюються окремими запитами COUNT(*), кешуються на 5 хвилин.
Як інтегрувати Samo.Tourvisor та Sletat?
Туроператор рідко продає тільки власні тури. Сайт агрегує пропозиції з кількох джерел: власні тури (в інфоблоці), пакетні тури з Samo.Tourvisor та пропозиції з Sletat.ru.
Samo.Tourvisor API — RESTful JSON. Основні ендпоінти:
-
GET /api/search— пошук турів за параметрами (країна, курорт, дата, ночі, дорослі/діти). Відповідь — масив пропозицій з ціною, готелем, датою вильоту, оператором. -
GET /api/hotel/{id}— деталі готелю: фото, опис, координати. -
POST /api/order— створення заявки на бронювання.
Інтеграція реалізується через модуль local/modules/project.tourvisor/. Клас \Project\Tourvisor\Client обгортає HTTP-запити через \Bitrix\Main\Web\HttpClient. Критичний момент — час відповіді. Samo.Tourvisor відповідає за 2–8 секунд. Користувач не повинен чекати: перше завантаження показує результати з локального інфоблоку (власні тури) миттєво, паралельно фронтенд відправляє AJAX-запит на /api/tourvisor/search/, бекенд кешує результат в Redis з TTL 30 хвилин.
Sletat.ru API — XML/SOAP. Старий протокол, але величезна база турів. Основний метод — GetTours(). Час відповіді — 5–15 секунд. Особливість — RequestId: перший запит повертає RequestId, за яким потрібно опитувати GetSearchResult() кожні 2–3 секунди, поки статус не стане Completed. Це реалізується через polling на фронтенді.
Об'єднання результатів з різних джерел. На фронті — єдиний список з позначкою джерела. Кожен результат містить source (local / tourvisor / sletat), external_id, price, currency. Сортування за ціною вимагає конвертації валют через курс НБУ, що зберігається в Highload-блоці CurrencyRates і оновлюється агентом раз на день.
Як реалізувати динамічне ціноутворення?
Три рівні ціноутворення:
- Сезонні коефіцієнти — високий сезон ×1.3, низький ×0.8. Зберігаються в Highload-блоці
PriceCoefficientsз діапазоном дат. - Раннє бронювання — знижка 10–20% при бронюванні за 60+ днів до вильоту. Правило: якщо
DEPARTURE_DATE - TODAY > 60, застосувати коефіцієнт 0.85. - Гарячі тури — знижка 15–40% за 3–7 днів до вильоту при незаповненій групі. Коефіцієнт залежить від відсотка заповнення:
GROUP_FILLED < 50%→ 0.6.
Розрахунок ціни в \Project\Tours\PriceCalculator::calculate($tourId, $departureDate):
$basePrice = $tour['BASE_PRICE'];
$coefficients = HighloadBlockTable::getList([
'filter' => [
'TOUR_ID' => $tourId,
'<=DATE_FROM' => $departureDate,
'>=DATE_TO' => $departureDate,
],
'order' => ['PRIORITY' => 'ASC'],
])->fetchAll();
$finalPrice = $basePrice;
foreach ($coefficients as $c) {
$finalPrice *= $c['COEFFICIENT'];
}
Пріоритет визначає порядок застосування. Сезонний коефіцієнт (пріоритет 1) застосовується першим, потім раннє бронювання (пріоритет 2), потім гарячий (пріоритет 3). Правила не конфліктують: раннє бронювання та гарячий тур взаємовиключні за визначенням.
Як організувати бронювання та часткову оплату?
Потік оформлення замовлення через модуль sale: клієнт обирає тур, дату вильоту, кількість учасників. Формується замовлення \Bitrix\Sale\Order::create() з кошиком з одного товару (тур) та властивостями замовлення — дані пасажирів (ПІБ, паспортні дані, дата народження). Часткова оплата реалізується через два платежі в замовленні: перший — 30–50% від вартості, другий — за 30 днів до вильоту. Обробник OnSalePaymentEntitySaved перевіряє, чи обидва платежі оплачені. Агент за 30 днів до вильоту переводить другий платіж у статус «До оплати» та надсилає email з нагадуванням.
Що входить в роботу
- Архітектурна документація (схема бази даних, структура модулів, опис API-інтеграцій)
- Налаштування віддаленого доступу та передача вихідних кодів
- Навчання менеджерів роботі з адмінкою та фільтрами
- Пост-релізна підтримка протягом 3 місяців (виправлення помилок, консультації)
Типові помилки при реалізації:
- Відсутність індексів на множинних властивостях призводить до гальмування фільтрації.
- Ігнорування таймаутів зовнішніх API — користувач бачить нескінченне завантаження.
- Синхронний виклик всіх джерел бронювання — сторінка завантажується 10+ секунд.
| Масштаб проєкту | Орієнтовні строки |
|---|---|
| Вітрина власних турів без онлайн-бронювання | 3–5 тижнів |
| Каталог з фільтрацією, бронювання, одна інтеграція (Tourvisor) | 6–10 тижнів |
| Повна платформа: кілька API, B2B-портал, динамічні ціни | 10–14 тижнів |
| Джерело | Тип API | Типовий час відповіді | Складність інтеграції |
|---|---|---|---|
| Локальний інфоблок | — | < 50 мс | Низька |
| Samo.Tourvisor | REST JSON | 2–8 сек | Середня |
| Sletat.ru | XML/SOAP | 5–15 сек | Висока (polling) |
Зв'яжіться з нами для обговорення архітектури вашого каталогу. Отримайте консультацію щодо строк та вартості — ми розрахуємо індивідуальний план. Наші інженери з 10-річним досвідом допоможуть вам уникнути типових помилок та побудувати продуктивне рішення.







