""" Хендлеры для работы с квартирами Показывает кто живёт в квартире, статистику и т.д. """ import logging from aiogram import Router, F from aiogram.types import Message from aiogram.filters import Command from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, func from database.db import AsyncSessionLocal from database.models import User from utils.decorators import admin_only from datetime import datetime logger = logging.getLogger(__name__) router = Router() @router.message(Command('apt')) @admin_only async def cmd_apartment_info(message: Message): """ Показать всех жильцов квартиры Использование: /apt 88 """ args = message.text.split() if len(args) < 2: await message.answer( 'Использование: /apt <номер квартиры>\n\n' 'Пример: /apt 88', parse_mode='HTML' ) return apartment = args[1].strip().upper() async with AsyncSessionLocal() as session: # Находим всех жильцов этой квартиры stmt = ( select(User) .where(User.apartment == apartment) .order_by(User.verification_date.asc()) ) result = await session.execute(stmt) users = list(result.scalars().all()) if not users: await message.answer(f'🏠 Квартира {apartment}\n\n❌ Нет жильцов') return # Считаем статистику verified_count = sum(1 for u in users if u.verified) unverified_count = len(users) - verified_count text = f'🏠 Квартира {apartment} — {len(users)} чел.\n\n' text += f'✅ Верифицированы: {verified_count}\n' if unverified_count > 0: text += f'⏳ Не верифицированы: {unverified_count}\n' text += '\n' for i, user in enumerate(users, 1): status = '✅' if user.verified else '⏳' name = user.full_name or f'user{user.user_id}' username = f'@{user.username}' if user.username else '' # Кто первый верифицировался — основной жилец main_marker = '👤 (основной)' if i == 1 else '' text += f'{i}. {status} {user.user_id} — {name} {username} {main_marker}\n' if user.verification_date: days_ago = (datetime.utcnow() - user.verification_date).days text += f' └─ Верифицирован: {days_ago} дн. назад\n' else: text += f' └─ Не верифицирован\n' if user.message_count > 0: text += f' └─ Сообщений: {user.message_count}\n' text += '\n' # Если больше одного — предупреждение для админа if len(users) > 1: text += '⚠️ Внимание: несколько человек в одной квартире!\n\n' text += 'Это может быть:\n' text += '✅ Члены семьи (нормально)\n' text += '❌ Шпион (нужно проверить)\n\n' text += '💡 Спросите в чате: "Кто ещё живёт в кв. {apartment}?"' await message.answer(text, parse_mode='HTML') @router.message(Command('apt_stats')) @admin_only async def cmd_apartment_stats(message: Message): """ Статистика по квартирам с несколькими жильцами """ async with AsyncSessionLocal() as session: # Группируем по квартирам и считаем количество жильцов stmt = ( select(User.apartment, func.count(User.user_id).label('count')) .where(User.apartment.isnot(None)) .group_by(User.apartment) .having(func.count(User.user_id) > 1) .order_by(func.count(User.user_id).desc()) ) result = await session.execute(stmt) multi_apt = list(result.all()) # Общая статистика stmt = select(func.count(User.user_id)) total_users = (await session.execute(stmt)).scalar() or 0 stmt = select(func.count(User.apartment)).where(User.apartment.isnot(None)) total_apartments = (await session.execute(stmt)).scalar() or 0 if not multi_apt: await message.answer( f'📊 Статистика по квартирам\n\n' f'Всего жильцов: {total_users}\n' f'Всего квартир: {total_apartments}\n\n' f'✅ Нет квартир с несколькими жильцами' ) return text = f'📊 Статистика по квартирам\n\n' text += f'Всего жильцов: {total_users}\n' text += f'Всего квартир: {total_apartments}\n' text += f'🏠 Квартир с несколькими жильцами: {len(multi_apt)}\n\n' text += f'Квартиры с несколькими жильцами:\n\n' for apt, count in multi_apt[:20]: # Показываем первые 20 text += f'🏠 Кв. {apt} — {count} чел.\n' if len(multi_apt) > 20: text += f'\n... и ещё {len(multi_apt) - 20} квартир' text += '\n\n💡 Используйте /apt <номер> для деталей' await message.answer(text, parse_mode='HTML') @router.message(Command('find_apt')) @admin_only async def cmd_find_apartment(message: Message): """ Поиск квартиры по номеру (частичное совпадение) Использование: /find 8 Найдёт: 8, 88, 8А, 108, 808 и т.д. """ args = message.text.split() if len(args) < 2: await message.answer( 'Использование: /find <часть номера>\n\n' 'Пример: /find 8 найдёт 8, 88, 8А, 108...', parse_mode='HTML' ) return search_term = args[1].strip().upper() async with AsyncSessionLocal() as session: # Ищем квартиры где есть такой номер stmt = ( select(User.apartment, func.count(User.user_id).label('count')) .where(User.apartment.like(f'%{search_term}%')) .group_by(User.apartment) .order_by(func.count(User.user_id).desc()) ) result = await session.execute(stmt) apartments = list(result.all()) if not apartments: await message.answer(f'🔍 Квартиры с "{search_term}" не найдены') return text = f'🔍 Квартиры с "{search_term}" ({len(apartments)}):\n\n' for apt, count in apartments: text += f'🏠 Кв. {apt} — {count} чел.\n' text += '\n💡 Используйте /apt <номер> для деталей' await message.answer(text, parse_mode='HTML')