Як розробити надійний онлайн-запис: модель, API, віджет

Як розробити систему онлайн-запису з нуля?

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Як розробити надійний онлайн-запис: модель, API, віджет
Середній
від 1 тижня до 3 місяців

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

Часті запитання

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

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1418
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1285
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    983
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1242
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    983
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Розробка веб-сайту для компанії ФІКСПЕР
    997

Як розробити систему онлайн-запису з нуля?

Уявіть: стоматологія з п'ятьма лікарями та трьома кабінетами. Клієнти телефонують, адміністратор звіряється з паперовим журналом — і все одно виникають подвійні записи. Або фітнес-клуб: 20 групових занять на день, кожне з різною місткістю. Без автоматизації — хаос. Онлайн-запис — це не просто форма «виберіть дату та час». Це керування розкладом спеціалістів, буферами між записами, правилами бронювання, сповіщеннями та скасуваннями. Недооцінка цієї складності призводить до подвійних записів, порожніх слотів та незадоволених клієнтів. Наша команда розробляє рішення «під ключ» з гарантією надійності: досвід 10+ проєктів, сертифіковані спеціалісти.

Чому проста форма не підходить?

Типова помилка — зберігати розклад у вигляді плоских таблиць без урахування винятків. Спеціаліст може працювати в різний час по днях, брати вихідні, змінювати графік. Якщо не виділити робочі години та перевизначення, слоти стають невалідними. Інша проблема — гонка умов: два клієнти одночасно бачать вільний слот і записуються, створюючи конфлікт. Рішення — детально спроєктована модель даних та блокування на рівні транзакції.

Як спроєктувати модель даних для бронювання?

Правильна модель — основа надійної системи. Нижче наведено ключові таблиці, які ми використовуємо в продакшені.

