Настройка модуля онлайн-бронирования номеров на 1С-Битрикс
В Битрикс нет стандартного модуля бронирования номеров. Есть модуль sale (заказы), инфоблоки, агенты. Из этого собирается система бронирования, но разрыв между «есть инструменты» и «работает бронирование» — это несколько недель проектирования и разработки. Главная техническая проблема, которую решает модуль: атомарная проверка доступности с защитой от одновременного резервирования одного номера двумя пользователями.
Структура данных
Типовая схема для объектов размещения:
Инфоблок hotel_rooms — каталог номеров:
-
PROPERTY_ROOM_TYPE— тип (стандарт, люкс, апартаменты) -
PROPERTY_CAPACITY— вместимость -
PROPERTY_AREA— площадь -
PROPERTY_FLOOR— этаж -
PROPERTY_BED_TYPE— тип кроватей (одна двуспальная, две односпальные) -
PROPERTY_AMENITIES— список удобств (множественное свойство)
Таблица броней bl_room_booking:
CREATE TABLE bl_room_booking (
id SERIAL PRIMARY KEY,
room_id INT NOT NULL,
order_id INT REFERENCES b_sale_order(ID),
user_id INT REFERENCES b_user(ID),
date_from DATE NOT NULL,
date_to DATE NOT NULL,
nights SMALLINT GENERATED ALWAYS AS (date_to - date_from) STORED,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
rate_code VARCHAR(64),
adults SMALLINT DEFAULT 1,
children SMALLINT DEFAULT 0,
price_night NUMERIC(10,2),
price_total NUMERIC(10,2),
guest_name VARCHAR(255),
guest_email VARCHAR(255),
guest_phone VARCHAR(50),
comment TEXT,
created_at TIMESTAMP DEFAULT NOW(),
expires_at TIMESTAMP,
CONSTRAINT chk_dates CHECK (date_to > date_from)
);
CREATE INDEX idx_booking_room_dates ON bl_room_booking(room_id, date_from, date_to) WHERE status IN ('pending', 'confirmed');
Атомарная проверка и создание брони
Race condition — главный враг систем бронирования. Два пользователя одновременно открыли форму на один номер, оба проверили доступность (свободно), оба нажали «Забронировать». Без блокировки оба получают подтверждение.
Решение — транзакция с явной блокировкой строки:
$connection = \Bitrix\Main\Application::getConnection();
$connection->startTransaction();
try {
// Блокируем запись номера для обновления
$connection->query(
"SELECT id FROM bl_room_booking
WHERE room_id = ?
AND status IN ('pending', 'confirmed')
AND date_from < ?
AND date_to > ?
FOR UPDATE",
[$roomId, $dateTo, $dateFrom]
);
$conflicts = $connection->getAffectedRowsCount();
if ($conflicts > 0) {
$connection->rollbackTransaction();
return ['error' => 'Номер недоступен на выбранные даты'];
}
$bookingId = $connection->query(
"INSERT INTO bl_room_booking
(room_id, user_id, date_from, date_to, status, expires_at, price_total)
VALUES (?, ?, ?, ?, 'pending', NOW() + INTERVAL '20 minutes', ?)
RETURNING id",
[$roomId, $userId, $dateFrom, $dateTo, $totalPrice]
)->fetch()['id'];
$connection->commitTransaction();
return ['booking_id' => $bookingId];
} catch (\Exception $e) {
$connection->rollbackTransaction();
throw $e;
}
Связь с модулем sale
После создания брони в статусе pending создаём заказ в модуле sale:
$order = \Bitrix\Sale\Order::create(SITE_ID, $userId);
$order->setField('CURRENCY', 'RUB');
$basket = $order->getBasket();
$item = \Bitrix\Sale\BasketItem::create($basket, 'catalog', $roomProductId);
$item->setFields([
'NAME' => 'Номер ' . $roomName . ' (' . $nights . ' ночей)',
'QUANTITY' => 1,
'PRICE' => $totalPrice,
'CURRENCY' => 'RUB',
]);
$basket->addItem($item);
$order->save();
// Привязываем order_id к брони
BookingTable::update($bookingId, ['ORDER_ID' => $order->getId()]);
При оплате заказа (событие OnSalePaymentEntitySaved, IS_PAID = Y) переводим бронь в confirmed.
Агент освобождения просроченных броней
Брони со статусом pending и истёкшим expires_at должны освобождаться автоматически:
function ReleaseExpiredRoomBookings(): string
{
\Bitrix\Main\Application::getConnection()->queryExecute(
"UPDATE bl_room_booking
SET status = 'expired'
WHERE status = 'pending' AND expires_at < NOW()"
);
// Отменяем связанные заказы в sale
$expired = \Bitrix\Main\Application::getConnection()->query(
"SELECT order_id FROM bl_room_booking WHERE status = 'expired' AND order_id IS NOT NULL AND notified = false"
);
while ($row = $expired->fetch()) {
$order = \Bitrix\Sale\Order::load($row['order_id']);
if ($order) $order->setField('STATUS_ID', 'CANCEL');
}
return __FUNCTION__ . '();';
}
Агент регистрируется с интервалом 60 секунд.
Административный интерфейс
В /bitrix/admin/ добавляется раздел «Бронирования». Ключевые представления:
-
Календарная сетка — строки = типы номеров, столбцы = даты. Ячейки закрашены по статусу брони. Реализуется через кастомную страницу с таблицей из
bl_room_booking. - Список броней — стандартный грид с фильтрами по статусу, датам, гостю.
- Карточка брони — детали, кнопки изменения статуса, привязанный заказ.
Сроки
| Этап | Срок |
|---|---|
| Инфоблок номеров + схема БД | 2 дня |
| Бэкенд: проверка, создание, агент | 4 дня |
| Форма бронирования на сайте (AJAX, календарь) | 3 дня |
| Связь с модулем sale и платёжными системами | 2 дня |
| Административный интерфейс | 3 дня |
| Тестирование | 2 дня |
| Итого | 2–3 недели |







