import re from aiogram import Router, F 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, get_extra_services_keyboard from app.keyboards.masters import get_masters_keyboard from app.states.booking import BookingState from app.services.booking_service import ( create_booking, update_booking, get_busy_times, is_slot_busy, cleanup_cancelled ) 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 from app.logger import logger router = Router() def _services_to_str(data): """Combine main + extra services into one string.""" main = data.get("service", "?") extra = data.get("extra_services", []) if extra: return main + ", " + ", ".join(extra) return main # отмена записи на любом шаге @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( "Выберите услугу:", reply_markup=get_services_keyboard() ) # выбор первой услуги @router.callback_query( StateFilter(BookingState.service), F.data.startswith("service_") ) async def choose_service(callback: CallbackQuery, state: FSMContext): 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=[]) await state.set_state(BookingState.extra_services) await callback.message.edit_text( "✅ Выбрано: " + service_name + "\n\n" "Хотите добавить ещё услугу?\n" "Нажмите нужную или «Готово»:", reply_markup=get_extra_services_keyboard() ) await callback.answer() # выбор дополнительных услуг @router.callback_query( StateFilter(BookingState.extra_services), 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( "👤 Выберите специалиста:", reply_markup=get_masters_keyboard() ) await callback.answer() return # Toggle extra service actual_key = callback.data.replace("extra_", "", 1) service_name = SERVICES.get(actual_key) if not service_name: await callback.answer("Услуга не найдена") return data = await state.get_data() extra = data.get("extra_services", []) if service_name in extra: extra.remove(service_name) logger.debug("User {uid} removed extra service: {srv}", uid=callback.from_user.id, srv=service_name) else: extra.append(service_name) logger.debug("User {uid} added extra service: {srv}", uid=callback.from_user.id, srv=service_name) await state.update_data(extra_services=extra) data = await state.get_data() services_text = _services_to_str(data) await callback.message.edit_text( "✅ Выбрано: " + services_text + "\n\n" "Хотите добавить ещё услугу?\n" "Нажмите нужную или «Готово»:", reply_markup=get_extra_services_keyboard(extra) ) await callback.answer() # выбор мастера @router.callback_query( StateFilter(BookingState.master), F.data.startswith("master_") ) async def choose_master(callback: CallbackQuery, state: FSMContext): from app.config import MASTERS 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) await state.set_state(BookingState.date) await callback.message.edit_text( "📅 Выберите дату:", reply_markup=get_dates_keyboard() ) await callback.answer() # выбор даты @router.callback_query( StateFilter(BookingState.date), F.data.startswith("date_") ) async def choose_date(callback: CallbackQuery, state: FSMContext): selected_date = callback.data.replace("date_", "") await state.update_data(date=selected_date) logger.debug("User {uid} selected date: {d}", uid=callback.from_user.id, d=selected_date) data = await state.get_data() busy_times = await get_busy_times(master=data.get("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) logger.debug("User {uid} selected time: {t}", uid=callback.from_user.id, t=selected_time) data = await state.get_data() services_str = _services_to_str(data) await state.set_state(BookingState.confirm) await callback.message.edit_text( "📋 Проверьте запись:\n\n" f"💆 Услуги: {services_str}\n" f"📅 Дата: {data.get('date', '?')}\n" f"👤 Мастер: {data.get('master', '?')}\n" f"🕒 Время: {selected_time}", reply_markup=get_confirm_booking_keyboard() ) await callback.answer() # подтверждение @router.callback_query( StateFilter(BookingState.confirm), F.data == "confirm_booking" ) async def confirm_booking_callback(callback: CallbackQuery, state: FSMContext): data = await state.get_data() logger.info("User {uid} confirmed booking: {data}", uid=callback.from_user.id, data=data) services_str = _services_to_str(data) editing_booking_id = data.get("editing_booking_id") if editing_booking_id: await update_booking( booking_id=editing_booking_id, service=services_str, master=data.get("master", "?"), date=data.get("date", "?"), time=data.get("time", "?") ) result_text = "✅ Запись успешно обновлена" else: master = data.get("master", "?") date = data.get("date", "?") time = data.get("time", "?") 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("❌ Это время уже занято. Пожалуйста, выберите другое время.") await state.clear() await callback.answer() return await create_booking( user_id=callback.from_user.id, service=services_str, master=master, date=date, time=time, user_name=callback.from_user.full_name or callback.from_user.username or str(callback.from_user.id) ) cleanup_cancelled() result_text = "✅ Запись успешно создана" await state.clear() 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) await callback.message.edit_text( "Выберите услугу:", reply_markup=get_services_keyboard() ) @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) await state.set_data(data) 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", []) services_text = _services_to_str(data) await callback.message.edit_text( "✅ Выбрано: " + services_text + "\n\n" "Хотите добавить ещё услугу?\n" "Нажмите нужную или «Готово»:", reply_markup=get_extra_services_keyboard(extra) ) elif current_state == BookingState.date: await state.set_state(BookingState.master) data = await state.get_data() data.pop("date", None) data.pop("time", None) await state.set_data(data) await callback.message.edit_text( "👤 Выберите специалиста:", reply_markup=get_masters_keyboard() ) elif current_state == BookingState.time: await state.set_state(BookingState.date) data = await state.get_data() data.pop("time", None) await state.set_data(data) await callback.message.edit_text( "📅 Выберите дату:", reply_markup=get_dates_keyboard() ) elif current_state == BookingState.confirm: await state.set_state(BookingState.time) data = await state.get_data() busy_times = await get_busy_times( master=data.get("master", "?"), date=data.get("date", "?") ) await callback.message.edit_text( "🕒 Выберите время:", reply_markup=get_time_keyboard(busy_times) ) else: await state.clear() await callback.message.edit_text("Отмена записи") await callback.answer()