Главная → Боты → кейс «Бот приёма записей»
Telegram-бот приёма записей: как он устроен и чем проверяется
демонстрационная работа Бот сделал я, для портфолио и для себя. Реальных клиентов через него не водил — зато вся логика расписания покрыта самопроверкой, которую может запустить любой, у кого есть Python.
1. Для кого такая запись
Сценарий повторяется у любой точки, где визит идёт по времени: салон, барбер, косметолог, репетитор, стоматолог, автомасляный сервис. Сейчас это выглядит так: клиент пишет в мессенджер, владелец отвечает вручную, ведёт список в заметках и хотя бы раз в неделю сажает двух клиентов на одно время.
Бот закрывает три вещи: клиент сам выбирает услугу и видит только свободное время; два клиента не могут занять пересекающееся окно — это проверяется кодом, а не вниманием человека; владелец одной командой получает журнал записей в CSV, который открывается в Excel без настройки кодировки.
2. Схема хранения: одна база, две таблицы
SQLite — это файл на диске, сервер не нужен. Таблицы создаются при первом запуске:
CREATE TABLE IF NOT EXISTS services(
id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, price INTEGER DEFAULT 0);
CREATE TABLE IF NOT EXISTS bookings(
id INTEGER PRIMARY KEY AUTOINCREMENT,
chat_id INTEGER NOT NULL,
service_id INTEGER NOT NULL REFERENCES services(id),
starts TEXT NOT NULL, -- ISO datetime
minutes INTEGER NOT NULL,
status TEXT NOT NULL DEFAULT 'active', -- active | canceled | done
reminded INTEGER NOT NULL DEFAULT 0,
created TEXT NOT NULL);
CREATE INDEX IF NOT EXISTS ix_book_starts ON bookings(starts, status);
Три решения здесь важные. row_factory = sqlite3.Row — строки доступны по именам колонок, и код
не сползает в row[3]. Отдельная колонка minutes, а не жёстко «час», — услуга на 90 минут
ложится в ту же схему. Флаг reminded — чтобы напоминание отправилось ровно один раз и не зависело от
того, перезапускался процесс или нет.
3. Расписание считается на лету
В базе хранятся только занятые начала. Сетка дня строится каждый раз заново, поэтому «перенесите рабочий день на 9 утра и уберите обед» — это правка двух констант, а не переписывание таблиц.
WORK_START, WORK_END, SLOT_MINUTES = 10, 19, 60
def free_slots(con, day_iso):
taken = busy(con, day_iso)
return ["%02d:00" % h for h in range(WORK_START, WORK_END)
if "%sT%02d:00:00" % (day_iso, h) not in taken]
4. Главная функция: проверка пересечения интервалов
Здесь вся суть задачи, и именно за это платят деньги. Наивная реализация («занят ли слот 14:00?») ломается на услуге в полтора часа: клиент записался в 13:00 на 90 минут, а система считает 14:00 свободным.
def book(con, chat_id, service_id, when_iso, minutes=SLOT_MINUTES):
start = dt.datetime.fromisoformat(when_iso)
end = start + dt.timedelta(minutes=minutes)
for r in con.execute("SELECT starts, minutes FROM bookings WHERE status='active' AND starts LIKE ?",
(start.date().isoformat() + "%",)):
s = dt.datetime.fromisoformat(r["starts"])
e = s + dt.timedelta(minutes=r["minutes"])
if start < e and s < end: # классическая проверка пересечения
return None # слот занят — честный отказ
# ...INSERT и возврат id записи
Условие start < e and s < end даёт три разных ответа: 14:00 поверх 13:30–14:30 — отказ;
15:00 сразу после 14:00–15:00 — запись проходит, потому что стык-в-стык пересечением не считается;
13:00 перед 14:00 — тоже проходит. Если написать <=, второй случай сломается: клиенты не смогут
записываться плотным потоком, и владелец потеряет один-два визита в день. Один символ — реальные деньги, поэтому
он покрыт проверкой №6 из самопроверки.
5. Отмена, которая не даёт отменить чужую запись
UPDATE bookings SET status='canceled' WHERE id=? AND chat_id=? AND status='active'
Номер записи можно угадать, а вот отменить чужой визит нельзя: условие по chat_id — это guard.
Запись не удаляется, а переходит в статус canceled: история сохраняется, освободившееся время
снова попадает в свободные слоты.
6. Напоминания ровно один раз
Отдельный режим --remind выбирает активные записи на ближайшие N часов, где reminded=0,
отправляет сообщение и ставит флаг. Он запускается по расписанию (раз в час), а не крутится внутри основного цикла:
упавший процесс опроса не должен тянуть за собой напоминания, и никто не получает спам повтором.
7. Тринадцать самопроверок одной командой
$ python bot_booking.py --check OK: логика бота проверена — 13 проверок (слоты, пересечения, отмена, напоминания, CSV)
Что именно проверяется: база поднимается и услуги засеиваются; сетка дня содержит 9 слотов; первая запись
создаётся; занятый слот исчезает из свободных; пересекающееся время возвращает None; стык-в-стык
разрешается; отмена работает; повторная отмена возвращает False; после отмены слот вернулся;
чужую запись отменить нельзя; напоминание срабатывает один раз; CSV выгружается непустым и с кириллицей;
/start отвечает текстом, а не падает.
Тесты идут на отдельной временной базе и боевую не трогают. Это тот раздел, который стоит показывать заказчику скриншотом: «вот работает, и вот чем проверяется» вместо обещаний «должно работать».
8. Установка: что нужно, чтобы он ожил
- Написать в Telegram
@BotFather, команда/newbot, имя и username (заканчивается наbot) — токен придёт сообщением. - Положить токен в переменную окружения
BOT_TOKENи запуститьpython bot_booking.py. - Поставить автозапуск: на Windows — задача планировщика, на Linux — systemd-юнит с
Restart=always. В коде токен не хранится и в скриншотах не светится. - Отдельной задачей раз в час запускать
--remind.
Никакого публичного адреса и веб-сервера не нужно: бот сам ходит за обновлениями (long-polling), поэтому работает даже за домашним NAT.
9. Цены и что в них входит
| Вариант | Цена | Что получает заказчик |
|---|---|---|
| Шаблон архивом | 990 ₽ | Исходник, инструкция по установке, самопроверка. Настройка на себя — ваша. |
| С установкой | 2 900 ₽ | То же плюс я ставлю бота на ваш компьютер или сервер, регистрирую токен, проверяю на ваших услугах. |
| Под ключ | от 6 000 ₽ | Ваши услуги и цены, ваше расписание и выходные, ваши тексты напоминаний, журнал в нужном формате, 7 дней правок. |
| Доработки | по задаче | Оплата в боте, буфер между визитами, второй админ-аккаунт, веб-интерфейс на тех же таблицах. |
Честно об ограничениях: один процесс — одна точка; на сеть салонов нужна колонка на локацию и другой порядок развёртывания. Оплата в базовой версии не принимается — это отдельный этап с кассой или платёжным сервисом, и стоит он другого чека.
Хотите такого бота под свою точку?
Напишите список услуг, часы работы и длину визита. Сначала покажу бота в деле на демо-базе, дальше решаем. Созвонов не веду — всё в переписке.
Работа написана и проверена 30.10.2026, команда --check проходит на Python без
установки сторонних пакетов. Демо-записей реальных клиентов в комплекте нет, и я не выдаю тестовые слоты за
клиентские.