Розробка сайту туроператора на 1С-Бітрікс

Наша компанія займається розробкою, підтримкою та обслуговуванням рішень на Бітрікс та Бітрікс24 будь-якої складності. Від простих односторінкових сайтів до складних інтернет-магазинів, CRM систем з інтеграцією 1С та телефонії. Досвід розробників підтверджено сертифікатами від вендора.
Пропоновані послуги
Показано 1 з 1 послугУсі 1626 послуг
Розробка сайту туроператора на 1С-Бітрікс
Складна
від 1 тижня до 3 місяців
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Розробка на базі Бітрікс, Бітрікс24, 1С для компанії Development of an Online
    585
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Розробка на базі 1С Підприємство для компанії МИРСАНБЕЛ
    751
  • image_crm_dolbimby_434_0.webp
    Розробка сайту на CRM Бітрікс24 для компанії DOLBIMBY
    657
  • image_crm_technotorgcomplex_453_0.webp
    Розробка на базі Бітрікс24 для компанії ТЕХНОТОРГКОМПЛЕКС
    989

Розробка сайту туроператора на 1С-Бітрікс

Сайт туроператора — це система з даними, що змінюються щодня. Ціни рухаються: раннє бронювання, гарячі пропозиції, сезонні коефіцієнти. Дати вильотів з'являються й зникають. Наявність місць залежить від зовнішніх систем бронювання, які відповідають по-різному — одна віддає JSON за 200 мс, інша — XML за 8 секунд. Якщо спроєктувати каталог як статичний інфоблок із ручним оновленням, через місяць менеджери перестануть актуалізувати ціни, а клієнти бронюватимуть тури за застарілими даними.

Дві задачі визначають складність проєкту: пошук із фасетною фільтрацією за кількома осями та інтеграція із зовнішніми API бронювання. Решта — шаблони, галереї, відгуки — типова робота.

Каталог турів: інфоблоки та зв'язки

Каталог будується на двох інфоблоках і одному Highload-блоці.

Напрямки — розділи інфоблоку турів. Ієрархія: «Європа» → «Італія» → «Тоскана». Властивості розділів через UF_* поля: UF_COUNTRY_CODE (ISO 3166-1), UF_CLIMATE_INFO (текст), UF_VISA_REQUIRED (булеве), UF_GALLERY (множинний файл). Розділи використовуються і для фільтрації, і як SEO-сторінки: /tours/italy/, /tours/italy/toscana/.

Інфоблок «Тури» (тип tours) — елементи всередині розділів-напрямків. Властивості:

  • DURATION — число, кількість днів. Фільтрація за діапазоном
  • DEPARTURE_DATES — множинна властивість типу «Дата». Кожен елемент містить 5-20 дат вильоту. Індекс на b_iblock_element_property за IBLOCK_PROPERTY_ID + VALUE_DATE обов'язковий — без нього фільтрація по датах на каталозі з 2000+ турів деградує до секунд
  • TOUR_TYPE — список: «Екскурсійний», «Пляжний», «Активний», «Круїзний», «Комбінований»
  • DIFFICULTY — список від 1 до 5, для активних і гірських маршрутів
  • GROUP_SIZE_MIN, GROUP_SIZE_MAX — числові
  • INCLUDED_SERVICES — множинний рядок: переліт, трансфер, страхування, екскурсії, харчування. Використовується в детальній картці та для фільтра «Що включено»
  • BASE_PRICE — число, базова ціна в основній валюті. Фактична ціна розраховується динамічно
  • HOTEL_STARS — список: 2-5 зірок, «Без розміщення»
  • BOOKING_SYSTEM_ID — рядок, зовнішній ідентифікатор туру в системі бронювання (Samo, Sletat)
  • GALLERY — множинний файл
  • VIDEO_URL — рядок, YouTube/Vimeo

Підключення до торгового каталогу через CCatalog::Add() потрібне лише якщо бронювання проходить повністю через модуль sale. Якщо оплата йде на зовнішню систему (Samo.Tourvisor), каталог Бітрікс не підключається — інфоблок працює як вітрина.