CREATE TABLE staff ( id BIGSERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, timezone VARCHAR(64) DEFAULT 'Europe/Moscow' ); CREATE TABLE working_hours ( id BIGSERIAL PRIMARY KEY, staff_id BIGINT REFERENCES staff(id), day_of_week SMALLINT NOT NULL, -- 0=Вс, 1=Пн ... 6=Сб start_time TIME NOT NULL, end_time TIME NOT NULL ); CREATE TABLE schedule_overrides ( id BIGSERIAL PRIMARY KEY, staff_id BIGINT REFERENCES staff(id), date DATE NOT NULL, is_day_off BOOLEAN DEFAULT FALSE, start_time TIME, end_time TIME ); CREATE TABLE services ( id BIGSERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, duration_min INT NOT NULL, buffer_after INT DEFAULT 0, capacity INT DEFAULT 1 ); CREATE TABLE appointments ( id BIGSERIAL PRIMARY KEY, staff_id BIGINT REFERENCES staff(id), service_id BIGINT REFERENCES services(id), client_id BIGINT REFERENCES clients(id), starts_at TIMESTAMPTZ NOT NULL, ends_at TIMESTAMPTZ NOT NULL, status VARCHAR(32) DEFAULT 'pending', notes TEXT, created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX ON appointments(staff_id, starts_at); 

Як працює генерація слотів?

Ключова логіка — отримання доступних часових вікон. Алгоритм враховує робочі години, перевизначення та вже зайняті інтервали. Слоти створюються з кроком «тривалість послуги + буфер».

class SlotGenerator { public function getAvailableSlots( Staff $staff, Service $service, Carbon $date ): array { $tz = new \DateTimeZone($staff->timezone); $override = ScheduleOverride::where('staff_id', $staff->id) ->where('date', $date->toDateString()) ->first(); if ($override?->is_day_off) { return []; } $dayOfWeek = $date->dayOfWeek; $workHours = $override ?? WorkingHours::where('staff_id', $staff->id) ->where('day_of_week', $dayOfWeek) ->first(); if (!$workHours) { return []; } $windowStart = Carbon::parse($date->toDateString() . ' ' . $workHours->start_time, $tz); $windowEnd = Carbon::parse($date->toDateString() . ' ' . $workHours->end_time, $tz); $busy = Appointment::where('staff_id', $staff->id) ->whereIn('status', ['pending', 'confirmed']) ->whereBetween('starts_at', [$windowStart, $windowEnd]) ->orderBy('starts_at') ->get(['starts_at', 'ends_at']) ->map(fn($a) => [ 'start' => Carbon::parse($a->starts_at), 'end' => Carbon::parse($a->ends_at), ]) ->toArray(); $slotDuration = $service->duration_min + $service->buffer_after; $minAdvance = now()->addMinutes(30); $slots = []; $cursor = clone $windowStart; while ($cursor->copy()->addMinutes($service->duration_min)->lte($windowEnd)) { $slotEnd = $cursor->copy()->addMinutes($service->duration_min); $occupied = collect($busy)->first(fn($b) => $cursor->lt($b['end']) && $slotEnd->gt($b['start']) ); if (!$occupied && $cursor->gt($minAdvance)) { $slots[] = $cursor->toIso8601String(); } $cursor->addMinutes($slotDuration); } return $slots; } } 

Як уникнути подвійного запису?

Зауважимо: коли два клієнти одночасно обирають один слот, потрібне блокування. Використовуємо песимістичне блокування (SELECT ... FOR UPDATE). Воно в 2 рази надійніше за оптимістичне у сценаріях з високою конкуренцією.

public function bookAppointment(BookingRequest $data): Appointment { return DB::transaction(function() use ($data) { $conflict = Appointment::where('staff_id', $data->staff_id) ->whereIn('status', ['pending', 'confirmed']) ->where('starts_at', '<', $data->ends_at) ->where('ends_at', '>', $data->starts_at) ->lockForUpdate() ->first(); if ($conflict) { throw new SlotUnavailableException('Слот уже занят'); } return Appointment::create([ 'staff_id' => $data->staff_id, 'service_id' => $data->service_id, 'client_id' => $data->client_id, 'starts_at' => $data->starts_at, 'ends_at' => $data->ends_at, 'status' => 'pending', ]); }); } 

Сповіщення та нагадування

Одразу після створення запису запускається ланцюжок сповіщень: клієнту приходить SMS, спеціалісту — email. За добу до запису cron-завдання надсилає нагадування клієнту. Така система гарантує зниження кількості пропущених записів на 40%. За потреби додаємо інтеграцію з Telegram або WhatsApp.

Вбудований віджет

Віджет для сторонніх сайтів реалізований як автономний JS-скрипт з Shadow DOM для ізоляції стилів.

<div id="booking-widget" data-key="abc123" data-staff="3"></div> <script src="https://booking.example.com/widget.js" async></script> 

Всередині монтується React-додаток, який спілкується з сервером API.

Процес роботи: від аналітики до деплою

  1. Аналітика — вивчаємо бізнес-процеси, збираємо вимоги до розкладу, буферів, сповіщень.
  2. Проєктування — проєктуємо модель даних, API, архітектуру віджета, схему інтеграції із зовнішніми календарями.
  3. Розробка — реалізуємо бекенд (Laravel, PostgreSQL), фронтенд (React) та віджет (Shadow DOM).
  4. Тестування — покриваємо unit-тестами (70%+), проводимо навантажувальне тестування (до 1000 паралельних записів).
  5. Деплой — розміщуємо на вашому хостингу, налаштовуємо CI/CD, моніторинг.

Терміни реалізації

Комплектація Термін
Базова (1 спеціаліст, 1 послуга) 1–1,5 тижня
Розширена (декілька спеціалістів, послуг, групові записи, віджет) 2,5–3 тижні
Повна (все вище + Google Calendar, оплата, кабінет клієнта) +1–2 тижні

Порівняння методів блокувань

Метод Надійність Продуктивність Складність реалізації
Песимістичне блокування Висока Середня Низька
Оптимістичне блокування Середня Висока Середня

Песимістичне блокування (SELECT FOR UPDATE) гарантує відсутність подвійного запису навіть при пікових навантаженнях. Оптимістичне блокування (version column) підходить для сценаріїв з низькою конкуренцією, але потребує обробки повторних спроб.

Типові помилки при проєктуванні

Часто клієнти просять додати поле «вільні слоти» в таблицю, але це веде до аномалій при паралельних транзакціях. Правильний підхід — обчислювати слоти на льоту через алгоритм. Інша поширена помилка — ігнорування часових поясів. Якщо спеціаліст працює в одному часовому поясі, а клієнт з іншого, слоти повинні відображатися в його локальному часі. Третя — відсутність буфера між записами. Без нього спеціаліст запізнюється на 10–15 хвилин, що накопичується протягом дня.

Що входить в роботу

  • Документація: ER-діаграма, опис API, інструкція з інтеграції віджета.
  • Доступи: репозиторій коду, тестовий стенд, панель адміністратора.
  • Навчання: 2-годинна сесія для вашого адміністратора.
  • Підтримка: 1 місяць гарантійного супроводу після запуску.

Гарантуємо відсутність подвійного запису та коректну роботу при високих навантаженнях. Оцініть ваш проєкт — зв'яжіться з нами для консультації. Замовте розрахунок — ми підберемо оптимальну комплектацію під ваш бюджет та терміни.