Setting up an online room booking module on 1C-Bitrix

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1175
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

Setting Up an Online Room Booking Module on 1C-Bitrix

1C-Bitrix has no standard room booking module. It provides the sale module (orders), info blocks, and agents. A booking system is assembled from these components, but the gap between "tools exist" and "booking works" is several weeks of design and development. The core technical problem the module solves is atomic availability checking with protection against two users simultaneously reserving the same room.

Data Structure

A typical schema for accommodation properties:

Info block hotel_rooms — room catalogue:

  • PROPERTY_ROOM_TYPE — type (standard, suite, apartment)
  • PROPERTY_CAPACITY — capacity
  • PROPERTY_AREA — area
  • PROPERTY_FLOOR — floor
  • PROPERTY_BED_TYPE — bed configuration (one double, two singles)
  • PROPERTY_AMENITIES — list of amenities (multiple property)

Bookings table 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');

Atomic Availability Check and Booking Creation

Race condition is the primary enemy of booking systems. Two users simultaneously open the booking form for the same room, both check availability (free), both click "Book". Without locking, both receive confirmation.

The solution is a transaction with an explicit row lock:

$connection = \Bitrix\Main\Application::getConnection();
$connection->startTransaction();

try {
    // Lock the room record for update
    $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' => 'Room unavailable for the selected dates'];
    }

    $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;
}

Integration with the Sale Module

After creating a booking in pending status, an order is created in the sale module:

$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'      => 'Room ' . $roomName . ' (' . $nights . ' nights)',
    'QUANTITY'  => 1,
    'PRICE'     => $totalPrice,
    'CURRENCY'  => 'RUB',
]);
$basket->addItem($item);
$order->save();

// Link order_id to the booking
BookingTable::update($bookingId, ['ORDER_ID' => $order->getId()]);

Upon payment (event OnSalePaymentEntitySaved, IS_PAID = Y), the booking transitions to confirmed.

Agent for Releasing Expired Bookings

Bookings with pending status and an elapsed expires_at must be released automatically:

function ReleaseExpiredRoomBookings(): string
{
    \Bitrix\Main\Application::getConnection()->queryExecute(
        "UPDATE bl_room_booking
         SET status = 'expired'
         WHERE status = 'pending' AND expires_at < NOW()"
    );
    // Cancel associated sale orders
    $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__ . '();';
}

The agent is registered with a 60-second interval.

Administrative Interface

A "Bookings" section is added to /bitrix/admin/. Key views:

  • Calendar grid — rows = room types, columns = dates. Cells are colour-coded by booking status. Implemented as a custom page with a table built from bl_room_booking.
  • Booking list — standard grid with filters by status, dates, and guest.
  • Booking card — details, status change buttons, linked order.

Timeline

Phase Duration
Room info block + DB schema 2 days
Backend: check, create, agent 4 days
Booking form on site (AJAX, calendar) 3 days
Integration with sale module and payment systems 2 days
Administrative interface 3 days
Testing 2 days
Total 2–3 weeks