Слияние ветки 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.
+219 -68
View File
@@ -1,11 +1,21 @@
import re
from aiogram import Router, F from aiogram import Router, F
from aiogram.types import Message
from aiogram.fsm.context import FSMContext from aiogram.fsm.context import FSMContext
from aiogram.filters import StateFilter 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.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.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() router = Router()
@@ -28,95 +38,236 @@ async def start_booking(message: Message, state: FSMContext):
await state.set_state(BookingState.service) await state.set_state(BookingState.service)
await message.answer( await message.answer(
"Выберите услугу:\n" "Выберите услугу:",
"1. Массаж\n" reply_markup=get_services_keyboard()
"2. SPA\n"
"3. Косметология",
reply_markup=cancel_menu
) )
# выбор услуги @router.callback_query(
@router.message(StateFilter(BookingState.service)) StateFilter(BookingState.service),
async def choose_service(message: Message, state: FSMContext): 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 state.update_data(
await message.answer( service=service_name
"⚠️ Сначала завершите запись или напишите 'отмена'"
) )
return
await state.update_data(service=message.text) await state.set_state(
BookingState.master
await state.set_state(BookingState.date)
await message.answer(
"Введите дату (например 20.06)"
) )
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)) @router.callback_query(
async def choose_date(message: Message, state: FSMContext): StateFilter(BookingState.date),
F.data.startswith("date_")
forbidden = [ )
"📅 Записаться", async def choose_date(
"👤 Профиль", callback: CallbackQuery,
"📖 Мои записи", state: FSMContext
"О компании", ):
"📞 Контакты" selected_date = callback.data.replace(
] "date_",
""
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() data = await state.get_data()
await state.set_state(BookingState.confirm) busy_times = get_busy_times(
master=data["master"],
await message.answer( date=selected_date
f"Услуга: {data['service']}\n"
f"Дата: {data['date']}\n\n"
f"Напишите 'да' для подтверждения"
) )
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)) @router.callback_query(
async def confirm_booking(message: Message, state: FSMContext): StateFilter(BookingState.confirm),
F.data == "confirm_booking"
if message.text.lower() != "да": )
await state.clear() async def confirm_booking_callback(
callback: CallbackQuery,
await message.answer( state: FSMContext
"❌ Запись отменена", ):
reply_markup=menu
)
data = await state.get_data() data = await state.get_data()
create_booking( editing_booking_id = data.get(
user_id=message.from_user.id, "editing_booking_id"
service=data["service"],
date=data["date"]
) )
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 state.clear()
await message.answer( await callback.message.edit_text(
"✅ Вы успешно записаны!", result_text
reply_markup=menu )
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 import Router, F
from aiogram.types import Message from aiogram.types import (
Message,
CallbackQuery
)
from aiogram.fsm.context import FSMContext 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 = 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 = [ if not bookings:
b for b in bookings if b["user_id"] == user_id await message.answer(
] "У вас нет активных записей"
)
if not user_bookings:
await message.answer("У вас нет записей")
return 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.mybookings import router as mybooking_router
from app.handlers.about import router as about_router from app.handlers.about import router as about_router
from app.handlers.contacts import router as contacts_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(): async def main():
bot = Bot(token=BOT_TOKEN) bot = Bot(token=BOT_TOKEN)
+81 -1
View File
@@ -1,12 +1,92 @@
from app.db.memory import bookings 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 = { booking = {
"id": len(bookings) + 1,
"user_id": user_id, "user_id": user_id,
"service": service, "service": service,
"date": date, "date": date,
"master": master,
"time": time,
"status": "active" "status": "active"
} }
bookings.append(booking) 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): class BookingState(StatesGroup):
service = State() service = State()
master = State()
date = State() date = State()
time = State()
confirm = State() confirm = State()