domovoy_bot/services/initiative_group.py
Admin 19c5fdd0ac feat: v2.1 - Инициативная группа
 Модель InitiativeGroup в БД
 Сервис initiative_group.py
 Хендлеры: /ig_add, /ig_remove, /ig_list, /ig_stats, /ig_check, /ig_broadcast
 Декораторы для ИГ
 Миграция v2.1 выполнена

📦 Бекап: backups/versions/v2.1_initiative_group_2026-02-27/

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-02-28 10:11:36 +00:00

189 lines
6.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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
stmt = select(InitiativeGroup).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().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)