Development of a service booking system 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

Booking System Development for 1C-Bitrix

The standard 1C-Bitrix "Online Store" module is designed for products with immediate payment, not for services with time slots. Trying to book a client for a haircut, consultation, or class using only the standard cart and orders functionality leads to a pile of workarounds: product with zero stock = "occupied", XML field = "time", emails instead of a real schedule. A proper booking system requires a separate architecture.

Data Model: Schedule and Slots

The core of the system is a schedule iblock. Optimal structure:

Iblock "Services" (SERVICES_IBLOCK_ID):

  • Standard fields: NAME, PREVIEW_TEXT, DETAIL_TEXT
  • Properties: DURATION (duration in minutes), PRICE, MAX_CAPACITY (group sessions), SPECIALIST_ID (link to specialist)

Iblock "Schedule" (SCHEDULE_IBLOCK_ID):

  • SERVICE_ID — link to service
  • SPECIALIST_ID — performer
  • DATE_FROM, DATE_TO — slot start and end (Date/Time property type)
  • STATUSfree/booked/blocked
  • BOOKING_ID — booking ID (set after reservation)

Bookings table (custom or via highload block):

CREATE TABLE bookings (
    ID INT AUTO_INCREMENT PRIMARY KEY,
    SLOT_ID INT NOT NULL,        -- schedule element ID
    USER_ID INT,                 -- user ID (NULL for unregistered)
    CLIENT_NAME VARCHAR(255),
    CLIENT_PHONE VARCHAR(20),
    CLIENT_EMAIL VARCHAR(255),
    STATUS ENUM('pending','confirmed','cancelled','completed') DEFAULT 'pending',
    COMMENT TEXT,
    CREATED_AT DATETIME,
    UPDATED_AT DATETIME,
    INDEX (SLOT_ID),
    INDEX (STATUS)
);

Schedule Slot Generation

Slots are generated based on working time templates. Approach: store weekly schedule templates per specialist and automatically create slots N weeks ahead.

class SlotGenerator
{
    public function generateForSpecialist(int $specialistId, \DateTime $from, \DateTime $to): void
    {
        $schedule = $this->getWeeklySchedule($specialistId); // [0=>[], 1=>['09:00','13:00',...]]
        $serviceDuration = $this->getServiceDuration($specialistId); // minutes

        $current = clone $from;
        while ($current <= $to) {
            $dayOfWeek = (int)$current->format('N'); // 1=Mon, 7=Sun
            $daySlots = $schedule[$dayOfWeek] ?? [];

            foreach ($daySlots as $timeRange) {
                [$start, $end] = explode('-', $timeRange); // '09:00-13:00'
                $this->createSlotsBetween($specialistId, $current, $start, $end, $serviceDuration);
            }
            $current->modify('+1 day');
        }
    }
}

Slots are created as iblock elements with status free. On booking — status changes to booked, on cancellation — back to free.

Time Selection Widget: Frontend Logic

The user selects a service → specialist → date → time. Each step is an AJAX request to a custom controller:

// /local/ajax/booking.php
class BookingController extends \CBitrixComponent
{
    public function getAvailableSlots(int $specialistId, string $date): array
    {
        $dateFrom = new \Bitrix\Main\Type\DateTime($date . ' 00:00:00');
        $dateTo = new \Bitrix\Main\Type\DateTime($date . ' 23:59:59');

        $result = \Bitrix\Iblock\ElementTable::getList([
            'filter' => [
                'IBLOCK_ID' => SCHEDULE_IBLOCK_ID,
                '=PROPERTY_SPECIALIST_ID' => $specialistId,
                '>=PROPERTY_DATE_FROM' => $dateFrom,
                '<=PROPERTY_DATE_FROM' => $dateTo,
                '=PROPERTY_STATUS' => 'free',
                '=ACTIVE' => 'Y',
            ],
            'select' => ['ID', 'PROPERTY_DATE_FROM', 'PROPERTY_DATE_TO'],
            'order' => ['PROPERTY_DATE_FROM' => 'ASC'],
        ]);
        // ...
    }
}

On the frontend — a calendar (e.g. FullCalendar or a custom React one) highlighting available days and time slots.

Handling Race Conditions During Booking

If two users select the same slot simultaneously, you must guarantee only one receives the booking. Solution using optimistic locking at the DB level:

public function bookSlot(int $slotId, array $clientData): BookingResult
{
    // Transaction + row lock
    $connection = \Bitrix\Main\Application::getConnection();
    $connection->startTransaction();

    try {
        // SELECT FOR UPDATE — lock the row
        $slot = $connection->query(
            "SELECT * FROM b_iblock_element_property
             WHERE IBLOCK_ELEMENT_ID = {$slotId} AND IBLOCK_PROPERTY_ID = " . STATUS_PROP_ID . "
             FOR UPDATE"
        )->fetch();

        if ($slot['VALUE'] !== 'free') {
            $connection->rollbackTransaction();
            return BookingResult::slotTaken();
        }

        // Update slot status
        $this->updateSlotStatus($slotId, 'booked');

        // Create booking
        $bookingId = $this->createBooking($slotId, $clientData);

        $connection->commitTransaction();
        return BookingResult::success($bookingId);

    } catch (\Exception $e) {
        $connection->rollbackTransaction();
        throw $e;
    }
}

Notifications and Reminders

After booking — an immediate notification to both the client and the specialist. 24 hours and 2 hours before — reminders. Implemented via Bitrix agents:

// Create a reminder agent on booking
\CAgent::AddAgent(
    '\BookingModule\ReminderAgent::send(' . $bookingId . ');',
    'my_booking_module',
    'N', // non-periodic
    0,
    '',
    'Y',
    ConvertTimeStamp($bookingDateTs - 86400, 'FULL') // 24 hours before
);

Notification channels: email (via main module), SMS (via external gateway), Telegram bot. Notification templates — via standard Bitrix mail events (CEvent::Send).

Payment Integration

If the booking requires a prepayment — create an order in the Bitrix online store:

$order = \Bitrix\Sale\Order::create(SITE_ID, $userId);
$order->setPersonTypeId(1);

$basket = $order->getBasket();
$item = \Bitrix\Sale\BasketItem::create($basket, 'catalog', $serviceElementId);
$item->setFields(['QUANTITY' => 1, 'PRICE' => $servicePrice, 'NAME' => $serviceName]);
$basket->addItem($item);

$order->save();
// Redirect to payment page

The booking-to-order relationship is stored in a table via the ORDER_ID field. On order payment — a webhook or OnSaleOrderPaid event handler confirms the booking.

Admin Interface

For administrators and specialists — a page in the /bitrix/admin/ section with:

  • View of all bookings for a date (calendar view)
  • Ability to block slots (vacation, lunch)
  • Manual confirmation/cancellation with client notification
  • CSV export for a date range

Development Stages

Stage Contents Timeline
Design Data model, usage scenarios 3–5 days
Iblocks and slot generator Creating structure, generating schedule 1 week
AJAX controllers Slot selection API, booking 1 week
Frontend widget Calendar, specialist and time selection 1–2 weeks
Notifications Email, SMS, reminders via agents 3–5 days
Admin interface Schedule management, booking view 1 week
Payment integration (optional) Prepayment via orders 1 week

Total: 6–10 weeks depending on the feature set.