"""
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('❌ Событие не найдено')