243 lines
8.3 KiB
Python
243 lines
8.3 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
from typing import Any
|
|
|
|
from fastapi import HTTPException
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.enums.credit_record import (
|
|
CreditRecordBillingScene,
|
|
CreditRecordChargeKind,
|
|
CreditRecordSubject,
|
|
CreditRecordSourceModule,
|
|
)
|
|
from app.enums.team import TeamStatus
|
|
from app.enums.user import UserType
|
|
from app.models.team import Team
|
|
from app.models.user import User
|
|
from app.services.credits import add_credits, deduct_credits
|
|
from app.services.credit_record_meta_service import CreditRecordMeta
|
|
from app.utils.id_gen import generate_id
|
|
|
|
|
|
async def set_team_manager(db: AsyncSession, team_id: str, user_id: str | None) -> Team:
|
|
"""设置团队管理人。user_id 为 None 表示取消管理人。"""
|
|
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:
|
|
raise HTTPException(status_code=404, detail="团队不存在")
|
|
|
|
if user_id is None:
|
|
team.manager_id = None
|
|
await db.flush()
|
|
await db.refresh(team)
|
|
return team
|
|
|
|
# 验证用户存在、是前台用户、属于该团队
|
|
user_result = await db.execute(
|
|
select(User).where(User.id == user_id, User.is_active.is_(True)).limit(1)
|
|
)
|
|
user = user_result.scalar_one_or_none()
|
|
if not user:
|
|
raise HTTPException(status_code=404, detail="用户不存在")
|
|
if user.user_type != UserType.FRONTEND.value:
|
|
raise HTTPException(status_code=400, detail="仅前台用户可设为团队管理人")
|
|
if user.team_id != team_id:
|
|
raise HTTPException(status_code=400, detail="用户不属于该团队,请先将其加入团队")
|
|
|
|
team.manager_id = user_id
|
|
await db.flush()
|
|
await db.refresh(team)
|
|
return team
|
|
|
|
|
|
async def is_team_manager(db: AsyncSession, user_id: str, team_id: str | None) -> bool:
|
|
"""判断用户是否是指定团队的管理人。"""
|
|
if not team_id:
|
|
return False
|
|
result = await db.execute(
|
|
select(Team.manager_id).where(Team.id == team_id, Team.deleted_at.is_(None)).limit(1)
|
|
)
|
|
manager_id = result.scalar_one_or_none()
|
|
return manager_id == user_id
|
|
|
|
|
|
async def get_managed_team(db: AsyncSession, user_id: str) -> Team | None:
|
|
"""获取用户管理的团队。"""
|
|
result = await db.execute(
|
|
select(Team).where(Team.manager_id == user_id, Team.deleted_at.is_(None)).limit(1)
|
|
)
|
|
return result.scalar_one_or_none()
|
|
|
|
|
|
async def get_team_members(
|
|
db: AsyncSession,
|
|
team_id: str,
|
|
*,
|
|
page: int = 1,
|
|
page_size: int = 20,
|
|
) -> dict[str, Any]:
|
|
"""列出团队成员(仅前台用户)。"""
|
|
page = max(int(page or 1), 1)
|
|
page_size = min(max(int(page_size or 20), 1), 100)
|
|
|
|
# 验证团队存在
|
|
team_result = await db.execute(
|
|
select(Team).where(Team.id == team_id, Team.deleted_at.is_(None)).limit(1)
|
|
)
|
|
if not team_result.scalar_one_or_none():
|
|
raise HTTPException(status_code=404, detail="团队不存在")
|
|
|
|
# 总数
|
|
from sqlalchemy import func
|
|
total = (await db.execute(
|
|
select(func.count(User.id)).where(
|
|
User.user_type == UserType.FRONTEND.value,
|
|
User.team_id == team_id,
|
|
User.is_active.is_(True),
|
|
)
|
|
)).scalar() or 0
|
|
|
|
# 列表
|
|
result = await db.execute(
|
|
select(User).where(
|
|
User.user_type == UserType.FRONTEND.value,
|
|
User.team_id == team_id,
|
|
User.is_active.is_(True),
|
|
)
|
|
.order_by(User.created_at.asc())
|
|
.offset((page - 1) * page_size)
|
|
.limit(page_size)
|
|
)
|
|
members = list(result.scalars().all())
|
|
|
|
return {
|
|
"items": [
|
|
{
|
|
"id": m.id,
|
|
"username": m.username,
|
|
"phone": m.phone,
|
|
"credits": m.credits,
|
|
"is_active": m.is_active,
|
|
"joined_at": m.created_at,
|
|
}
|
|
for m in members
|
|
],
|
|
"total": total,
|
|
}
|
|
|
|
|
|
async def transfer_credits_to_member(
|
|
db: AsyncSession,
|
|
manager_id: str,
|
|
target_member_id: str,
|
|
amount: float,
|
|
direction: str = "increase", # "increase" 管理人→成员; "decrease" 成员扣减
|
|
description: str | None = None,
|
|
) -> None:
|
|
"""管理人为团队成员增加或扣减积分。
|
|
|
|
direction:
|
|
- "increase": 管理人从自己余额转积分给成员(管理人减少,成员增加)
|
|
- "decrease": 从成员扣积分回到管理人(成员减少,管理人增加)
|
|
"""
|
|
if amount <= 0:
|
|
raise HTTPException(status_code=400, description="积分数量必须大于0")
|
|
if direction not in ("increase", "decrease"):
|
|
raise HTTPException(status_code=400, description="无效操作方向")
|
|
|
|
# 获取管理人
|
|
manager_result = await db.execute(
|
|
select(User).where(User.id == manager_id, User.is_active.is_(True)).limit(1)
|
|
)
|
|
manager = manager_result.scalar_one_or_none()
|
|
if not manager:
|
|
raise HTTPException(status_code=404, detail="管理员不存在")
|
|
|
|
# 获取目标成员
|
|
member_result = await db.execute(
|
|
select(User).where(User.id == target_member_id, User.is_active.is_(True)).limit(1)
|
|
)
|
|
member = member_result.scalar_one_or_none()
|
|
if not member:
|
|
raise HTTPException(status_code=404, detail="成员不存在")
|
|
|
|
# 验证管理人是该团队的管理人且目标是同团队成员
|
|
if not manager.team_id:
|
|
raise HTTPException(status_code=400, detail="您不在任何团队中")
|
|
if member.team_id != manager.team_id:
|
|
raise HTTPException(status_code=400, detail="只能操作同团队成员")
|
|
|
|
team_result = await db.execute(
|
|
select(Team).where(Team.id == manager.team_id, Team.deleted_at.is_(None)).limit(1)
|
|
)
|
|
team = team_result.scalar_one_or_none()
|
|
if not team or team.manager_id != manager_id:
|
|
raise HTTPException(status_code=403, detail="只有团队管理人才能分配积分")
|
|
|
|
# 禁止管理人给自己转积分
|
|
if target_member_id == manager_id:
|
|
raise HTTPException(status_code=400, detail="不能给自己调整积分")
|
|
|
|
desc = description or ("团队积分发放" if direction == "increase" else "团队积分扣减")
|
|
xfer_id = generate_id()
|
|
|
|
# 构建团队内部转账的 meta,确保 team_id_snapshot 等字段被正确设置
|
|
def _build_team_transfer_meta(uid: str) -> CreditRecordMeta:
|
|
meta = CreditRecordMeta(
|
|
owner_type="team_internal_transfer",
|
|
owner_id=xfer_id,
|
|
charge_kind=CreditRecordChargeKind.TEAM_INTERNAL.value,
|
|
credit_subject=CreditRecordSubject.TEAM_INTERNAL.value,
|
|
source_module=CreditRecordSourceModule.TEAM.value,
|
|
billing_scene=CreditRecordBillingScene.TEAM_INTERNAL_TRANSFER.value,
|
|
)
|
|
return meta
|
|
|
|
if direction == "increase":
|
|
# 管理人扣减
|
|
await deduct_credits(
|
|
db,
|
|
manager_id,
|
|
amount,
|
|
f"分配给成员 {member.username}: {desc}",
|
|
record_type="team_internal",
|
|
biz_key=f"mgr_xfer_out:{manager_id}:{target_member_id}:{xfer_id}",
|
|
record_meta=_build_team_transfer_meta(manager_id),
|
|
)
|
|
# 成员增加
|
|
await add_credits(
|
|
db,
|
|
target_member_id,
|
|
amount,
|
|
f"来自团队管理人: {desc}",
|
|
record_type="team_internal",
|
|
biz_key=f"mgr_xfer_in:{manager_id}:{target_member_id}:{xfer_id}",
|
|
record_meta=_build_team_transfer_meta(target_member_id),
|
|
)
|
|
else:
|
|
# 成员扣减
|
|
await deduct_credits(
|
|
db,
|
|
target_member_id,
|
|
amount,
|
|
f"扣减给团队管理人: {desc}",
|
|
record_type="team_internal",
|
|
biz_key=f"mgr_deduct_out:{manager_id}:{target_member_id}:{xfer_id}",
|
|
record_meta=_build_team_transfer_meta(target_member_id),
|
|
)
|
|
# 管理人增加
|
|
await add_credits(
|
|
db,
|
|
manager_id,
|
|
amount,
|
|
f"来自成员 {member.username}: {desc}",
|
|
record_type="team_internal",
|
|
biz_key=f"mgr_deduct_in:{manager_id}:{target_member_id}:{xfer_id}",
|
|
record_meta=_build_team_transfer_meta(manager_id),
|
|
)
|