Три теннисные площадки в Москве, с крытыми и открытыми кортами, теперь принимают брони через сайт: сетка по часам, тренеры, абонементы и оплата онлайн. Создание сайта выполнил Евгений Росляков. Веб-разработкой он занимается с 2016 года, работает самозанятым, заключает договор, юрлицам выдаёт закрывающие документы. Он принимает заказы в Москве и удалённо.
До сайта корты бронировали через администратора. Люди звонили или писали, администратор смотрел в таблицу и вписывал фамилию. Пока таблицей пользовался один человек, всё держалось. Вечером желающих много, на телефоне и в мессенджере сидят двое, и появлялись двойные брони: два человека приезжали на один корт в один час.
Вторая беда — «резервы». Человеку обещали корт, пока он решает, платить ли. Он мог не ответить, а лучший час всё это время простаивал. Снять бронь без звонка администратор не решался.
Нужно было, чтобы человек сам видел свободные часы на любой площадке, выбирал, платил и получал подтверждение. Двойная бронь должна быть невозможна технически, а не маловероятна. Неоплаченное место должно освобождаться само.
И ещё администратору нужен рабочий инструмент вместо таблицы: день по кортам, переносы, отметки об абонементах.
Сайт собран на Django, это фреймворк на языке Python. В нём есть готовая админка, учётные записи и работа с базой, поэтому время ушло на правила бронирования, а не на каркас.
Данные лежат в PostgreSQL. Главная защита от двойной брони стоит на уровне базы: у таблицы бронирований есть ограничение EXCLUDE по диапазонам времени, для него включено расширение btree_gist. Если интервал для того же корта пересекается, база сама отказывает. Это срабатывает, даже когда два человека нажали «оплатить» в одну секунду и приложение не успело сравнить запросы. Проверки в коде остались, но служат подсказкой пользователю, а не последней линией обороны.
Сетка по часам кешируется в Redis, чтобы страница открывалась быстро. Там же ставятся короткие блокировки: пока человек на странице оплаты, выбранный час закрыт для остальных. Блокировка живёт ограниченное время и пропадает сама.
Отложенные задачи выполняет Celery. Оплата не прошла за 15 минут — бронь снимается, час возвращается в сетку. Перед игрой клиенту уходит напоминание. Работает лист ожидания: освободился корт, первому в очереди приходит сообщение, и у него есть время ответить. Ночью строятся отчёты для администратора: загрузка кортов по дням, продажи абонементов, отмены.
В админке администратор видит день по кортам, может поставить бронь вручную, перенести игру, продлить абонемент. Тренеры привязаны к своему расписанию, поэтому клиент выбирает корт и тренера в одном действии. Оплата идёт через платёжный сервис, работающий в России.
Вечерние часы больше не превращаются в спор «я был первым». Один корт у двух людей в одно время забронировать нельзя, и обеспечивает это база, а не внимательность администратора. Неоплаченные резервы исчезают сами, лучшие часы возвращаются в продажу, а отзванивать и напоминать не нужно.
Лист ожидания оказался полезнее, чем рассчитывали. Отменённый вечер уходит следующему желающему за несколько минут, а раньше корт чаще простаивал, потому что об отмене никто не узнавал вовремя.
Администраторы работают в одном окне. Таблицу с бронями отключили. Звонки остались, но стали короткими просьбами перенести игру. Ночные отчёты показывают, какие часы и площадки загружены слабее, и руководство уже подстраивает расписание и цены абонементов.
Что пока не так. Блокировка на время оплаты может на пару минут задержать корт, если человек передумал и просто закрыл вкладку. Это сознательный компромисс: лучше потерять пару минут, чем получить спорную бронь. Групповые занятия с отдельным составом участников сайт тоже пока не умеет.
Следующий шаг — турниры и клубные матчи.
Если вам нужна онлайн-запись на корты, залы или студии с оплатой, напишите мне на freelance.ru: сколько у вас площадок и как сейчас ведётся расписание. Обсудим, какие правила брони стоит закрепить в системе.