Туроператоры сталкиваются с проблемой: цены меняются ежедневно, даты вылетов плавают, а внешние системы бронирования отвечают с разной скоростью. Одна отдаёт 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-летним опытом помогут вам избежать типичных ошибок и построить производительное решение.







