✅ Модель 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>
189 lines
6.4 KiB
Python
189 lines
6.4 KiB
Python
"""
|
||
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)
|