Слияние ветки dev: актуальный код бота с inline-клавиатурами и PostgreSQL

This commit is contained in:
DevProject Agent
2026-06-14 19:07:41 +04:00
25 changed files with 573 additions and 91 deletions
Vendored
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+223 -72
View File
@@ -1,11 +1,21 @@
import re
from aiogram import Router, F
from aiogram.types import Message
from aiogram.fsm.context import FSMContext
from aiogram.filters import StateFilter
from aiogram.types import Message, CallbackQuery
from app.keyboards.services import get_services_keyboard
from app.keyboards.masters import get_masters_keyboard
from app.states.booking import BookingState
from app.services.booking_service import create_booking
from app.services.booking_service import (
create_booking,
update_booking,
get_busy_times
)
from app.keyboards.menu import menu, cancel_menu
from app.keyboards.calendar import get_dates_keyboard
from app.keyboards.time_slots import get_time_keyboard
from app.keyboards.confirm_booking import get_confirm_booking_keyboard
router = Router()
@@ -28,95 +38,236 @@ async def start_booking(message: Message, state: FSMContext):
await state.set_state(BookingState.service)
await message.answer(
"Выберите услугу:\n"
"1. Массаж\n"
"2. SPA\n"
"3. Косметология",
reply_markup=cancel_menu
"Выберите услугу:",
reply_markup=get_services_keyboard()
)
# выбор услуги
@router.message(StateFilter(BookingState.service))
async def choose_service(message: Message, state: FSMContext):
@router.callback_query(
StateFilter(BookingState.service),
F.data.startswith("service_")
)
async def choose_service(
callback: CallbackQuery,
state: FSMContext
):
services = {
"service_massage": "Массаж",
"service_spa": "SPA",
"service_cosmetology": "Косметология"
}
forbidden = [
"📅 Записаться",
"👤 Профиль",
"📖 Мои записи",
"О компании",
"📞 Контакты"
]
service_name = services[callback.data]
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)"
await state.update_data(
service=service_name
)
await state.set_state(
BookingState.master
)
await callback.message.edit_text(
"👤 Выберите специалиста:",
reply_markup=get_masters_keyboard()
)
await callback.answer()
#выбор мастера
@router.callback_query(
StateFilter(BookingState.master),
F.data.startswith("master_")
)
async def choose_master(
callback: CallbackQuery,
state: FSMContext
):
masters = {
"master_anna": "Анна",
"master_maria": "Мария",
"master_alex": "Алексей"
}
master_name = masters[callback.data]
await state.update_data(
master=master_name
)
await state.set_state(
BookingState.date
)
await callback.message.edit_text(
"📅 Выберите дату:",
reply_markup=get_dates_keyboard()
)
await callback.answer()
# выбор даты
@router.message(StateFilter(BookingState.date))
async def choose_date(message: Message, state: FSMContext):
@router.callback_query(
StateFilter(BookingState.date),
F.data.startswith("date_")
)
async def choose_date(
callback: CallbackQuery,
state: FSMContext
):
selected_date = callback.data.replace(
"date_",
""
)
forbidden = [
"📅 Записаться",
"👤 Профиль",
"📖 Мои записи",
"О компании",
"📞 Контакты"
]
if message.text in forbidden:
await message.answer(
"⚠️ Сначала завершите запись или напишите 'отмена'"
)
return
await state.update_data(date=message.text)
await state.update_data(
date=selected_date
)
data = await state.get_data()
await state.set_state(BookingState.confirm)
await message.answer(
f"Услуга: {data['service']}\n"
f"Дата: {data['date']}\n\n"
f"Напишите 'да' для подтверждения"
busy_times = get_busy_times(
master=data["master"],
date=selected_date
)
await state.set_state(
BookingState.time
)
await callback.message.edit_text(
"🕒 Выберите время:",
reply_markup=get_time_keyboard(
busy_times
)
)
await callback.answer()
@router.callback_query(
StateFilter(BookingState.time),
F.data.startswith("time_")
)
async def choose_time(
callback: CallbackQuery,
state: FSMContext
):
selected_time = callback.data.replace(
"time_",
""
)
await state.update_data(
time=selected_time
)
data = await state.get_data()
await state.set_state(
BookingState.confirm
)
await callback.message.edit_text(
f"📋 Проверьте запись:\n\n"
f"💆 Услуга: {data['service']}\n"
f"📅 Дата: {data['date']}\n"
f"👤 Мастер:{data['master']}\n"
f"🕒 Время: {selected_time}",
reply_markup=get_confirm_booking_keyboard()
)
await callback.answer()
# подтверждение
@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
)
@router.callback_query(
StateFilter(BookingState.confirm),
F.data == "confirm_booking"
)
async def confirm_booking_callback(
callback: CallbackQuery,
state: FSMContext
):
data = await state.get_data()
create_booking(
user_id=message.from_user.id,
service=data["service"],
date=data["date"]
editing_booking_id = data.get(
"editing_booking_id"
)
if editing_booking_id:
update_booking(
booking_id=editing_booking_id,
service=data["service"],
master=data["master"],
date=data["date"],
time=data["time"]
)
result_text = "✅ Запись успешно обновлена"
else:
create_booking(
user_id=callback.from_user.id,
service=data["service"],
master=data["master"],
date=data["date"],
time=data["time"]
)
result_text = "✅ Запись успешно создана"
await state.clear()
await message.answer(
"✅ Вы успешно записаны!",
reply_markup=menu
)
await callback.message.edit_text(
result_text
)
await callback.answer()
@router.callback_query(
StateFilter(BookingState.confirm),
F.data == "cancel_booking"
)
async def cancel_booking_callback(
callback: CallbackQuery,
state: FSMContext
):
await state.clear()
await callback.message.edit_text(
"❌ Запись отменена"
)
await callback.answer()
@router.callback_query(
StateFilter(BookingState.confirm),
F.data == "edit_booking"
)
async def edit_booking_callback(
callback: CallbackQuery,
state: FSMContext
):
old_data = await state.get_data()
await state.clear()
await state.update_data(
service=old_data.get("service")
)
await state.set_state(
BookingState.service
)
from app.keyboards.services import get_services_keyboard
await callback.message.edit_text(
"Выберите услугу:",
reply_markup=get_services_keyboard()
)
+87 -16
View File
@@ -1,28 +1,99 @@
from aiogram import Router, F
from aiogram.types import Message
from aiogram.types import (
Message,
CallbackQuery
)
from aiogram.fsm.context import FSMContext
from app.db.memory import bookings
from app.states.booking import BookingState
from app.keyboards.services import get_services_keyboard
from app.services.booking_service import (
get_user_bookings,
cancel_booking,
)
from app.keyboards.my_booking import (
get_booking_actions
)
router = Router()
@router.message(F.text == "📖 Мои записи")
async def my_bookings(message: Message, state: FSMContext):
await state.clear()
user_id = message.from_user.id
@router.message(
F.text == "📖 Мои записи"
)
async def my_bookings(
message: Message
):
bookings = get_user_bookings(
message.from_user.id
)
user_bookings = [
b for b in bookings if b["user_id"] == user_id
]
if not user_bookings:
await message.answer("У вас нет записей")
if not bookings:
await message.answer(
"У вас нет активных записей"
)
return
text = "📖 Ваши записи:\n\n"
for booking in bookings:
await message.answer(
f"💆 {booking['service']}\n"
f"👤 {booking['master']}\n"
f"📅 {booking['date']}\n"
f"🕒 {booking['time']}",
reply_markup=get_booking_actions(
booking["id"]
)
)
for b in user_bookings:
text += f"💆 {b['service']} — 📅 {b['date']}\n"
await message.answer(text)
@router.callback_query(
F.data.startswith(
"cancel_booking_"
)
)
async def cancel_booking_handler(
callback: CallbackQuery
):
booking_id = int(
callback.data.split("_")[-1]
)
cancel_booking(
booking_id
)
await callback.message.edit_text(
"❌ Запись отменена"
)
await callback.answer()
@router.callback_query(
F.data.startswith("edit_booking_")
)
async def edit_booking_handler(
callback: CallbackQuery,
state: FSMContext
):
booking_id = int(
callback.data.split("_")[-1]
)
await state.update_data(
editing_booking_id=booking_id
)
await state.set_state(
BookingState.service
)
await callback.message.edit_text(
"✏️ Выберите новую услугу:",
reply_markup=get_services_keyboard()
)
await callback.answer()
Binary file not shown.
+24
View File
@@ -0,0 +1,24 @@
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from datetime import datetime, timedelta
def get_dates_keyboard():
buttons = []
today = datetime.now()
for i in range(7):
day = today + timedelta(days=i)
buttons.append(
[
InlineKeyboardButton(
text=day.strftime("%d.%m"),
callback_data=f"date_{day.strftime('%d.%m')}"
)
]
)
return InlineKeyboardMarkup(
inline_keyboard=buttons
)
+28
View File
@@ -0,0 +1,28 @@
from aiogram.types import InlineKeyboardMarkup
from aiogram.types import InlineKeyboardButton
def get_confirm_booking_keyboard():
return InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="✅ Подтвердить",
callback_data="confirm_booking"
)
],
[
InlineKeyboardButton(
text="✏️ Изменить",
callback_data="edit_booking"
)
],
[
InlineKeyboardButton(
text="❌ Отменить",
callback_data="cancel_booking"
)
]
]
)
+28
View File
@@ -0,0 +1,28 @@
from aiogram.types import InlineKeyboardMarkup
from aiogram.types import InlineKeyboardButton
def get_masters_keyboard():
return InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="👩 Анна",
callback_data="master_anna"
)
],
[
InlineKeyboardButton(
text="👩 Мария",
callback_data="master_maria"
)
],
[
InlineKeyboardButton(
text="👨 Алексей",
callback_data="master_alex"
)
]
]
)
+25
View File
@@ -0,0 +1,25 @@
from aiogram.types import (
InlineKeyboardMarkup,
InlineKeyboardButton
)
def get_booking_actions(
booking_id: int
):
return InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="✏️ Изменить",
callback_data=f"edit_booking_{booking_id}"
)
],
[
InlineKeyboardButton(
text="❌ Отменить",
callback_data=f"cancel_booking_{booking_id}"
)
]
]
)
+28
View File
@@ -0,0 +1,28 @@
from aiogram.types import InlineKeyboardMarkup
from aiogram.types import InlineKeyboardButton
def get_services_keyboard():
return InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="💆 Массаж",
callback_data="service_massage"
)
],
[
InlineKeyboardButton(
text="🌿 SPA",
callback_data="service_spa"
)
],
[
InlineKeyboardButton(
text="✨ Косметология",
callback_data="service_cosmetology"
)
]
]
)
+41
View File
@@ -0,0 +1,41 @@
from aiogram.types import InlineKeyboardMarkup
from aiogram.types import InlineKeyboardButton
def get_time_keyboard(
busy_times=None
):
if busy_times is None:
busy_times = []
times = [
"10:00",
"11:00",
"12:00",
"13:00",
"14:00",
"15:00",
"16:00",
"17:00"
]
buttons = []
for time in times:
if time in busy_times:
continue
buttons.append(
[
InlineKeyboardButton(
text=time,
callback_data=f"time_{time}"
)
]
)
return InlineKeyboardMarkup(
inline_keyboard=buttons
)
+5 -1
View File
@@ -10,7 +10,11 @@ from app.handlers.profile import router as profile_router
from app.handlers.mybookings import router as mybooking_router
from app.handlers.about import router as about_router
from app.handlers.contacts import router as contacts_router
from app.services.booking_service import (
create_booking,
update_booking,
get_busy_times
)
async def main():
bot = Bot(token=BOT_TOKEN)
+82 -2
View File
@@ -1,12 +1,92 @@
from app.db.memory import bookings
def create_booking(user_id: int, service: str, date: str):
def create_booking(
user_id: int,
service: str,
date: str,
master: str,
time: str
):
booking = {
"id": len(bookings) + 1,
"user_id": user_id,
"service": service,
"date": date,
"master": master,
"time": time,
"status": "active"
}
bookings.append(booking)
return booking
return booking
def get_busy_times(
master: str,
date: str
):
busy_times = []
for booking in bookings:
if (
booking["master"] == master
and booking["date"] == date
and booking["status"] == "active"
):
busy_times.append(
booking["time"]
)
return busy_times
def get_user_bookings(
user_id: int
):
result = []
for booking in bookings:
if (
booking["user_id"] == user_id
and booking["status"] == "active"
):
result.append(booking)
return result
def cancel_booking(
booking_id: int
):
for booking in bookings:
if booking["id"] == booking_id:
booking["status"] = "cancelled"
return True
return False
def update_booking(
booking_id: int,
service: str,
master: str,
date: str,
time: str
):
for booking in bookings:
if booking["id"] == booking_id:
booking["service"] = service
booking["master"] = master
booking["date"] = date
booking["time"] = time
return booking
return None
Binary file not shown.
+2
View File
@@ -2,5 +2,7 @@ from aiogram.fsm.state import StatesGroup, State
class BookingState(StatesGroup):
service = State()
master = State()
date = State()
time = State()
confirm = State()