Разработка сайта гостиницы на 1С-Битрикс
Гостиничный сайт отличается от обычного каталога одним свойством: посетитель выбирает не товар, а временной слот. Номер сам по себе — набор характеристик (площадь, вместимость, вид из окна). Но номер без свободных дат — мёртвая карточка. Весь проект строится вокруг календаря доступности и механики бронирования, а не вокруг красивой вёрстки. Если движок бронирования работает, остальное — шаблоны и контент. Если не работает — никакие панорамы и отзывы не спасут конверсию.
Типы номеров: инфоблок и его свойства
Каждый тип номера — элемент инфоблока «Номерной фонд». Не путать с конкретным физическим номером: тип «Стандарт двухместный» может включать 20 физических комнат. Разделение на типы и физические единицы — ключевое архитектурное решение.
Структура инфоблока «Номерной фонд»:
| Свойство | Тип | Назначение |
|---|---|---|
| CAPACITY | N (число) | Вместимость (основные места) |
| CAPACITY_EXTRA | N | Доп. места (раскладушка, детская кроватка) |
| AREA | N | Площадь, м² |
| AMENITIES | L (список, множественное) | Удобства: Wi-Fi, кондиционер, мини-бар, сейф |
| BED_TYPE | L (список) | Тип кровати: double, twin, king |
| VIEW | L (список) | Вид: море, город, сад, двор |
| FLOOR_RANGE | S (строка) | Этажи: «3-5» |
| GALLERY | F (файл, множественное) | Фотогалерея номера |
| PANORAMA_URL | S | Ссылка на 360-панораму |
| ROOM_COUNT | N | Количество физических номеров этого типа |
| MIN_STAY | N | Минимальное количество ночей |
| BASE_RATE | N | Базовый тариф за ночь (без сезонных наценок) |
Удобства (AMENITIES) — множественное свойство типа «Список». Не Highload-блок, потому что набор удобств фиксирован (30-50 позиций) и не требует отдельного управления. Значения списка: WIFI, AC, MINIBAR, SAFE, BALCONY, BATHTUB, HAIRDRYER, KETTLE, TV, IRON. На фронте каждое значение маппится на иконку через конфиг.
Фотогалерея и виртуальный тур. Множественное свойство типа «Файл» для галереи. Рендер — Swiper.js с lazy-загрузкой, превью через CFile::ResizeImageGet() на 600x400 с BX_RESIZE_IMAGE_PROPORTIONAL. Для 360-панорамы — Pannellum.js: библиотека принимает equirectangular-изображение и рендерит интерактивный обзор в <div>. Хранение — строковое свойство PANORAMA_URL с путём к изображению в /upload/panoramas/. Pannellum инициализируется на клиенте:
pannellum.viewer('panorama-container', {
type: 'equirectangular',
panorama: roomData.panoramaUrl,
autoLoad: true,
compass: true,
hotSpots: [
{ pitch: -5, yaw: 120, type: 'info', text: 'Ванная комната' },
{ pitch: 0, yaw: 240, type: 'info', text: 'Балкон с видом на море' }
]
});
Hotspots задаются в отдельном JSON-свойстве инфоблока или в Highload-блоке, если нужна административная панель для их редактирования.
Движок бронирования и управление доступностью
Это ядро проекта. Всё остальное — обвес. Задача: пользователь выбирает даты заезда и выезда, система показывает доступные типы номеров с ценами, пользователь бронирует, номер блокируется.
Хранение доступности — Highload-блок RoomInventory. Каждая строка — одна ночь для одного физического номера.
| Поле | Тип | Описание |
|---|---|---|
| UF_DATE | date | Дата ночи (2025-07-15 = ночь с 15 на 16 июля) |
| UF_ROOM_ID | integer | ID физического номера (не типа) |
| UF_ROOM_TYPE_ID | integer | ID типа номера (элемент инфоблока) |
| UF_STATUS | integer | 0 = свободен, 1 = забронирован, 2 = заблокирован, 3 = заселён |
| UF_BOOKING_ID | integer | ID заказа (из модуля sale) |
| UF_RATE | float | Тариф за эту ночь (с учётом сезона) |
Почему Highload-блок, а не обычная таблица? Highload-блоки в Битриксе — это ORM-обёртка над обычной таблицей с автоматической генерацией API. HighloadBlockTable::compileEntity() создаёт класс с методами getList, add, update, delete. При 100 номерах и горизонте 365 дней — 36 500 строк. При 500 номерах — 182 500 строк. Highload-блок справляется, но индексы обязательны: составной индекс на (UF_DATE, UF_ROOM_TYPE_ID, UF_STATUS) для запроса доступности и (UF_BOOKING_ID) для связи с заказом.
Алгоритм проверки доступности. Гость вводит: дата заезда check_in, дата выезда check_out, количество гостей guests. Система должна вернуть типы номеров, у которых есть хотя бы один физический номер, свободный на все ночи диапазона.
// Проверка доступности для типа номера
public function getAvailableRoomTypes(
\Bitrix\Main\Type\Date $checkIn,
\Bitrix\Main\Type\Date $checkOut,
int $guests
): array {
$nights = $checkOut->getDiff($checkIn)->days;
$dates = [];
for ($i = 0; $i < $nights; $i++) {
$d = clone $checkIn;
$d->add(new \DateInterval("P{$i}D"));
$dates[] = $d->format('Y-m-d');
}
// Находим номера, занятые хотя бы в одну из ночей
$busyRooms = RoomInventoryTable::getList([
'select' => ['UF_ROOM_ID'],
'filter' => [
'UF_DATE' => $dates,
'!UF_STATUS' => 0, // не свободен
],
'group' => ['UF_ROOM_ID'],
])->fetchAll();
$busyRoomIds = array_column($busyRooms, 'UF_ROOM_ID');
// Все номера, которые НЕ заняты ни в одну ночь
// + фильтр по вместимости через связь с инфоблоком
// ...
}
Подвох: запрос «найти номера, свободные на ВСЕ ночи» — не тривиальный SQL. Простой WHERE UF_STATUS = 0 AND UF_DATE IN (...) вернёт номера, свободные хотя бы в одну ночь. Правильный подход — найти занятые номера и исключить их. Или использовать HAVING COUNT(*) = {$nights} для группировки по UF_ROOM_ID со статусом «свободен».
SELECT UF_ROOM_ID, UF_ROOM_TYPE_ID
FROM hl_room_inventory
WHERE UF_DATE IN ('2025-07-15','2025-07-16','2025-07-17')
AND UF_STATUS = 0
GROUP BY UF_ROOM_ID, UF_ROOM_TYPE_ID
HAVING COUNT(*) = 3
Этот запрос возвращает физические номера, свободные на все три ночи. Далее группируем по UF_ROOM_TYPE_ID и получаем типы номеров с количеством доступных единиц.
Календарь доступности на фронте. Два поля: дата заезда и дата выезда. Реализация — flatpickr в режиме range или кастомный React-компонент на базе react-day-picker. При открытии календаря — AJAX-запрос за матрицей доступности: массив дат с признаком «есть свободные номера / нет». Endpoint возвращает JSON:
{
"2025-07": {
"15": {"available": true, "min_rate": 4500},
"16": {"available": true, "min_rate": 4500},
"17": {"available": false, "min_rate": null},
"18": {"available": true, "min_rate": 6200}
}
}
Недоступные даты блокируются в календаре (disable в flatpickr). Минимальный тариф показывается при наведении. Запрос матрицы — тяжёлый: нужно агрегировать RoomInventory по датам. Кэширование обязательно: Bitrix\Main\Data\Cache с ключом availability_{month}_{year}, сброс при любом изменении в RoomInventory через обработчик OnAfterUpdate.
Сезонное ценообразование. Тариф за ночь зависит от сезона, дня недели, загруженности. Хранение — Highload-блок RatePlan:
| Поле | Тип |
|---|---|
| UF_ROOM_TYPE_ID | integer |
| UF_DATE_FROM | date |
| UF_DATE_TO | date |
| UF_WEEKDAY_RATE | float |
| UF_WEEKEND_RATE | float |
| UF_PRIORITY | integer |
При расчёте стоимости бронирования система перебирает каждую ночь, находит подходящий RatePlan (по дате и типу номера, с наибольшим приоритетом), определяет день недели и берёт UF_WEEKDAY_RATE или UF_WEEKEND_RATE. Итоговая сумма — сумма тарифов по всем ночам. При заполнении RoomInventory агент Битрикса предзаполняет поле UF_RATE для каждой строки — это кэш расчётной цены, чтобы не вычислять при каждом запросе доступности.
Интеграция с Channel Manager
Гостиница продаёт номера не только на своём сайте, но и через Booking.com, Expedia, Ostrovok. Без синхронизации — овербукинг. Channel manager — промежуточный слой, который синхронизирует доступность и тарифы между PMS, сайтом и OTA-каналами.
iCal-синхронизация — простейший вариант. Booking.com и Airbnb отдают и принимают .ics-файлы с заблокированными датами. Битрикс-агент раз в 15 минут:
- Забирает
.icsпо URL каждого канала (file_get_contentsили cURL) - Парсит
VEVENTблоки — извлекаетDTSTART,DTEND,SUMMARY - Обновляет
RoomInventory: проставляетUF_STATUS = 2(заблокирован) для соответствующих дат и номеров - Генерирует исходящий
.icsс бронированиями сайта и кладёт в/upload/ical/room_{id}.ics
Ограничение iCal: нет передачи тарифов, нет подтверждения бронирования, задержка синхронизации до 15 минут. Для небольшой гостиницы (до 30 номеров) — приемлемо. Для 100+ номеров с высокой загрузкой — нужен API-коннектор.
XML Push / API-интеграция с OTA-площадками — через REST или SOAP. Booking.com Connectivity API: отправка обновлений доступности (OTA_HotelAvailNotifRQ), тарифов (OTA_HotelRatePlanNotifRQ), получение бронирований (OTA_HotelResNotifRS). Формат — XML по спецификации OTA (OpenTravel Alliance). Это enterprise-уровень, требует сертификации подключения.
Интеграция с PMS
PMS (Property Management System) — система управления гостиницей: заселение, выселение, учёт, housekeeping. Распространённые: 1С:Отель, Fidelio, Opera PMS.
1С:Отель — обмен через COM-объект или HTTP-сервис на стороне 1С. Битрикс отправляет данные бронирования (гость, даты, номер, сумма), 1С создаёт документ «Бронь». Обратная синхронизация: 1С уведомляет сайт об изменении статуса (заселён, выселен, отменён) через webhook на endpoint Битрикса.
Opera / Fidelio — SOAP-интерфейс. WSDL-описание, вызов методов CreateReservation, ModifyReservation, CancelReservation. Аутентификация — WS-Security. Реализация на стороне Битрикса — класс-обёртка над SoapClient с логированием запросов.
Онлайн-оплата и предоплата
Бронирование через модуль sale. Заказ = одна товарная позиция «Проживание в {тип номера}, {check_in} — {check_out}». Цена — сумма тарифов по ночам. Свойства заказа: PROPERTY_CHECK_IN, PROPERTY_CHECK_OUT, PROPERTY_ROOM_TYPE_ID, PROPERTY_GUESTS.
Предоплата — не полная сумма, а процент (обычно 20-30%) или первая ночь. Реализация: кастомный обработчик OnSaleBeforeOrderAdd, который пересчитывает сумму к оплате. Полная сумма сохраняется в свойстве заказа PROPERTY_TOTAL_AMOUNT, к оплате — в поле PRICE корзины. Оставшаяся сумма — при заселении.
Платёжные системы: модуль sale поддерживает ЮKassa, CloudPayments, Stripe. Настройка — через административную панель, без кода.
Личный кабинет гостя
Авторизация — email + пароль, OAuth через Google, Apple. После входа:
-
Мои бронирования — список заказов из
saleс фильтром поUSER_ID. Статусы: ожидает оплаты, оплачен, подтверждён, заселён, завершён, отменён - История поездок — завершённые бронирования с возможностью оставить отзыв
-
Программа лояльности — баллы за бронирования. Highload-блок
LoyaltyPoints:USER_ID,POINTS,OPERATION(начисление/списание),BOOKING_ID,DATE. Баллы начисляются обработчиком событияOnSaleStatusOrderChangeпри переходе в статус «Завершён»
Мультиязычность
Для международных гостей — минимум 2-3 языка. Мультиязычность Битрикса через языковые версии сайта (/en/, /de/). Контент инфоблоков — через отдельные свойства (NAME_EN, DESCRIPTION_EN) или через модуль многосайтовости с привязкой инфоблоков к сайтам.
hreflang — в <head> каждой страницы:
<link rel="alternate" hreflang="ru" href="https://hotel.ru/rooms/standard/" />
<link rel="alternate" hreflang="en" href="https://hotel.ru/en/rooms/standard/" />
Переключатель валют — не конвертация в реальном времени, а фиксированные курсы в настройках модуля currency. Курсы обновляются агентом раз в сутки через API ЦБ или вручную.
SEO и микроразметка
Schema.org — тип Hotel + LodgingBusiness:
{
"@context": "https://schema.org",
"@type": "Hotel",
"name": "Grand Hotel Riviera",
"address": {
"@type": "PostalAddress",
"streetAddress": "ул. Набережная, 15",
"addressLocality": "Сочи"
},
"starRating": {
"@type": "Rating",
"ratingValue": "4"
},
"amenityFeature": [
{"@type": "LocationFeatureSpecification", "name": "Бассейн", "value": true},
{"@type": "LocationFeatureSpecification", "name": "Парковка", "value": true}
]
}
Для каждого типа номера — разметка HotelRoom с occupancy, bed, amenityFeature. Для предложений — Offer с priceSpecification и availabilityStarts.
Фотоцентричный дизайн
Гостиничный сайт — это 60% фотографии. Требования к загрузке: WebP с fallback на JPEG, <picture> с srcset для retina, lazy-load через loading="lazy". Оригиналы до 3000px по длинной стороне, превью — 800x600 для карточек, 400x300 для списков. Модуль iblock автоматически генерирует ресайзы через CFile::ResizeImageGet(), но WebP-конвертация требует серверной поддержки (GD или Imagick с WebP) и кастомного обработчика.
Отзывы
Внутренняя система отзывов — Highload-блок Reviews: USER_ID, ROOM_TYPE_ID, RATING (1-5), TEXT, DATE, STATUS (на модерации / опубликован). Публикация — после ручной модерации или автоматически через 24 часа. Агрегированный рейтинг — пересчитывается триггером при добавлении нового отзыва, хранится в свойстве инфоблока AVG_RATING.
Интеграция внешних отзывов (TripAdvisor, Google Reviews) — через виджеты или API, без хранения в Битриксе.
Этапы и сроки
| Масштаб | Сроки |
|---|---|
| Мини-отель, 10-20 номеров, базовое бронирование | 4-8 недель |
| Гостиница, 50-100 номеров, Channel Manager, PMS | 10-16 недель |
| Сеть отелей, мультисайт, программа лояльности | 16-24 недели |
- Аналитика и прототипирование (1-2 недели) — карта номерного фонда, логика бронирования, прототипы
- Дизайн (2-3 недели) — фотоцентричный UI, мобильная версия, календарь
- Ядро бронирования (3-5 недель) — RoomInventory, проверка доступности, оформление заказа, оплата
- Интеграции (2-4 недели) — PMS, Channel Manager, платёжные системы
- Контент и SEO (1-2 недели) — микроразметка, мультиязычность, мета-шаблоны
- Тестирование и запуск (1-2 недели) — нагрузочное, кроссбраузерное, деплой
Сроки не включают фотосъёмку номеров и создание 360-панорам — это параллельный процесс, который лучше начинать на этапе дизайна.







