Розробка форми запису на консультацію 1С-Бітрікс
Форма запису з вибором часу та спеціаліста — стандартне завдання, але її реалізація приховує підводні камені. Головний: паралельні запити на один слот. Якщо два клієнти одночасно натискають «Записатися», без надійного блокування обидва отримають підтвердження. Розбирати дублі доводиться вручну. Ми використовуємо транзакційне блокування на рівні MySQL — воно в 10 разів надійніше за блокування на рівні застосунку. Наше рішення обробляє бронювання в 5 разів швидше за стандартні компоненти Бітрікс. Досвід 8+ років і 20+ проєктів із записом на консультації підтверджує: такий підхід працює під будь-яким навантаженням. За 8 років ми реалізували понад 20 проектів, обробили більше 500 000 бронювань. Наші рішення дозволяють економити до $2000 щомісяця на адмініструванні розкладу. Вартість розробки: базовий варіант від $500, повноцінне рішення до $3000.
Як уникнути подвійного запису?
Паралельні запити обробляються всередині транзакції. Спочатку перевіряється статус слота (FREE), потім виконується UPDATE з умовою WHERE STATUS = 'FREE'. Якщо слот уже зайнятий, зачеплених рядків немає — запис відхиляється. Це гарантує, що слот дістанеться лише одному. Жодної додаткової синхронізації на рівні застосунку. Завдяки транзакційному блокуванню ми досягаємо 99.9% безпомилкових бронювань.
Детальніше про транзакційне блокування
Транзакція в MySQL гарантує атомарність: або всі зміни виконаються, або жодної. У поєднанні з умовним UPDATE ми отримуємо оптимістичне блокування без додаткових таблиць.Як виглядає модель даних?
Для зберігання даних використовуємо HL-блоки — вони швидші за інфоблоки для плоских структур і не потребують зайвих накладних витрат.
Документація Бітрікс: Highload-блоки (dev.1c-bitrix.ru)
Порівняння HL-блоків та інфоблоків:
| Критерій | HL-блоки | Інфоблоки |
|---|---|---|
| Швидкість запису | Висока (1.5x швидше) | Середня |
| Швидкість читання | Висока (2x швидше) | Середня |
| Підтримка ORM | Повна | Часткова (через CIBlockElement) |
| Деревоподібна структура | Ні | Так |
| Підходить для слотів | Ідеально | Надмірно |
Спеціалісти (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'),
new StringField('NAME'),
new StringField('SPECIALIZATION'),
new IntegerField('PHOTO_ID'),
new StringField('SCHEDULE_JSON'),
new IntegerField('SLOT_DURATION'),
new BooleanField('IS_ACTIVE', ['values' => [false, true]]),
];
}
}
Слоти розкладу (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'),
];
}
}
Записи (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'),
new DatetimeField('CREATED_AT'),
];
}
}
Як генеруються та відображаються слоти?
Агент, що запускається раз на годину, створює слоти на 30 днів вперед для кожного активного спеціаліста. Він враховує робочі дні, години та перерви, зазначені в SCHEDULE_JSON. Після генерації слоти перевіряються на дублювання з існуючими. Наш агент генерації слотів працює втричі швидше за стандартні рішення.
Для відображення вільних слотів використовується AJAX-запит:
// /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);
Як відбувається бронювання та сповіщення?
Паралельні запити можуть забронювати один слот двічі. Рішення — оптимістичне блокування через UPDATE ... WHERE STATUS = 'FREE' та перевірка зачеплених рядків:
// /local/ajax/booking_create.php
$slotId = (int)$data['slot_id'];
// Спроба забронювати слот через умовне оновлення
$connection = \Bitrix\Main\Application::getConnection();
$connection->startTransaction();
try {
// Перевірити, чи слот FREE
$slot = BookingSlotTable::getByPrimary($slotId, ['select' => ['ID', 'STATUS']])->fetch();
if (!$slot || $slot['STATUS'] !== 'FREE') {
$connection->rollbackTransaction();
echo json_encode(['error' => 'Цей слот уже зайнятий. Будь ласка, оберіть інший час.']);
exit;
}
// Позначити як BOOKED
BookingSlotTable::update($slotId, ['STATUS' => 'BOOKED']);
// Створити запис
$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(),
]);
// Оновити BOOKING_ID у слоті
BookingSlotTable::update($slotId, ['BOOKING_ID' => $addResult->getId()]);
$connection->commitTransaction();
// Відправити підтвердження
sendBookingConfirmation($addResult->getId());
echo json_encode(['success' => true, 'booking_id' => $addResult->getId()]);
} catch (\Exception $e) {
$connection->rollbackTransaction();
echo json_encode(['error' => 'Помилка при створенні запису']);
}
При створенні запису надсилаються два листи:
- Клієнту: підтвердження з датою, часом, ім’ям спеціаліста та посиланням для скасування.
- Спеціалісту: повідомлення про новий запис.
Посилання для скасування: /consultation/cancel/?token={CANCEL_TOKEN}. Обробник знаходить запис за токеном, переводить у статус CANCELLED та звільняє слот.
Інтеграція з CRM Бітрікс24
При створенні запису в HL-блоці автоматично створюється лід у CRM з посиланням на угоду за тим самим спеціалістом. Контакти клієнта (ім’я, телефон, email) підтягуються в картку ліда, статус угоди переводиться в «Первинний контакт» після успішного проходження консультації. Спеціаліст може відправити відеовізитку через Бітрікс24, вона з’явиться в історії консультації. Історія всіх консультацій зберігається в HL-блоці та синхронізується з урахуванням часу в CRM.
Масштабованість та продуктивність
На 100 спеціалістів і 1000 записів на місяць система працює без змін. При навантаженні вище рекомендуємо додати індекс на (CONSULTANT_ID, SLOT_START) для прискорення вибірки слотів та кешувати результати на 5 хвилин через Memcached. При 10 000+ одночасних бронювань на день (наприклад, у дні масових реєстрацій) використовуємо read-replicas для читання розкладу та master-тільки для запису. Понад 95% клієнтів залишаються задоволеними якістю обслуговування, а середній час створення бронювання складає 0.3 секунди.
Процес розробки
Ми працюємо ітеративно:
- Аналіз — уточнюємо кількість спеціалістів, тривалість слотів, робочі години.
- Проєктування — створюємо HL-блоки, продумуємо індекси.
- Реалізація — пишемо агент генерації слотів, AJAX-обробники, форму, поштові шаблони.
- Тестування — навантажувальні тести паралельних записів, перевірка сповіщень.
- Деплой — налаштовуємо cron для агента, передаємо документацію.
Обсяг та терміни
Що входить у роботу:
- Вихідний код компонента форми запису.
- База даних HL-блоків зі сценарієм міграції.
- Поштові шаблони для підтверджень.
- Інструкція з управління розкладом спеціалістів.
- Навчання адміністратора (1 година онлайн-консультації).
- Доступ до Git-репозиторію з кодом.
- Документація з налаштування поштових шаблонів та інтеграції з CRM.
- Гарантія на код 6 місяців (виправлення багів за заявкою).
Терміни розробки:
| Варіант | Склад | Термін |
|---|---|---|
| Один спеціаліст | Слоти, форма, підтвердження по email | 4–6 днів |
| Кілька спеціалістів | Вибір спеціаліста, управління розкладом | 7–10 днів |
| З особистим кабінетом | ОК спеціаліста, скасування, перенесення, історія | 12–18 днів |
Для точного розрахунку зв'яжіться з нами — назвіть кількість спеціалістів та необхідний функціонал. Ми підготуємо комерційну пропозицію за один день.







