Розробка форми запису на консультацію 1С-Бітрікс

Наша компанія займається розробкою, підтримкою та обслуговуванням рішень на Бітрікс та Бітрікс24 будь-якої складності. Від простих односторінкових сайтів до складних інтернет-магазинів, CRM систем з інтеграцією 1С та телефонії. Досвід розробників підтверджено сертифікатами від вендора.
Пропоновані послуги
Показано 1 з 1 послугУсі 1626 послуг
Розробка форми запису на консультацію 1С-Бітрікс
Середня
~1-2 тижні
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Розробка на базі Бітрікс, Бітрікс24, 1С для компанії Development of an Online
    585
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Розробка на базі 1С Підприємство для компанії МИРСАНБЕЛ
    751
  • image_crm_dolbimby_434_0.webp
    Розробка сайту на CRM Бітрікс24 для компанії DOLBIMBY
    657
  • image_crm_technotorgcomplex_453_0.webp
    Розробка на базі Бітрікс24 для компанії ТЕХНОТОРГКОМПЛЕКС
    989

Розробка форми запису на консультацію 1С-Бітрікс

Форма запису на консультацію — це не просто форма зворотного зв'язку. Користувач має обрати зручний час, спеціаліста, тему — і одразу отримати підтвердження. Основна технічна складність: відображення реального розкладу (незайнятих слотів) і блокування обраного часу від паралельних записів. На 1С-Бітрікс це вирішується через HL-блоки для розкладу та транзакційне блокування при створенні запису.

Модель даних

Спеціалісти (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'),        // Посилання на b_user
            new StringField('NAME'),
            new StringField('SPECIALIZATION'),
            new IntegerField('PHOTO_ID'),       // b_file
            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'),    // Посилання на запис, якщо BOOKED
        ];
    }
}

Записи (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'),
        ];
    }
}

Генерація слотів

Слоти генеруються агентом або при першому запиті. Розклад спеціаліста — JSON з робочими годинами:

{
  "1": {"start": "09:00", "end": "18:00"},
  "2": {"start": "09:00", "end": "18:00"},
  "3": {"start": "10:00", "end": "16:00"},
  "4": {"start": "09:00", "end": "18:00"},
  "5": {"start": "09:00", "end": "17:00"}
}

Генерація слотів на 30 днів уперед:

class SlotGenerator
{
    public function generateForConsultant(int $consultantId, int $daysAhead = 30): void
    {
        $consultant = ConsultantTable::getByPrimary($consultantId)->fetch();
        $schedule   = json_decode($consultant['SCHEDULE_JSON'], true);
        $duration   = (int)$consultant['SLOT_DURATION']; // наприклад, 60 хвилин

        for ($day = 0; $day <= $daysAhead; $day++) {
            $date    = new \DateTime("+{$day} days");
            $weekDay = $date->format('N'); // 1=Пн, 7=Нд

            if (!isset($schedule[$weekDay])) {
                continue; // Вихідний
            }

            $daySchedule = $schedule[$weekDay];
            $slotStart   = new \DateTime($date->format('Y-m-d') . ' ' . $daySchedule['start']);
            $slotEnd     = new \DateTime($date->format('Y-m-d') . ' ' . $daySchedule['end']);

            // Перевірити, що слот ще не створено
            $existingCount = BookingSlotTable::getCount([
                '>=SLOT_START' => $slotStart,
                '<SLOT_START'  => $slotEnd,
                'CONSULTANT_ID' => $consultantId,
            ]);

            if ($existingCount > 0) {
                continue;
            }

            $current = clone $slotStart;
            while ($current < $slotEnd) {
                $next = clone $current;
                $next->modify("+{$duration} minutes");

                if ($next > $slotEnd) break;

                BookingSlotTable::add([
                    'CONSULTANT_ID' => $consultantId,
                    'SLOT_START'    => \Bitrix\Main\Type\DateTime::createFromPhp($current),
                    'SLOT_END'      => \Bitrix\Main\Type\DateTime::createFromPhp($next),
                    'STATUS'        => 'FREE',
                ]);

                $current = $next;
            }
        }
    }
}

Відображення вільних слотів

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' => 'Помилка при створенні запису']);
}

Сповіщення

При створенні запису — два листи:

  1. Клієнту: підтвердження з датою, часом, іменем спеціаліста та посиланням для скасування.
  2. Спеціалісту: сповіщення про новий запис.

Посилання для скасування: /consultation/cancel/?token={CANCEL_TOKEN}. Обробник знаходить запис за токеном, переводить у статус CANCELLED і звільняє слот.

Терміни розробки

Варіант Склад Термін
Один спеціаліст Слоти, форма, підтвердження по email 4–6 днів
Кілька спеціалістів Вибір спеціаліста, управління розкладом 7–10 днів
З особистим кабінетом ОК спеціаліста, скасування, перенос, історія 12–18 днів