Highload-блок «PriceCoefficients» — зберігає правила динамічного ціноутворення. Поля: TOUR_ID, DATE_FROM, DATE_TO, COEFFICIENT (float), RULE_TYPE (список: «Раннє бронювання», «Гарячий тур», «Сезонний», «Групова знижка»), PRIORITY (число, порядок застосування). Highload обрано тому, що записів будуть тисячі — кожен тур × кожен сезон × кожен тип правила. Вибірка через \Bitrix\Highloadblock\HighloadBlockTable::getList() із фільтром по TOUR_ID та поточній даті.

Пошук і фасетна фільтрація — ядро проєкту

Пошук туру — це не текстовий пошук. Клієнт думає категоріями: «Італія, червень, 7-10 днів, до $2000, екскурсійний». П'ять фільтрів одночасно, і результат має з'являтися без перезавантаження сторінки.

Стандартний catalog.smart.filter тут не працює. Він розрахований на товари з фіксованими властивостями. У туру дата вильоту — множинна властивість, ціна — обчислювана, напрямок — ієрархія розділів. Фасетний індекс (\Bitrix\Iblock\PropertyIndex\Manager::buildIndex()) покриває лише частину сценаріїв.

Рішення — кастомний компонент project:tour.filter із власною логікою.

Фільтрація за напрямком. Ієрархічний вибір: країна → регіон → курорт. При виборі країни фільтр підвантажує регіони AJAX-запитом на /api/tours/regions/?country=IT. У component.phpCIBlockSection::GetList() з фільтром за SECTION_ID батька. Кешування через CPHPCache з тегом iblock_id_N.

Фільтрація за датами вильоту. Клієнт обирає діапазон — наприклад, «з 1 по 30 червня». У БД дати зберігаються як множинні значення властивості. Запит:

$filter = [
    '>=PROPERTY_DEPARTURE_DATES' => '2025-06-01',
    '<=PROPERTY_DEPARTURE_DATES' => '2025-06-30',
];

Проблема: стандартний CIBlockElement::GetList() з таким фільтром по множинній властивості працює повільно на великих обсягах. Рішення — проміжна таблиця b_tour_departure_index, що заповнюється агентом при оновленні туру. Схема: TOUR_ID, DEPARTURE_DATE (DATE, з індексом). Фільтрація йде JOIN'ом на цю таблицю, результат — масив TOUR_ID, який підставляється в CIBlockElement::GetList(['ID' => $tourIds]).

Фільтрація за ціною. Ціна обчислюється в момент запиту: BASE_PRICE × коефіцієнт сезону × коефіцієнт раннього бронювання. Прямий фільтр за обчислюваним значенням неможливий. Два підходи:

  1. Матеріалізована ціна — агент перераховує актуальну ціну кожного туру раз на годину й записує у властивість CURRENT_PRICE. Фільтрація за нею стандартна. Мінус — затримка до години між зміною коефіцієнта й оновленням ціни.
  2. Двоетапна фільтрація — спочатку вибираються тури за іншими фільтрами (напрямок, дати, тривалість), потім для кожного обчислюється ціна й відсікаються ті, що не потрапляють у діапазон. Точно, але на каталозі з 5000 турів другий етап може зайняти 200-500 мс. Вирішується кешуванням обчислених цін у Redis з TTL 15 хвилин.

На практиці обирають перший варіант — клієнт бачить ціну з похибкою до години, але фільтр працює миттєво. Точна ціна показується на детальній сторінці туру та при оформленні замовлення.

AJAX-фільтрація. Усі фільтри надсилаються одним GET-запитом: /api/tours/search/?destination=IT&date_from=2025-06-01&date_to=2025-06-30&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 із масивом турів та метаданими фасетів (скільки турів за кожним типом при поточних фільтрах).

