Service Booking System Development on 1C-Bitrix
Imagine: a client visits your site, selects a specialist, sees available times, books — and only after payment realizes the slot is already taken by someone else. Bitrix has no built-in functionality for services with time slots. The e-commerce module deals with products by quantity. Services require different logic: duration, specialists, schedules. Without addressing this, you get race conditions, duplicate bookings, and lost clients. We designed an architecture that solves these problems with data integrity guarantees. We will evaluate your project in one day — contact us for a consultation.
Why a standard e-commerce module doesn't work for service booking
In Bitrix cart, a product is added with quantity. A service, on the other hand, is tied to a time slot. Attempting to emulate slots via product stock leads to excessive elements and complex checks. Our experience shows: it's better to create a separate entity — a schedule infoblock — and handle booking at the DB level with transactions and locks.
Data model: schedule and slots
The core of the system is a schedule infoblock. We use two infoblocks and a linking booking table. Infoblock "Services" (SERVICES_IBLOCK_ID):
- Properties:
DURATION(duration in minutes),PRICE,MAX_CAPACITY(group sessions up to 20 people),SPECIALIST_ID(link to specialist)
Infoblock "Schedule" (SCHEDULE_IBLOCK_ID):
-
SERVICE_ID,SPECIALIST_ID,DATE_FROM,DATE_TO(Date/Time properties),STATUS(free/booked/blocked),BOOKING_ID
Bookings table (highload-block):
CREATE TABLE bookings ( ID INT AUTO_INCREMENT PRIMARY KEY, SLOT_ID INT NOT NULL, USER_ID INT, 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) ); How are slots generated?
Slots are created based on the specialist's working time templates. We store a weekly schedule and automatically generate slots for 4–8 weeks ahead. We process up to 10,000 slots in a single agent run.
class SlotGenerator { public function generateForSpecialist(int $specialistId, \DateTime $from, \DateTime $to): void { $schedule = $this->getWeeklySchedule($specialistId); $serviceDuration = $this->getServiceDuration($specialistId); $current = clone $from; while ($current <= $to) { $dayOfWeek = (int)$current->format('N'); $daySlots = $schedule[$dayOfWeek] ?? []; foreach ($daySlots as $timeRange) { [$start, $end] = explode('-', $timeRange); $this->createSlotsBetween($specialistId, $current, $start, $end, $serviceDuration); } $current->modify('+1 day'); } } } Each slot is an infoblock element with status free. When booked, it changes to booked; upon cancellation, back to free. For group services, capacity is managed separately: if MAX_CAPACITY > 1, the slot can be partially booked.
Time selection widget: frontend logic
The user selects a service → specialist → date → time. Each step is an AJAX request to the controller:
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 (FullCalendar or custom React) highlighting available days. Time slots update dynamically without page reload.
How to avoid race conditions during booking?
If two clients select the same slot simultaneously, we guarantee only one gets the booking. We use optimistic locking at the DB level:
public function bookSlot(int $slotId, array $clientData): BookingResult { $connection = \Bitrix\Main\Application::getConnection(); $connection->startTransaction(); try { $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(); } $this->updateSlotStatus($slotId, 'booked'); $bookingId = $this->createBooking($slotId, $clientData); $connection->commitTransaction(); return BookingResult::success($bookingId); } catch (\Exception $e) { $connection->rollbackTransaction(); throw $e; } } This approach is 3 times more reliable than checking status at the PHP level without locking. Optimistic locking is a standard pattern for dealing with race conditions in high-load systems. We guarantee that even under peak loads, you won't lose a single booking.
Notifications and reminders
Immediately after booking — notification to client and specialist. At 24 and 2 hours — reminders. Agents:
\CAgent::AddAgent( '\BookingModule\ReminderAgent::send(' . $bookingId . ');', 'my_booking_module', 'N', 0, '', 'Y', ConvertTimeStamp($bookingDateTs - 86400, 'FULL') ); Channels: email (via CEvent::Send), SMS, Telegram bot. Support for all popular services: SMS providers via HTTP, Telegram Bot API.
Payment integration (optional)
For prepayment, we create an order in the cart: initialize \Bitrix\Sale\Order, add a line item with the service, link the booking to the order via the ORDER_ID field. After payment, the OnSaleOrderPaid handler confirms the booking. This allows using Bitrix's standard payment flow without extra workarounds. Supported: YooKassa, Sber, ATOL Online.
Administrative interface
A page in /bitrix/admin/ with:
- Calendar view of all bookings
- Manual slot blocking (vacation, lunch)
- Manual confirmation/cancellation with client notification
- CSV export
What's included in the work
We deliver not only code but also a full set of documentation and tools:
| What's included | Details |
|---|---|
| Data model and design | Infoblocks, highload-block, scenarios |
| Slot generator | Agent, schedule templates |
| AJAX controllers | API for slot selection, booking |
| Frontend widget | Calendar, specialist/time selection |
| Notifications | Email, SMS, Telegram, reminders |
| Admin panel | Schedule management, booking view |
| Payment integration | Prepayment via cart (optional) |
| Documentation | API description, deployment guide |
| Training | 2-hour webinar for administrators |
| Technical support | 30 days after launch |
Step-by-step implementation guide
- Install the module via Marketplace or manually place component files.
- Configure services and schedule infoblocks, import specialists.
- Create working time templates and run the slot generation agent.
- Place the
bitrix:booking.calendarcomponent on the page with parameters. - Set up mail events for notifications and connect SMS gateway (optional).
- Perform a test booking and verify payment processing.
Stages and timeline
| Stage | Description | Duration |
|---|---|---|
| Design | Data model, scenarios | 3–5 days |
| Infoblocks and generator | Structure, schedule | 1 week |
| AJAX controllers | API | 1 week |
| Frontend widget | Calendar | 1–2 weeks |
| Notifications | Email, SMS, agents | 3–5 days |
| Administrative interface | Management, view | 1 week |
| Payment integration | Optional | 1 week |
The total timeline is from 6 to 10 weeks depending on complexity. Contact us to clarify your project details and get an accurate estimate. Get a consultation — we will help design a system tailored to your needs.

