""" Initiative Group — хендлеры для работы с инициативной группой Только для администраторов """ import logging from aiogram import Router, F from aiogram.types import Message 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.initiative_group import InitiativeGroupService from utils.decorators import admin_only import config logger = logging.getLogger(__name__) router = Router() # ============================================================================= # FSM для добавления в ИГ # ============================================================================= class IGState(StatesGroup): waiting_for_notes = State() # ============================================================================= # ДОБАВЛЕНИЕ В ИГ # ============================================================================= @router.message(Command('ig_add')) @admin_only async def cmd_ig_add(message: Message, state: FSMContext): """ Добавить пользователя в ИГ Использование: /ig_add [роль] Роли: member, admin, coordinator """ args = message.text.split() if len(args) < 2: await message.answer( '❌ Использование:\n' '/ig_add <user_id> [роль]\n\n' 'Роли: member, admin, coordinator\n' 'Пример: /ig_add 123456789 member', parse_mode='HTML' ) return try: user_id = int(args[1]) except ValueError: await message.answer('❌ Неверный ID пользователя') return role = args[2] if len(args) > 2 else 'member' valid_roles = ['member', 'admin', 'coordinator'] if role not in valid_roles: await message.answer(f'❌ Неверная роль. Доступные: {", ".join(valid_roles)}') return async with AsyncSessionLocal() as session: ig_service = InitiativeGroupService(session) success = await ig_service.add_member(user_id, role) if success: # Получаем имя пользователя from database.models import User user = await session.get(User, user_id) name = user.full_name or user.username or f'user{user_id}' if user else f'user{user_id}' await message.answer( f'✅ Пользователь добавлен в ИГ!\n\n' f'👤 {name}\n' f'🔖 ID: {user_id}\n' f'🎭 Роль: {role}\n\n' f'Отправьте заметку о роли (необязательно) или "пропустить":', parse_mode='HTML' ) await state.update_data(ig_user_id=user_id) await state.set_state(IGState.waiting_for_notes) else: await message.answer('❌ Не удалось добавить участника') @router.message(IGState.waiting_for_notes) async def process_ig_notes(message: Message, state: FSMContext): """Получили заметку о роли""" data = await state.get_data() user_id = data.get('ig_user_id') notes = message.text if message.text.lower() != 'пропустить' else None if notes and notes.lower() != 'пропустить': async with AsyncSessionLocal() as session: ig_service = InitiativeGroupService(session) await ig_service.update_role(user_id, 'member') # Можно расширить для сохранения notes await message.answer('✅ Заметка сохранена') await state.clear() # ============================================================================= # УДАЛЕНИЕ ИЗ ИГ # ============================================================================= @router.message(Command('ig_remove')) @admin_only async def cmd_ig_remove(message: Message): """ Удалить пользователя из ИГ Использование: /ig_remove """ args = message.text.split() if len(args) < 2: await message.answer( '❌ Использование:\n' '/ig_remove <user_id>', parse_mode='HTML' ) return try: user_id = int(args[1]) except ValueError: await message.answer('❌ Неверный ID пользователя') return async with AsyncSessionLocal() as session: ig_service = InitiativeGroupService(session) success = await ig_service.remove_member(user_id) if success: await message.answer(f'✅ Пользователь {user_id} удалён из ИГ') else: await message.answer(f'❌ Пользователь {user_id} не найден в ИГ') # ============================================================================= # СПИСОК УЧАСТНИКОВ ИГ # ============================================================================= @router.message(Command('ig_list')) @admin_only async def cmd_ig_list(message: Message): """Показать список участников ИГ""" async with AsyncSessionLocal() as session: ig_service = InitiativeGroupService(session) members = await ig_service.get_members(active_only=True) stats = await ig_service.get_stats() if not members: await message.answer('ℹ️ В инициативной группе пока нет участников') return text = f'👥 Инициативная группа\n\n' text += f'Всего участников: {stats["active"]}\n\n' # Группируем по ролям by_role = {} for member in members: role = member['role'] if role not in by_role: by_role[role] = [] by_role[role].append(member) role_names = { 'admin': '🔴 Администраторы', 'coordinator': '🟡 Координаторы', 'member': '🟢 Участники' } for role, role_members in by_role.items(): role_name = role_names.get(role, role.capitalize()) text += f'{role_name}\n' for m in role_members: apt = f'(кв. {m["apartment"]})' if m['apartment'] else '' text += f' • {m["name"]} {apt}\n' text += '\n' await message.answer(text, parse_mode='HTML') # ============================================================================= # СТАТИСТИКА ИГ # ============================================================================= @router.message(Command('ig_stats')) @admin_only async def cmd_ig_stats(message: Message): """Статистика ИГ""" async with AsyncSessionLocal() as session: ig_service = InitiativeGroupService(session) stats = await ig_service.get_stats() text = '📊 Статистика инициативной группы\n\n' text += f'Всего участников: {stats["total"]}\n' text += f'Активных: {stats["active"]}\n\n' if stats['by_role']: text += 'По ролям:\n' for role, count in stats['by_role'].items(): text += f' • {role}: {count}\n' await message.answer(text, parse_mode='HTML') # ============================================================================= # РАССЫЛКА ТОЛЬКО ИГ # ============================================================================= @router.message(Command('ig_broadcast')) @admin_only async def cmd_ig_broadcast(message: Message, state: FSMContext): """ Рассылка только участникам ИГ Использование: /ig_broadcast """ await message.answer( '📬 Рассылка для инициативной группы\n\n' 'Отправьте сообщение для рассылки:', parse_mode='HTML' ) await state.set_state('ig_broadcast_waiting') @router.message(StateFilter('ig_broadcast_waiting')) @admin_only async def process_ig_broadcast(message: Message, state: FSMContext): """Отправка рассылки ИГ""" broadcast_message = message.text async with AsyncSessionLocal() as session: ig_service = InitiativeGroupService(session) member_ids = await ig_service.get_ig_only_broadcast_recipients() if not member_ids: await message.answer('❌ В ИГ нет участников') await state.clear() return success_count = 0 error_count = 0 for user_id in member_ids: try: await message.bot.send_message( user_id, f'📬 Сообщение для инициативной группы\n\n' f'{broadcast_message}\n\n' f'Это сообщение отправлено только участникам ИГ', parse_mode='HTML' ) success_count += 1 except Exception as e: logger.error(f'Не удалось отправить сообщение пользователю {user_id}: {e}') error_count += 1 await message.answer( f'✅ Рассылка завершена!\n\n' f'Успешно: {success_count}\n' f'Ошибок: {error_count}\n' f'Получателей: {len(member_ids)}', parse_mode='HTML' ) await state.clear() # ============================================================================= # ПРОВЕРКА УЧАСТНИКА # ============================================================================= @router.message(Command('ig_check')) @admin_only async def cmd_ig_check(message: Message): """ Проверить является ли пользователь участником ИГ Использование: /ig_check """ args = message.text.split() if len(args) < 2: await message.answer( '❌ Использование:\n' '/ig_check <user_id>', parse_mode='HTML' ) return try: user_id = int(args[1]) except ValueError: await message.answer('❌ Неверный ID пользователя') return async with AsyncSessionLocal() as session: ig_service = InitiativeGroupService(session) member = await ig_service.get_member(user_id) if member: from database.models import User user = await session.get(User, user_id) name = user.full_name or user.username or f'user{user_id}' if user else f'user{user_id}' await message.answer( f'✅ Пользователь в ИГ\n\n' f'👤 {name}\n' f'🎭 Роль: {member["role"]}\n' f'📅 В ИГ с: {member["joined_at"].strftime("%d.%m.%Y")}\n' f'📝 Заметка: {member["notes"] or "нет"}', parse_mode='HTML' ) else: await message.answer(f'ℹ️ Пользователь {user_id} не является участником ИГ')