Фасети — лічильники поряд із кожним значенням фільтра («Екскурсійний (23)», «Пляжний (45)»). Обчислюються окремими запитами COUNT(*) за кожною властивістю з рештою фільтрів. Це 4-6 додаткових запитів, кожен — легкий за наявності індексів. Результат кешується на 5 хвилин.

Інтеграція з системами бронювання

Туроператор рідко продає лише власні тури. Сайт агрегує пропозиції з кількох джерел: власні тури (в інфоблоці), пакетні тури з 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. Метод search() приймає параметри фільтра сайту, маппить їх у формат API, виконує запит, парсить відповідь.

Критичний момент — час відповіді. Samo.Tourvisor відповідає за 2-8 секунд залежно від кількості операторів. Користувач не повинен чекати:

  1. Перше завантаження сторінки пошуку показує результати з локального інфоблоку (власні тури) — миттєво
  2. Паралельно фронтенд надсилає AJAX-запит на /api/tourvisor/search/
  3. Бекенд запитує Tourvisor API, кешує результат у Redis із TTL 30 хвилин
  4. Відповідь підвантажується на сторінку, об'єднується з локальними результатами, сортується за ціною

Sletat.ru API — XML/SOAP. Старий протокол, але величезна база турів від сотень операторів. Основний метод — GetTours() із параметрами пошуку. Відповідь — XML, парсинг через SimpleXMLElement. Час відповіді — 5-15 секунд.

Стратегія та сама — асинхронне завантаження. Але у Sletat є особливість — RequestId. Перший запит повертає RequestId, за яким потрібно опитувати другий ендпоінт GetSearchResult() кожні 2-3 секунди, поки статус не стане Completed. Реалізується через polling на фронтенді:

async function pollSletat(requestId) {
    const response = await fetch(`/api/sletat/result/?request_id=${requestId}`);
    const data = await response.json();
    if (data.status === 'completed') {
        renderResults(data.tours);
    } else {
        setTimeout(() => pollSletat(requestId), 3000);
    }
}

Об'єднання результатів із різних джерел. На фронті — єдиний список із позначкою джерела. Кожен результат містить source (local / tourvisor / sletat), external_id, price, currency. Сортування за ціною вимагає конвертації валют через курс, що зберігається в Highload-блоці CurrencyRates й оновлюється агентом раз на добу.

Динамічне ціноутворення

Три рівні ціноутворення:

  1. Сезонні коефіцієнти — високий сезон ×1.3, низький ×0.8. Зберігаються в Highload-блоці PriceCoefficients із діапазоном дат
  2. Раннє бронювання — знижка 10-20% при бронюванні за 60+ днів до вильоту. Правило: якщо DEPARTURE_DATE - TODAY > 60, застосувати коефіцієнт 0.85
  3. Гарячі тури — знижка 15-40% за 3-7 днів до вильоту при незаповненій групі. Коефіцієнт залежить від відсотка заповнення: GROUP_FILLED < 50% → 0.6

Розрахунок ціни в \Project\Tours\PriceCalculator::calculate($tourId, $departureDate) отримує всі відповідні коефіцієнти, впорядковані за PRIORITY, та множить послідовно. Пріоритет визначає порядок: сезонний (1) першим, потім раннє бронювання (2), потім гарячий (3). Раннє бронювання та гарячий тур — взаємовиключні за визначенням.

Бронювання та часткова оплата

Потік оформлення замовлення через модуль sale:

  1. Клієнт обирає тур, дату вильоту, кількість учасників
  2. Створення замовлення: \Bitrix\Sale\Order::create(), кошик з одним товаром (тур), властивості замовлення — дані пасажирів (ПІБ, паспортні дані, дата народження)
  3. Часткова оплата — перший платіж 30-50% від вартості. Другий — за 30 днів до вильоту
  4. Реалізація: два об'єкти \Bitrix\Sale\Payment у замовленні. Перший зі статусом «До оплати», другий — «Відкладений». Обробник OnSalePaymentEntitySaved перевіряє, чи обидва платежі оплачені. Агент за 30 днів до вильоту переводить другий платіж у статус «До оплати» та надсилає email з нагадуванням

