"""
Хендлеры для работы с квартирами
Показывает кто живёт в квартире, статистику и т.д.
"""
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')