""" Initiative Group Service — сервис для работы с инициативной группой """ import logging from datetime import datetime from typing import Dict, List, Optional from sqlalchemy import select, func from sqlalchemy.ext.asyncio import AsyncSession logger = logging.getLogger(__name__) class InitiativeGroupService: """Сервис для управления инициативной группой""" def __init__(self, session: AsyncSession): self.session = session async def add_member(self, user_id: int, role: str = 'member', notes: str = None) -> bool: """ Добавить участника в ИГ Returns: True если успешно """ from database.models import InitiativeGroup # Проверяем не состоит ли уже existing = await self.session.get(InitiativeGroup, user_id) if existing: # Обновляем роль и статус existing.role = role existing.is_active = True if notes: existing.notes = notes await self.session.commit() logger.info(f"Участник {user_id} обновлён в ИГ (роль: {role})") return True # Создаём нового member = InitiativeGroup( user_id=user_id, role=role, notes=notes ) self.session.add(member) await self.session.commit() logger.info(f"Участник {user_id} добавлен в ИГ (роль: {role})") return True async def remove_member(self, user_id: int) -> bool: """ Удалить участника из ИГ Returns: True если успешно """ from database.models import InitiativeGroup member = await self.session.get(InitiativeGroup, user_id) if not member: return False # Не удаляем, а деактивируем member.is_active = False await self.session.commit() logger.info(f"Участник {user_id} удалён из ИГ") return True async def get_members(self, active_only: bool = True) -> List[Dict]: """ Получить список участников ИГ Returns: список участников """ from database.models import InitiativeGroup, User from sqlalchemy.orm import joinedload # Используем joinedload для загрузки связи User stmt = ( select(InitiativeGroup) .options(joinedload(InitiativeGroup.user)) .join(User, InitiativeGroup.user_id == User.user_id) ) if active_only: stmt = stmt.where(InitiativeGroup.is_active == True) stmt = stmt.order_by(InitiativeGroup.joined_at) result = await self.session.execute(stmt) members = list(result.scalars().unique().all()) return [ { 'user_id': m.user_id, 'role': m.role, 'joined_at': m.joined_at, 'is_active': m.is_active, 'notes': m.notes, 'name': m.user.full_name or m.user.username or f'user{m.user_id}', 'apartment': m.user.apartment } for m in members ] async def get_member(self, user_id: int) -> Optional[Dict]: """ Получить информацию об участнике ИГ Returns: информация или None """ from database.models import InitiativeGroup member = await self.session.get(InitiativeGroup, user_id) if not member or not member.is_active: return None return { 'user_id': member.user_id, 'role': member.role, 'joined_at': member.joined_at, 'notes': member.notes } async def is_member(self, user_id: int) -> bool: """ Проверить является ли пользователь участником ИГ Returns: True если участник """ from database.models import InitiativeGroup member = await self.session.get(InitiativeGroup, user_id) return member is not None and member.is_active async def update_role(self, user_id: int, role: str) -> bool: """ Обновить роль участника Returns: True если успешно """ from database.models import InitiativeGroup member = await self.session.get(InitiativeGroup, user_id) if not member: return False member.role = role await self.session.commit() logger.info(f"Роль участника {user_id} изменена на {role}") return True async def get_member_ids(self, active_only: bool = True) -> List[int]: """ Получить список ID участников ИГ Returns: список ID """ from database.models import InitiativeGroup stmt = select(InitiativeGroup.user_id) if active_only: stmt = stmt.where(InitiativeGroup.is_active == True) result = await self.session.execute(stmt) return [row[0] for row in result.all()] async def get_stats(self) -> Dict: """ Получить статистику ИГ Returns: статистика """ from database.models import InitiativeGroup # Всего участников stmt_total = select(func.count(InitiativeGroup.id)) result = await self.session.execute(stmt_total) total = result.scalar() or 0 # Активных stmt_active = select(func.count(InitiativeGroup.id)).where(InitiativeGroup.is_active == True) result = await self.session.execute(stmt_active) active = result.scalar() or 0 # По ролям stmt_roles = select(InitiativeGroup.role, func.count(InitiativeGroup.id)).group_by(InitiativeGroup.role) result = await self.session.execute(stmt_roles) by_role = {row[0]: row[1] for row in result.all()} return { 'total': total, 'active': active, 'by_role': by_role } async def get_ig_only_broadcast_recipients(self) -> List[int]: """ Получить список ID для рассылки только ИГ Returns: список user_id """ return await self.get_member_ids(active_only=True)