Документообіг: візи та страхування

Розділ «Документи» — окремий інфоблок або Highload-блок з візовими вимогами за країнами. Поля: COUNTRY_CODE, VISA_TYPE (список: «Не потрібна», «По прибутті», «У посольстві», «Електронна»), PROCESSING_DAYS, DOCUMENTS_LIST (текст), NOTES. На детальній сторінці туру автоматично виводиться блок із візовою інформацією за країною призначення — підтягується за UF_COUNTRY_CODE розділу.

Страхування — обов'язковий допродаж. Реалізується як пов'язаний товар у каталозі або через компонент project:tour.insurance із калькулятором вартості за тривалістю та напрямком.

Галереї та відгуки

Фотогалерея напрямку — множинна властивість UF_GALLERY розділу. Lightbox-перегляд через bx.lightbox або кастомний Swiper.js. Відео — властивість VIDEO_URL елемента, вбудовується через iframe із lazy loading.

Відгуки — окремий інфоблок reviews. Властивості: TOUR_ID (прив'язка), AUTHOR_ID (прив'язка до користувача), RATING (число 1-5), TRAVEL_DATE, PHOTOS (множинний файл). Модерація через workflow: новий відгук зберігається зі статусом «На модерації», публікується після перевірки в адмінці. Середній рейтинг туру обчислюється агентом і записується у властивість AVG_RATING.

B2B-портал для агентів

Окрема група користувачів agents із правами доступу через модуль main. Агент бачить:

  • Оптові ціни — розраховуються окремим типом ціни в каталозі (WHOLESALE) або окремим коефіцієнтом у Highload-блоці
  • Комісію за кожне бронювання — властивість замовлення AGENT_COMMISSION, що обчислюється відсотком від вартості туру
  • Звіти з продажів — кастомний компонент із вибіркою з b_sale_order за USER_ID агента

Авторизація — стандартна через main.auth, але з редиректом на /agents/ (окремий розділ із шаблоном agent_cabinet). Реєстрація агента — за заявкою, яку підтверджує адміністратор.

SEO: Schema.org та гео-сторінки

На детальній сторінці туру — JSON-LD розмітка TouristTrip:

{
  "@context": "https://schema.org",
  "@type": "TouristTrip",
  "name": "Тоскана: винні маршрути",
  "touristType": "Cultural",
  "offers": {
    "@type": "AggregateOffer",
    "lowPrice": "1200",
    "highPrice": "1800",
    "priceCurrency": "USD"
  },
  "subjectOf": {
    "@type": "TravelAction",
    "fromLocation": {"@type": "City", "name": "Київ"},
    "toLocation": {"@type": "City", "name": "Флоренція"}
  }
}

Формується в result_modifier.php, виводиться через $APPLICATION->AddHeadString(). AggregateOffer показує діапазон цін за всіма датами вильоту — Google відображає це у сніпеті.

Гео-сторінки /tours/italy/, /tours/turkey/antalya/ — розділи інфоблоку з унікальними UF_SEO_TITLE, UF_SEO_DESCRIPTION, UF_SEO_TEXT. Кожна сторінка — SEO-оптимізований каталог із фільтрами, прив'язаними до цього напрямку.

Етапи та терміни

Масштаб проєкту Орієнтовні терміни
Вітрина власних турів без онлайн-бронювання 3-5 тижнів
Каталог із фільтрацією, бронювання, одна інтеграція (Tourvisor) 6-10 тижнів
Повна платформа: кілька API, B2B-портал, динамічні ціни 10-14 тижнів

Основний час іде на пошуковий рушій та інтеграції. Каталог і шаблони — типова робота, 2-3 тижні. Кастомний фільтр із фасетами — 2 тижні. Інтеграція з кожним зовнішнім API — 1-2 тижні на підключення плюс тиждень на тестування граничних випадків (таймаути, зміна формату відповіді, недоступність сервісу). B2B-портал — 2-3 тижні, якщо немає специфічних вимог до звітності.