Розробка форми запису на консультацію 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 днів |
Для точного розрахунку зв'яжіться з нами — назвіть кількість спеціалістів та необхідний функціонал. Ми підготуємо комерційну пропозицію за один день.







