diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..7cdad9a Binary files /dev/null and b/.DS_Store differ diff --git a/app/.DS_Store b/app/.DS_Store new file mode 100644 index 0000000..fb7bc82 Binary files /dev/null and b/app/.DS_Store differ diff --git a/app/__pycache__/config.cpython-314.pyc b/app/__pycache__/config.cpython-314.pyc new file mode 100644 index 0000000..108a69f Binary files /dev/null and b/app/__pycache__/config.cpython-314.pyc differ diff --git a/app/__pycache__/main.cpython-314.pyc b/app/__pycache__/main.cpython-314.pyc new file mode 100644 index 0000000..adcbe0d Binary files /dev/null and b/app/__pycache__/main.cpython-314.pyc differ diff --git a/app/db/__pycache__/memory.cpython-314.pyc b/app/db/__pycache__/memory.cpython-314.pyc new file mode 100644 index 0000000..5af35a3 Binary files /dev/null and b/app/db/__pycache__/memory.cpython-314.pyc differ diff --git a/app/handlers/__pycache__/about.cpython-314.pyc b/app/handlers/__pycache__/about.cpython-314.pyc new file mode 100644 index 0000000..9bc0abd Binary files /dev/null and b/app/handlers/__pycache__/about.cpython-314.pyc differ diff --git a/app/handlers/__pycache__/booking.cpython-314.pyc b/app/handlers/__pycache__/booking.cpython-314.pyc new file mode 100644 index 0000000..12ed1fc Binary files /dev/null and b/app/handlers/__pycache__/booking.cpython-314.pyc differ diff --git a/app/handlers/__pycache__/contacts.cpython-314.pyc b/app/handlers/__pycache__/contacts.cpython-314.pyc new file mode 100644 index 0000000..60424dc Binary files /dev/null and b/app/handlers/__pycache__/contacts.cpython-314.pyc differ diff --git a/app/handlers/__pycache__/mybookings.cpython-314.pyc b/app/handlers/__pycache__/mybookings.cpython-314.pyc new file mode 100644 index 0000000..9d55276 Binary files /dev/null and b/app/handlers/__pycache__/mybookings.cpython-314.pyc differ diff --git a/app/handlers/__pycache__/profile.cpython-314.pyc b/app/handlers/__pycache__/profile.cpython-314.pyc new file mode 100644 index 0000000..2abd582 Binary files /dev/null and b/app/handlers/__pycache__/profile.cpython-314.pyc differ diff --git a/app/handlers/__pycache__/start.cpython-314.pyc b/app/handlers/__pycache__/start.cpython-314.pyc new file mode 100644 index 0000000..fec20d9 Binary files /dev/null and b/app/handlers/__pycache__/start.cpython-314.pyc differ diff --git a/app/handlers/booking.py b/app/handlers/booking.py index f2afef0..057c212 100644 --- a/app/handlers/booking.py +++ b/app/handlers/booking.py @@ -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 - ) \ No newline at end of file + 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() + ) diff --git a/app/handlers/mybookings.py b/app/handlers/mybookings.py index cf560f3..6f69a63 100644 --- a/app/handlers/mybookings.py +++ b/app/handlers/mybookings.py @@ -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) \ No newline at end of file +@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() \ No newline at end of file diff --git a/app/keyboards/__pycache__/menu.cpython-314.pyc b/app/keyboards/__pycache__/menu.cpython-314.pyc new file mode 100644 index 0000000..b27d2bd Binary files /dev/null and b/app/keyboards/__pycache__/menu.cpython-314.pyc differ diff --git a/app/keyboards/calendar.py b/app/keyboards/calendar.py new file mode 100644 index 0000000..1e02289 --- /dev/null +++ b/app/keyboards/calendar.py @@ -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 + ) \ No newline at end of file diff --git a/app/keyboards/confirm_booking.py b/app/keyboards/confirm_booking.py new file mode 100644 index 0000000..8f7a28a --- /dev/null +++ b/app/keyboards/confirm_booking.py @@ -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" + ) + ] + ] + ) \ No newline at end of file diff --git a/app/keyboards/masters.py b/app/keyboards/masters.py new file mode 100644 index 0000000..94272f2 --- /dev/null +++ b/app/keyboards/masters.py @@ -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" + ) + ] + ] + ) \ No newline at end of file diff --git a/app/keyboards/my_booking.py b/app/keyboards/my_booking.py new file mode 100644 index 0000000..c161135 --- /dev/null +++ b/app/keyboards/my_booking.py @@ -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}" + ) + ] + ] + ) \ No newline at end of file diff --git a/app/keyboards/services.py b/app/keyboards/services.py new file mode 100644 index 0000000..813ffbc --- /dev/null +++ b/app/keyboards/services.py @@ -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" + ) + ] + ] + ) \ No newline at end of file diff --git a/app/keyboards/time_slots.py b/app/keyboards/time_slots.py new file mode 100644 index 0000000..b93663c --- /dev/null +++ b/app/keyboards/time_slots.py @@ -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 + ) \ No newline at end of file diff --git a/app/main.py b/app/main.py index 614b1b3..8ce7621 100644 --- a/app/main.py +++ b/app/main.py @@ -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) diff --git a/app/services/__pycache__/booking_service.cpython-314.pyc b/app/services/__pycache__/booking_service.cpython-314.pyc new file mode 100644 index 0000000..64dc8e8 Binary files /dev/null and b/app/services/__pycache__/booking_service.cpython-314.pyc differ diff --git a/app/services/booking_service.py b/app/services/booking_service.py index fe182df..fe6de45 100644 --- a/app/services/booking_service.py +++ b/app/services/booking_service.py @@ -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 \ No newline at end of file + + 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 diff --git a/app/states/__pycache__/booking.cpython-314.pyc b/app/states/__pycache__/booking.cpython-314.pyc new file mode 100644 index 0000000..2dfd4f0 Binary files /dev/null and b/app/states/__pycache__/booking.cpython-314.pyc differ diff --git a/app/states/booking.py b/app/states/booking.py index 12e607a..6b5a03a 100644 --- a/app/states/booking.py +++ b/app/states/booking.py @@ -2,5 +2,7 @@ from aiogram.fsm.state import StatesGroup, State class BookingState(StatesGroup): service = State() + master = State() date = State() + time = State() confirm = State() \ No newline at end of file