from __future__ import annotations import json from fastapi import APIRouter, Body, Depends, Query from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from app.dependencies import get_admin_user, get_db from app.enums.team import TEAM_STATUS_LABELS, TeamStatus from app.enums.user import UserType from app.models.team import Team from app.models.user import User from app.schemas.team import TeamCreate, TeamListOut, TeamOptionOut, TeamUpdate from app.schemas.team_manager import SetManagerRequest from app.services.credit.team_subscription_service import ( list_member_period_usage, list_team_subscriptions_for_management, ) from app.services.operation_log import log_operation from app.services.team_manager_service import get_manager_history, set_team_manager from app.services.team_service import create_team, list_team_options, list_teams, soft_delete_team, update_team router = APIRouter(prefix="/admin/teams", tags=["admin-teams"]) async def _team_detail_payload(db: AsyncSession, team: Team) -> dict: member_count = (await db.execute( select(func.count(User.id)).where( User.user_type == UserType.FRONTEND.value, User.team_id == team.id, ) )).scalar() or 0 manager_name = None if team.manager_id: manager_name = (await db.execute( select(User.username).where(User.id == team.manager_id).limit(1) )).scalar_one_or_none() status = team.status or TeamStatus.ACTIVE.value return { "id": team.id, "name": team.name, "code": team.code, "description": team.description, "status": status, "status_label": TEAM_STATUS_LABELS.get(status, "其他状态"), "is_read_only": status == TeamStatus.DISABLED.value, "team_credit_frozen": status == TeamStatus.DISABLED.value, "sort_order": team.sort_order or 0, "member_count": int(member_count), "created_at": team.created_at, "updated_at": team.updated_at, "manager_id": team.manager_id, "manager_name": manager_name, "first_subscription_paid_at": team.first_subscription_paid_at, } @router.get("", response_model=TeamListOut) async def list_admin_teams( page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=500), keyword: str | None = Query(None), status: str | None = Query(None, pattern="^(active|disabled)$"), admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): del admin return await list_teams(db, page=page, page_size=page_size, keyword=keyword, status=status) @router.get("/options", response_model=list[TeamOptionOut]) async def list_admin_team_options( include_disabled: bool = Query(True), admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): del admin return await list_team_options(db, include_disabled=include_disabled) @router.post("") async def create_admin_team( req: TeamCreate, admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): team = await create_team(db, req) await log_operation( db, admin.id, admin.username, f"创建团队 {team.name}", "POST", "/admin/teams", detail=json.dumps({"after": req.model_dump()}, ensure_ascii=False), ) return await _team_detail_payload(db, team) @router.get("/{team_id}") async def get_admin_team( team_id: str, admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): del admin result = await db.execute(select(Team).where(Team.id == team_id, Team.deleted_at.is_(None)).limit(1)) team = result.scalar_one_or_none() if not team: from fastapi import HTTPException raise HTTPException(status_code=404, detail="团队不存在") return await _team_detail_payload(db, team) @router.put("/{team_id}") async def update_admin_team( team_id: str, req: TeamUpdate, admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): team, before, after = await update_team(db, team_id, req) await log_operation( db, admin.id, admin.username, f"修改团队 {team.name}", "PUT", f"/admin/teams/{team_id}", detail=json.dumps({"before": before, "after": after}, ensure_ascii=False), ) return await _team_detail_payload(db, team) @router.put("/{team_id}/manager") async def set_team_manager_endpoint( team_id: str, req: SetManagerRequest = Body(...), admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): team = await set_team_manager(db, team_id, req.user_id) manager_name = (await db.execute( select(User.username).where(User.id == req.user_id).limit(1) )).scalar_one_or_none() await log_operation( db, admin.id, admin.username, f"更换团队队长 {team.name}: {manager_name or req.user_id}", "PUT", f"/admin/teams/{team_id}/manager", detail=json.dumps({"manager_id": req.user_id}, ensure_ascii=False), ) return await _team_detail_payload(db, team) @router.get("/{team_id}/subscriptions") async def list_admin_team_subscriptions( team_id: str, admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): del admin return await list_team_subscriptions_for_management(db, team_id=team_id) @router.get("/{team_id}/member-usage") async def list_admin_team_member_usage( team_id: str, subscription_id: str | None = Query(None), admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): del admin return await list_member_period_usage(db, team_id=team_id, subscription_id=subscription_id) @router.get("/{team_id}/manager-history") async def list_admin_team_manager_history( team_id: str, admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): del admin return await get_manager_history(db, team_id) @router.delete("/{team_id}") async def delete_admin_team( team_id: str, admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): team, before = await soft_delete_team(db, team_id) await log_operation( db, admin.id, admin.username, f"删除团队 {team.name}", "DELETE", f"/admin/teams/{team_id}", detail=json.dumps({"before": before, "after": {"deleted_at": str(team.deleted_at)}}, ensure_ascii=False), ) return {"message": "团队已删除"}