Как разработать надёжную онлайн-запись: модель, API, виджет

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

Разработка и обслуживание любых видов сайтов:

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

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

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

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

Часто задаваемые вопросы

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1414
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1285
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    982
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1241
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    982
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Разработка веб-сайта для компании ФИКСПЕР
    994

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

Представьте: стоматология с пятью врачами и тремя кабинетами. Клиенты звонят, администратор сверяется с бумажным журналом — и всё равно возникают двойные записи. Или фитнес-клуб: 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 месяц гарантийного сопровождения после запуска.

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