""" Events — календарь событий дома """ import logging from datetime import datetime, timedelta from aiogram import Router, F from aiogram.types import Message, CallbackQuery from aiogram.filters import Command, StateFilter from aiogram.fsm.state import State, StatesGroup from aiogram.fsm.context import FSMContext from sqlalchemy.ext.asyncio import AsyncSession from database.db import AsyncSessionLocal from services.events import EventsService from utils.decorators import admin_only logger = logging.getLogger(__name__) router = Router() # ============================================================================= # FSM для создания события # ============================================================================= class EventState(StatesGroup): waiting_for_title = State() waiting_for_date = State() waiting_for_location = State() waiting_for_description = State() # ============================================================================= # СОЗДАНИЕ СОБЫТИЯ # ============================================================================= @router.message(Command('event_add')) @router.message(Command('добавить_событие')) @admin_only async def cmd_event_add(message: Message, state: FSMContext): """ Добавить событие Использование: /event_add """ await message.answer( '📅 Создание события\n\n' 'Отправьте название события:\n' 'Например: "Общее собрание жильцов"', parse_mode='HTML' ) await state.set_state(EventState.waiting_for_title) @router.message(EventState.waiting_for_title) async def process_event_title(message: Message, state: FSMContext): """Получили название события""" await state.update_data(title=message.text) await message.answer( f'✅ Название: {message.text}\n\n' 'Отправьте дату и время события в формате:\n' 'ДД.ММ.ГГГГ ЧЧ:ММ\n\n' 'Например: 01.03.2026 19:00', parse_mode='HTML' ) await state.set_state(EventState.waiting_for_date) @router.message(EventState.waiting_for_date) async def process_event_date(message: Message, state: FSMContext): """Получили дату события""" text = message.text.strip() try: event_date = datetime.strptime(text, '%d.%m.%Y %H:%M') except ValueError: await message.answer( '❌ Неверный формат даты.\n' 'Используйте: ДД.ММ.ГГГГ ЧЧ:ММ', parse_mode='HTML' ) return if event_date < datetime.utcnow(): await message.answer('❌ Дата должна быть в будущем!') return await state.update_data(event_date=event_date) await message.answer( f'✅ Дата: {event_date.strftime("%d.%m.%Y %H:%M")}\n\n' 'Отправьте место проведения (необязательно) или "пропустить":\n' 'Например: "Холл 1 этажа"', parse_mode='HTML' ) await state.set_state(EventState.waiting_for_location) @router.message(EventState.waiting_for_location) async def process_event_location(message: Message, state: FSMContext): """Получили место проведения""" location = message.text if message.text.lower() != 'пропустить' else None await state.update_data(location=location) await message.answer( f'✅ Место: {location or "Не указано"}\n\n' 'Отправьте описание события (необязательно) или "пропустить":', parse_mode='HTML' ) await state.set_state(EventState.waiting_for_description) @router.message(EventState.waiting_for_description) async def process_event_description(message: Message, state: FSMContext): """Получили описание""" description = message.text if message.text.lower() != 'пропустить' else None await state.update_data(description=description) # Создаём событие data = await state.get_data() async with AsyncSessionLocal() as session: events_service = EventsService(session) event_id = await events_service.create_event( title=data['title'], event_date=data['event_date'], description=description, location=data.get('location'), created_by=message.from_user.id ) await message.answer( f'✅ Событие создано!\n\n' f'📅 {data["title"]}\n' f'🗓️ {data["event_date"].strftime("%d.%m.%Y %H:%M")}\n' f'📍 {data.get("location") or "Место не указано"}\n' f'🔖 ID: {event_id}\n\n' f'Для просмотра: /events', parse_mode='HTML' ) await state.clear() # ============================================================================= # ПРОСМОТР СОБЫТИЙ # ============================================================================= @router.message(Command('events')) @router.message(Command('события')) async def cmd_events(message: Message): """Показать предстоящие события""" async with AsyncSessionLocal() as session: events_service = EventsService(session) events = await events_service.get_upcoming_events(10) if not events: await message.answer('📅 Предстоящих событий нет\n\n' 'Добавить событие: /event_add', parse_mode='HTML') return text = '📅 Предстоящие события\n\n' for event in events: emoji = events_service.get_event_type_emoji(event['event_type']) date_str = event['event_date'].strftime('%d.%m.%Y %H:%M') text += f'{emoji} {event["title"]}\n' text += f' └─ {date_str}\n' text += f' └─ 📍 {event["location"] or "Место не указано"}\n' text += f' └─ 🔖 ID: {event["id"]}\n\n' text += '💡 Для участия: /join ' await message.answer(text, parse_mode='HTML') # ============================================================================= # УЧАСТИЕ В СОБЫТИИ # ============================================================================= @router.message(Command('join')) async def cmd_join(message: Message): """ Присоединиться к событию Использование: /join """ args = message.text.split() if len(args) < 2: await message.answer( '❌ Использование:\n' '/join <event_id>', parse_mode='HTML' ) return try: event_id = int(args[1]) except ValueError: await message.answer('❌ Неверный ID события') return async with AsyncSessionLocal() as session: events_service = EventsService(session) event = await events_service.get_event(event_id) if not event: await message.answer('❌ Событие не найдено') return if not event['is_active']: await message.answer('❌ Событие отменено') return success = await events_service.join_event(event_id, message.from_user.id) if success: await message.answer( f'✅ Вы записались на событие!\n\n' f'📅 {event["title"]}\n' f'🗓️ {event["event_date"].strftime("%d.%m.%Y %H:%M")}\n' f'👥 Участников: {event["participants_count"] + 1}', parse_mode='HTML' ) else: await message.answer('ℹ️ Вы уже записаны на это событие') @router.message(Command('leave')) async def cmd_leave(message: Message): """ Отписаться от события Использование: /leave """ args = message.text.split() if len(args) < 2: await message.answer( '❌ Использование:\n' '/leave <event_id>', parse_mode='HTML' ) return try: event_id = int(args[1]) except ValueError: await message.answer('❌ Неверный ID события') return async with AsyncSessionLocal() as session: events_service = EventsService(session) success = await events_service.leave_event(event_id, message.from_user.id) if success: await message.answer('✅ Вы отписались от события') else: await message.answer('ℹ️ Вы не записаны на это событие') # ============================================================================= # МОИ СОБЫТИЯ # ============================================================================= @router.message(Command('my_events')) @router.message(Command('мои_события')) async def cmd_my_events(message: Message): """Показать события пользователя""" async with AsyncSessionLocal() as session: events_service = EventsService(session) events = await events_service.get_user_events(message.from_user.id) if not events: await message.answer('📅 Вы не записаны на события\n\n' 'Посмотреть все: /events', parse_mode='HTML') return text = '📅 Ваши события\n\n' for event in events: date_str = event['event_date'].strftime('%d.%m.%Y %H:%M') text += f'📅 {event["title"]}\n' text += f' └─ {date_str}\n' text += f' └─ 📍 {event["location"] or "Место не указано"}\n\n' await message.answer(text, parse_mode='HTML') # ============================================================================= # УЧАСТНИКИ СОБЫТИЯ # ============================================================================= @router.message(Command('participants')) @router.message(Command('участники')) @admin_only async def cmd_participants(message: Message): """ Показать участников события Использование: /participants """ args = message.text.split() if len(args) < 2: await message.answer( '❌ Использование:\n' '/participants <event_id>', parse_mode='HTML' ) return try: event_id = int(args[1]) except ValueError: await message.answer('❌ Неверный ID события') return async with AsyncSessionLocal() as session: events_service = EventsService(session) participants = await events_service.get_participants(event_id) event = await events_service.get_event(event_id) if not participants: await message.answer('ℹ️ Нет участников') return text = f'👥 Участники: {event["title"]}\n\n' text += f'Всего: {len(participants)}\n\n' for p in participants: apt = f'(кв. {p["apartment"]})' if p['apartment'] else '' text += f'• {p["name"]} {apt}\n' await message.answer(text, parse_mode='HTML') # ============================================================================= # ОТМЕНА СОБЫТИЯ # ============================================================================= @router.message(Command('event_cancel')) @router.message(Command('отменить_событие')) @admin_only async def cmd_event_cancel(message: Message): """ Отменить событие Использование: /event_cancel """ args = message.text.split() if len(args) < 2: await message.answer( '❌ Использование:\n' '/event_cancel <event_id>', parse_mode='HTML' ) return try: event_id = int(args[1]) except ValueError: await message.answer('❌ Неверный ID события') return async with AsyncSessionLocal() as session: events_service = EventsService(session) success = await events_service.cancel_event(event_id) if success: await message.answer('✅ Событие отменено') else: await message.answer('❌ Событие не найдено')