Добавлена ветка cosmetics
This commit is contained in:
+18
-112
@@ -1,14 +1,12 @@
|
||||
"""Booking handler - full FSM flow for service reservation."""
|
||||
import re
|
||||
|
||||
from aiogram import Router, F
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.filters import StateFilter
|
||||
from aiogram.types import Message, CallbackQuery, FSInputFile, InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from aiogram.types import Message, CallbackQuery
|
||||
from app.keyboards.services import get_services_keyboard, get_extra_services_keyboard
|
||||
from app.keyboards.masters import get_masters_keyboard
|
||||
from app.states.booking import BookingState
|
||||
from app.config import SERVICES as ALL_SERVICES, ADMIN_CHAT_ID
|
||||
from app.services.booking_service import (
|
||||
create_booking,
|
||||
update_booking,
|
||||
@@ -34,17 +32,6 @@ def _services_to_str(data):
|
||||
return main
|
||||
|
||||
|
||||
def _make_service_card(key):
|
||||
"""Recreate the service card text for 'back' navigation from a photo card."""
|
||||
svc = ALL_SERVICES[key]
|
||||
return (
|
||||
svc["emoji"] + " " + svc["name"] + "\n\n"
|
||||
+ svc["desc"] + "\n\n"
|
||||
+ "\u23f1 Продолжительность: " + str(svc["duration"]) + " мин\n"
|
||||
+ "\U0001f4b0 Цена: " + str(svc["price"]) + "\u20bd"
|
||||
)
|
||||
|
||||
|
||||
# отмена записи на любом шаге
|
||||
@router.message(F.text.lower() == "отмена")
|
||||
async def cancel(message: Message, state: FSMContext):
|
||||
@@ -66,14 +53,15 @@ async def start_booking(message: Message, state: FSMContext):
|
||||
)
|
||||
|
||||
|
||||
# выбор первой услуги (через кнопку "📅 Записаться")
|
||||
# выбор первой услуги
|
||||
@router.callback_query(
|
||||
StateFilter(BookingState.service),
|
||||
F.data.startswith("service_")
|
||||
)
|
||||
async def choose_service(callback: CallbackQuery, state: FSMContext):
|
||||
svc_data = ALL_SERVICES[callback.data]
|
||||
service_name = svc_data["name"] if isinstance(svc_data, dict) else svc_data
|
||||
from app.config import SERVICES
|
||||
|
||||
service_name = SERVICES[callback.data]
|
||||
logger.debug("User {uid} selected first service: {srv}", uid=callback.from_user.id, srv=service_name)
|
||||
|
||||
await state.update_data(service=service_name, extra_services=[])
|
||||
@@ -94,7 +82,10 @@ async def choose_service(callback: CallbackQuery, state: FSMContext):
|
||||
F.data.startswith("extra_")
|
||||
)
|
||||
async def choose_extra_service(callback: CallbackQuery, state: FSMContext):
|
||||
from app.config import SERVICES
|
||||
|
||||
if callback.data == "extra_done":
|
||||
# Go to master selection
|
||||
await state.set_state(BookingState.master)
|
||||
await callback.message.edit_text(
|
||||
"👤 Выберите специалиста:",
|
||||
@@ -103,12 +94,12 @@ async def choose_extra_service(callback: CallbackQuery, state: FSMContext):
|
||||
await callback.answer()
|
||||
return
|
||||
|
||||
# Toggle extra service
|
||||
actual_key = callback.data.replace("extra_", "", 1)
|
||||
svc_data = ALL_SERVICES.get(actual_key)
|
||||
if not svc_data:
|
||||
service_name = SERVICES.get(actual_key)
|
||||
if not service_name:
|
||||
await callback.answer("Услуга не найдена")
|
||||
return
|
||||
service_name = svc_data["name"] if isinstance(svc_data, dict) else svc_data
|
||||
|
||||
data = await state.get_data()
|
||||
extra = data.get("extra_services", [])
|
||||
@@ -142,8 +133,7 @@ async def choose_extra_service(callback: CallbackQuery, state: FSMContext):
|
||||
async def choose_master(callback: CallbackQuery, state: FSMContext):
|
||||
from app.config import MASTERS
|
||||
|
||||
master_data = MASTERS[callback.data]
|
||||
master_name = master_data["name"] if isinstance(master_data, dict) else master_data
|
||||
master_name = MASTERS[callback.data]
|
||||
logger.debug("User {uid} selected master: {m}", uid=callback.from_user.id, m=master_name)
|
||||
|
||||
await state.update_data(master=master_name)
|
||||
@@ -232,14 +222,6 @@ async def confirm_booking_callback(callback: CallbackQuery, state: FSMContext):
|
||||
date = data.get("date", "?")
|
||||
time = data.get("time", "?")
|
||||
|
||||
from app.db.postgres import PostgresDB as _PG
|
||||
from app.config import USE_POSTGRES as _UPG
|
||||
if _UPG:
|
||||
_u = await _PG.get_user(callback.from_user.id)
|
||||
if not _u:
|
||||
_n = callback.from_user.full_name or callback.from_user.username or str(callback.from_user.id)
|
||||
await _PG.set_user(callback.from_user.id, _n)
|
||||
|
||||
if await is_slot_busy(master, date, time):
|
||||
logger.warning("Slot already busy: {mst} {dt} {tm}", mst=master, dt=date, tm=time)
|
||||
await callback.message.edit_text("❌ Это время уже занято. Пожалуйста, выберите другое время.")
|
||||
@@ -258,26 +240,6 @@ async def confirm_booking_callback(callback: CallbackQuery, state: FSMContext):
|
||||
cleanup_cancelled()
|
||||
result_text = "✅ Запись успешно создана"
|
||||
|
||||
# Notify admin about new booking
|
||||
try:
|
||||
if ADMIN_CHAT_ID:
|
||||
from aiogram import Bot
|
||||
from app.config import BOT_TOKEN
|
||||
admin_bot = Bot(token=BOT_TOKEN)
|
||||
user_str = callback.from_user.full_name or callback.from_user.username or str(callback.from_user.id)
|
||||
await admin_bot.send_message(
|
||||
ADMIN_CHAT_ID,
|
||||
"🆕 Новая запись!\n\n"
|
||||
f"👤 Клиент: {user_str} (id={callback.from_user.id})\n"
|
||||
f"💆 Услуги: {services_str}\n"
|
||||
f"👤 Мастер: {master}\n"
|
||||
f"📅 Дата: {date}\n"
|
||||
f"🕒 Время: {time}"
|
||||
)
|
||||
await admin_bot.session.close()
|
||||
except Exception as e:
|
||||
logger.warning("Failed to notify admin: {error}", error=e)
|
||||
|
||||
await state.clear()
|
||||
await callback.message.edit_text(result_text)
|
||||
await callback.answer()
|
||||
@@ -309,80 +271,24 @@ async def edit_booking_callback(callback: CallbackQuery, state: FSMContext):
|
||||
)
|
||||
|
||||
|
||||
# прямая запись с карточки услуги (без предварительного выбора)
|
||||
@router.callback_query(F.data.startswith("service_"))
|
||||
async def quick_book_service(callback: CallbackQuery, state: FSMContext):
|
||||
svc_data = ALL_SERVICES[callback.data]
|
||||
service_name = svc_data["name"] if isinstance(svc_data, dict) else svc_data
|
||||
svc_key = callback.data
|
||||
logger.info("User {uid} quick book from card: {srv}", uid=callback.from_user.id, srv=service_name)
|
||||
|
||||
await state.clear()
|
||||
await state.update_data(
|
||||
service=service_name,
|
||||
extra_services=[],
|
||||
_from_card=True,
|
||||
_card_key=svc_key
|
||||
)
|
||||
await state.set_state(BookingState.extra_services)
|
||||
|
||||
text = (
|
||||
"✅ Выбрано: " + service_name + "\n\n"
|
||||
"Хотите добавить ещё услугу?\n"
|
||||
"Нажмите нужную или «Готово»:"
|
||||
)
|
||||
|
||||
# Карточка с фото — удаляем и отправляем новое сообщение
|
||||
try:
|
||||
await callback.message.delete()
|
||||
await callback.message.answer(text, reply_markup=get_extra_services_keyboard([]))
|
||||
except Exception:
|
||||
await callback.message.edit_text(text, reply_markup=get_extra_services_keyboard([]))
|
||||
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "go_back")
|
||||
async def go_back_handler(callback: CallbackQuery, state: FSMContext):
|
||||
current_state = await state.get_state()
|
||||
|
||||
if current_state == BookingState.extra_services:
|
||||
# Назад к списку услуг — проверяем, с карточки пришёл или из меню
|
||||
# Back to first service
|
||||
await state.set_state(BookingState.service)
|
||||
data = await state.get_data()
|
||||
data.pop("extra_services", None)
|
||||
data.pop("service", None)
|
||||
from_card = data.pop("_from_card", False)
|
||||
card_key = data.pop("_card_key", None)
|
||||
await state.set_data(data)
|
||||
|
||||
if from_card and card_key and card_key in ALL_SERVICES:
|
||||
# Возвращаем карточку с фото
|
||||
svc = ALL_SERVICES[card_key]
|
||||
card_text = _make_service_card(card_key)
|
||||
book_btn = InlineKeyboardMarkup(inline_keyboard=[[
|
||||
InlineKeyboardButton(text="\U0001f4c5 Записаться", callback_data=card_key)
|
||||
]])
|
||||
try:
|
||||
img = FSInputFile(svc["image"])
|
||||
await callback.message.answer_photo(photo=img, caption=card_text, reply_markup=book_btn)
|
||||
try:
|
||||
await callback.message.delete()
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
await callback.message.edit_text(
|
||||
"Выберите услугу:",
|
||||
reply_markup=get_services_keyboard()
|
||||
)
|
||||
else:
|
||||
await callback.message.edit_text(
|
||||
"Выберите услугу:",
|
||||
reply_markup=get_services_keyboard()
|
||||
)
|
||||
await callback.message.edit_text(
|
||||
"Выберите услугу:",
|
||||
reply_markup=get_services_keyboard()
|
||||
)
|
||||
|
||||
elif current_state == BookingState.master:
|
||||
# Назад к доп. услугам
|
||||
# Back to extra services
|
||||
await state.set_state(BookingState.extra_services)
|
||||
data = await state.get_data()
|
||||
extra = data.get("extra_services", [])
|
||||
|
||||
Reference in New Issue
Block a user