feat: реализован MVP Telegram-бота для записи

* настроена структура проекта
* подключены handlers и routers
* реализовано главное меню
* добавлены разделы: профиль, контакты, о компании, мои записи
* реализована FSM-машина состояний для записи
* добавлен сервисный слой booking_service
* реализовано сохранение записей в памяти
* добавлено подтверждение записи
* реализована отмена записи
* исправлены конфликты FSM и кнопок меню
* добавлена отдельная клавиатура во время записи
* выполнен рефакторинг импортов и структуры проекта
* улучшен пользовательский сценарий записи
This commit is contained in:
grigorij
2026-06-11 13:50:24 +04:00
parent 2c6f82bfb5
commit 61d8eb48a9
13 changed files with 285 additions and 27 deletions
+11
View File
@@ -0,0 +1,11 @@
from aiogram import Router, F
from aiogram.types import Message
from aiogram.fsm.context import FSMContext
router = Router()
@router.message(F.text == "О компании")
async def about(message: Message, state: FSMContext):
await state.clear() # 🔥 СБРОС FSM
await message.answer("Мы demo SPA бот 💆‍♂️")
+122
View File
@@ -0,0 +1,122 @@
from aiogram import Router, F
from aiogram.types import Message
from aiogram.fsm.context import FSMContext
from aiogram.filters import StateFilter
from app.states.booking import BookingState
from app.services.booking_service import create_booking
from app.keyboards.menu import menu, cancel_menu
router = Router()
# отмена записи на любом шаге
@router.message(F.text.lower() == "отмена")
async def cancel(message: Message, state: FSMContext):
await state.clear()
await message.answer(
"❌ Запись отменена",
reply_markup=menu
)
# старт записи
@router.message(F.text == "📅 Записаться")
async def start_booking(message: Message, state: FSMContext):
await state.clear()
await state.set_state(BookingState.service)
await message.answer(
"Выберите услугу:\n"
"1. Массаж\n"
"2. SPA\n"
"3. Косметология",
reply_markup=cancel_menu
)
# выбор услуги
@router.message(StateFilter(BookingState.service))
async def choose_service(message: Message, state: FSMContext):
forbidden = [
"📅 Записаться",
"👤 Профиль",
"📖 Мои записи",
"О компании",
"📞 Контакты"
]
if message.text in forbidden:
await message.answer(
"⚠️ Сначала завершите запись или напишите 'отмена'"
)
return
await state.update_data(service=message.text)
await state.set_state(BookingState.date)
await message.answer(
"Введите дату (например 20.06)"
)
# выбор даты
@router.message(StateFilter(BookingState.date))
async def choose_date(message: Message, state: FSMContext):
forbidden = [
"📅 Записаться",
"👤 Профиль",
"📖 Мои записи",
"О компании",
"📞 Контакты"
]
if message.text in forbidden:
await message.answer(
"⚠️ Сначала завершите запись или напишите 'отмена'"
)
return
await state.update_data(date=message.text)
data = await state.get_data()
await state.set_state(BookingState.confirm)
await message.answer(
f"Услуга: {data['service']}\n"
f"Дата: {data['date']}\n\n"
f"Напишите 'да' для подтверждения"
)
# подтверждение
@router.message(StateFilter(BookingState.confirm))
async def confirm_booking(message: Message, state: FSMContext):
if message.text.lower() != "да":
await state.clear()
await message.answer(
"❌ Запись отменена",
reply_markup=menu
)
data = await state.get_data()
create_booking(
user_id=message.from_user.id,
service=data["service"],
date=data["date"]
)
await state.clear()
await message.answer(
"✅ Вы успешно записаны!",
reply_markup=menu
)
+11
View File
@@ -0,0 +1,11 @@
from aiogram import Router, F
from aiogram.types import Message
from aiogram.fsm.context import FSMContext
router = Router()
@router.message(F.text == "📞 Контакты")
async def contacts(message: Message, state: FSMContext):
await state.clear()
await message.answer("📍 Москва\n☎ +7 999 999 99 99")
+28
View File
@@ -0,0 +1,28 @@
from aiogram import Router, F
from aiogram.types import Message
from aiogram.fsm.context import FSMContext
from app.db.memory import bookings
router = Router()
@router.message(F.text == "📖 Мои записи")
async def my_bookings(message: Message, state: FSMContext):
await state.clear()
user_id = message.from_user.id
user_bookings = [
b for b in bookings if b["user_id"] == user_id
]
if not user_bookings:
await message.answer("У вас нет записей")
return
text = "📖 Ваши записи:\n\n"
for b in user_bookings:
text += f"💆 {b['service']} — 📅 {b['date']}\n"
await message.answer(text)
+19
View File
@@ -0,0 +1,19 @@
from aiogram import Router, F
from aiogram.types import Message
from aiogram.fsm.context import FSMContext
from app.db.memory import users
router = Router()
@router.message(F.text == "👤 Профиль")
async def profile(message: Message, state: FSMContext):
await state.clear()
user = users.get(message.from_user.id)
if not user:
await message.answer("Профиль не найден")
return
await message.answer(f"👤 Имя: {user['name']}")
+2
View File
@@ -0,0 +1,2 @@
from aiogram import Router
router = Router()
+14 -3
View File
@@ -1,8 +1,19 @@
from aiogram import Router, F
from aiogram import Router
from aiogram.filters import CommandStart
from aiogram.types import Message
from app.keyboards.menu import menu
from app.db.memory import users
router = Router()
@router.message(F.text == "/start")
@router.message(CommandStart())
async def start(message: Message):
await message.answer("👋 Бот запущен (чистый старт)")
users[message.from_user.id] = {
"name": message.from_user.full_name
}
await message.answer(
"👋 Добро пожаловать в Demo Bot",
reply_markup=menu
)