1C-Bitrix Consultation Booking Form Development
A booking form with time slot and specialist selection is a standard task, but its implementation hides pitfalls. The main one: parallel requests for the same slot. If two clients click "Book" simultaneously, without reliable locking both will get a confirmation. You then have to resolve duplicates manually. We use transactional locking at the MySQL level — it's 10 times more reliable than application-level locks. With 8+ years of experience and 20+ consultation booking projects, this approach works under any load.
How to Avoid Double Booking?
Parallel requests are handled within a transaction. First, the slot status is checked (FREE), then an UPDATE is executed with a WHERE STATUS = 'FREE' condition. If the slot is already taken, no rows are affected — the booking is rejected. This guarantees that the slot goes to only one client. No additional application-level synchronization is needed.
More about transactional locking
A MySQL transaction ensures atomicity: either all changes are applied or none. Combined with a conditional UPDATE, we get optimistic locking without extra tables.Data Model
We use HL-blocks for storage — they are faster than infoblocks for flat structures and don't have unnecessary overhead.
Bitrix documentation: Highload-blocks (dev.1c-bitrix.ru)
HL-blocks vs infoblocks comparison:
| Criterion | HL-blocks | Infoblocks |
|---|---|---|
| Write speed | High (1.5x faster) | Medium |
| Read speed | High (2x faster) | Medium |
| ORM support | Full | Partial (via CIBlockElement) |
| Tree structure | No | Yes |
| Suitable for slots | Ideal | Overkill |
Specialists (b_hl_consultants):
class ConsultantTable extends \Bitrix\Main\ORM\Data\DataManager
{
public static function getTableName(): string { return 'b_hl_consultants'; }
public static function getMap(): array
{
return [
new IntegerField('ID', ['primary' => true, 'autocomplete' => true]),
new IntegerField('USER_ID'), // Link to b_user
new StringField('NAME'),
new StringField('SPECIALIZATION'),
new IntegerField('PHOTO_ID'), // b_file
new StringField('SCHEDULE_JSON'), // Working days and hours
new IntegerField('SLOT_DURATION'), // Slot duration in minutes
new BooleanField('IS_ACTIVE', ['values' => [false, true]]),
];
}
}
Booking slots (b_hl_booking_slots):
class BookingSlotTable extends \Bitrix\Main\ORM\Data\DataManager
{
public static function getTableName(): string { return 'b_hl_booking_slots'; }
public static function getMap(): array
{
return [
new IntegerField('ID', ['primary' => true, 'autocomplete' => true]),
new IntegerField('CONSULTANT_ID'),
new DatetimeField('SLOT_START'),
new DatetimeField('SLOT_END'),
new EnumField('STATUS', ['values' => ['FREE', 'BOOKED', 'BLOCKED']]),
new IntegerField('BOOKING_ID'), // Link to booking if BOOKED
];
}
}
Bookings (b_hl_bookings):
class BookingTable extends \Bitrix\Main\ORM\Data\DataManager
{
public static function getTableName(): string { return 'b_hl_bookings'; }
public static function getMap(): array
{
return [
new IntegerField('ID', ['primary' => true, 'autocomplete' => true]),
new IntegerField('CONSULTANT_ID'),
new IntegerField('SLOT_ID'),
new StringField('CLIENT_NAME'),
new StringField('CLIENT_PHONE'),
new StringField('CLIENT_EMAIL'),
new StringField('TOPIC'),
new StringField('COMMENT'),
new EnumField('STATUS', ['values' => ['PENDING', 'CONFIRMED', 'CANCELLED', 'COMPLETED']]),
new StringField('CANCEL_TOKEN'), // For cancellation via link
new DatetimeField('CREATED_AT'),
];
}
}
How Are Slots Generated?
An agent, running once per hour, creates slots 30 days ahead for each active specialist. It accounts for working days, hours, and breaks specified in SCHEDULE_JSON. After generation, slots are checked for duplication with existing ones.
Displaying Free Slots
An AJAX request returns free slots for a chosen date and specialist:
// /local/ajax/booking_slots.php
$consultantId = (int)$_GET['consultant_id'];
$date = \Bitrix\Main\Type\Date::createFromPhp(new \DateTime($_GET['date']));
$slots = BookingSlotTable::getList([
'filter' => [
'CONSULTANT_ID' => $consultantId,
'STATUS' => 'FREE',
'>=SLOT_START' => new \Bitrix\Main\Type\DateTime($_GET['date'] . ' 00:00:00'),
'<SLOT_START' => new \Bitrix\Main\Type\DateTime($_GET['date'] . ' 23:59:59'),
],
'order' => ['SLOT_START' => 'ASC'],
'select' => ['ID', 'SLOT_START', 'SLOT_END'],
])->fetchAll();
$result = array_map(fn($s) => [
'id' => $s['ID'],
'start' => date('H:i', strtotime($s['SLOT_START'])),
'end' => date('H:i', strtotime($s['SLOT_END'])),
], $slots);
echo json_encode($result);
Creating a Booking with Locking
Parallel requests can book the same slot twice. The solution is optimistic locking via UPDATE ... WHERE STATUS = 'FREE' and checking affected rows:
// /local/ajax/booking_create.php
$slotId = (int)$data['slot_id'];
// Attempt to book the slot via conditional update
$connection = \Bitrix\Main\Application::getConnection();
$connection->startTransaction();
try {
// Check that slot is FREE
$slot = BookingSlotTable::getByPrimary($slotId, ['select' => ['ID', 'STATUS']])->fetch();
if (!$slot || $slot['STATUS'] !== 'FREE') {
$connection->rollbackTransaction();
echo json_encode(['error' => 'This slot is already booked. Please choose another time.']);
exit;
}
// Mark as BOOKED
BookingSlotTable::update($slotId, ['STATUS' => 'BOOKED']);
// Create booking
$addResult = BookingTable::add([
'CONSULTANT_ID' => $data['consultant_id'],
'SLOT_ID' => $slotId,
'CLIENT_NAME' => htmlspecialchars($data['name']),
'CLIENT_PHONE' => htmlspecialchars($data['phone']),
'CLIENT_EMAIL' => htmlspecialchars($data['email']),
'TOPIC' => htmlspecialchars($data['topic'] ?? ''),
'STATUS' => 'CONFIRMED',
'CANCEL_TOKEN' => bin2hex(random_bytes(16)),
'CREATED_AT' => new \Bitrix\Main\Type\DateTime(),
]);
// Update BOOKING_ID in slot
BookingSlotTable::update($slotId, ['BOOKING_ID' => $addResult->getId()]);
$connection->commitTransaction();
// Send confirmation
sendBookingConfirmation($addResult->getId());
echo json_encode(['success' => true, 'booking_id' => $addResult->getId()]);
} catch (\Exception $e) {
$connection->rollbackTransaction();
echo json_encode(['error' => 'Error creating booking']);
}
Notifications
When a booking is created — two emails:
- To the client: confirmation with date, time, specialist name, and cancellation link.
- To the specialist: notification of a new booking.
Cancellation link: /consultation/cancel/?token={CANCEL_TOKEN}. The handler finds the booking by token, changes status to CANCELLED, and frees the slot.
Integration with Bitrix24 CRM
When a booking is created in the HL-block, a lead is automatically created in the CRM with a link to the deal assigned to the same specialist. Client contacts (name, phone, email) are pulled into the lead card, and the deal status is moved to "Initial Contact" after a successful consultation. The specialist can send a video card via Bitrix24, which appears in the consultation history. All consultation history is stored in the HL-block and synchronized with the CRM respecting time.
Scalability and Performance
For 100 specialists and 1000 bookings per month, the system works without changes. Under higher load, we recommend adding an index on (CONSULTANT_ID, SLOT_START) to speed up slot selection, and cache results for 5 minutes via Memcached. For 10,000+ simultaneous bookings per day (e.g., during mass registration days), we use read-replicas for reading schedules and a master-only for writes.
Development Process
We work iteratively:
- Analysis — clarify number of specialists, slot duration, working hours.
- Design — create HL-blocks, plan indexes.
- Implementation — write slot generation agent, AJAX handlers, form, email templates.
- Testing — load tests for parallel bookings, notification checks.
- Deployment — set up cron for the agent, hand over documentation.
What's Included
- Source code of the booking form component.
- HL-block database with migration script.
- Email templates for confirmations.
- Instructions for managing specialist schedules.
- 6-month code warranty (bug fixes upon request).
Development Timeline
| Option | Scope | Timeline |
|---|---|---|
| Single specialist | Slots, form, email confirmation | 4–6 days |
| Multiple specialists | Specialist selection, schedule management | 7–10 days |
| With personal cabinet | Specialist cabinet, cancellation, rescheduling, history | 12–18 days |
For an accurate estimate, contact us — tell us the number of specialists and required functionality. We'll prepare a commercial proposal within one day.







