1cd26e174e
- company_claim_requests:认领园区入驻企业(仅搜索);admin 认领仅当企业无管理员; 审核人:admin→园区管理员,member→有企业管理员则企业管理员否则园区管理员; 证明资料(姓名/身份证/手机号)仅存证 - company_apply_requests:已有企业加入平台;park_entered 园区管理员审, independent 运营方审;通过后建 ParkCompany 并设提交人为管理员 - teams/team_members:团队即建即用;支持升级为企业(复用企业入驻申请) - 审核权限:园区管理员=ParkTenant.operator_user_id;企业管理员=is_admin;运营方=operator - 新表由启动时 create_all 幂等创建
293 lines
11 KiB
Python
293 lines
11 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""团队(非企业,/ent/teams)。
|
||
|
||
创建即生效(无需审核);支持成员加入/退出;支持升级为企业
|
||
(升级 = 提交企业入驻申请,审核通过后团队标记 upgraded)。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||
from pydantic import BaseModel
|
||
from sqlalchemy import select
|
||
|
||
from ..dependencies import get_db, get_current_user
|
||
from ...rbac import write_audit
|
||
from ...infrastructure.models import Team, TeamMember, User
|
||
from ...infrastructure.repositories import Database, new_id, utcnow_iso
|
||
|
||
router = APIRouter(prefix="/ent/teams", tags=["teams"])
|
||
|
||
|
||
def _now() -> str:
|
||
return utcnow_iso()
|
||
|
||
|
||
def _team_dict(t, member_count: int = 0) -> dict:
|
||
return {
|
||
"id": t.id, "name": t.name, "bio": t.bio,
|
||
"owner_user_id": t.owner_user_id, "status": t.status,
|
||
"upgraded_company_id": t.upgraded_company_id,
|
||
"member_count": member_count, "created_at": t.created_at,
|
||
}
|
||
|
||
|
||
def _member_dict(m) -> dict:
|
||
return {
|
||
"id": m.id, "team_id": m.team_id, "user_id": m.user_id,
|
||
"role": m.role, "status": m.status, "created_at": m.created_at,
|
||
"left_at": m.left_at,
|
||
}
|
||
|
||
|
||
async def _get_team(db: Database, tid: str) -> Team:
|
||
t = (await db.session.execute(select(Team).where(Team.id == tid))).scalars().first()
|
||
if t is None:
|
||
raise HTTPException(status_code=404, detail="团队不存在")
|
||
return t
|
||
|
||
|
||
async def _can_manage(db: Database, team: Team, user: dict, *, need_owner: bool = False) -> bool:
|
||
"""owner/admin 可管理;owner 专属(升级/解散)需 need_owner。"""
|
||
if team.owner_user_id == user.get("id", ""):
|
||
return True
|
||
if need_owner:
|
||
return False
|
||
m = await db.session.scalar(select(TeamMember).where(
|
||
TeamMember.team_id == team.id, TeamMember.user_id == user.get("id", ""),
|
||
TeamMember.status == "active"))
|
||
return bool(m and m.role in ("owner", "admin"))
|
||
|
||
|
||
class TeamCreate(BaseModel):
|
||
name: str
|
||
bio: str = ""
|
||
|
||
|
||
class TeamUpdate(BaseModel):
|
||
name: str = ""
|
||
bio: str = ""
|
||
|
||
|
||
class TeamMemberAdd(BaseModel):
|
||
user_id: str = "" # 二选一
|
||
keyword: str = "" # 用户名/手机号搜索
|
||
|
||
|
||
@router.post("", summary="创建团队(即建即用,无需审核)")
|
||
async def create_team(
|
||
body: TeamCreate,
|
||
request: Request,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
name = (body.name or "").strip()
|
||
if not name:
|
||
raise HTTPException(status_code=400, detail="请填写团队名称")
|
||
team = Team(id=new_id("team"), name=name, bio=body.bio.strip(),
|
||
owner_user_id=user["id"], status="active", created_at=_now())
|
||
db.session.add(team)
|
||
await db.session.flush()
|
||
db.session.add(TeamMember(id=new_id("tm"), team_id=team.id, user_id=user["id"],
|
||
role="owner", status="active", created_at=_now()))
|
||
await db.session.commit()
|
||
await write_audit(db, action="team.create", resource="team",
|
||
resource_id=team.id, detail=f"name={name}", user=user, request=request)
|
||
return {"ok": True, "team": _team_dict(team, 1)}
|
||
|
||
|
||
@router.get("/mine", summary="我的团队(我创建的 + 我加入的)")
|
||
async def my_teams(
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
items = []
|
||
rows = (await db.session.scalars(
|
||
select(Team).where(Team.owner_user_id == user["id"])
|
||
.order_by(Team.created_at.desc()))).all()
|
||
seen = {t.id for t in rows}
|
||
for t in rows:
|
||
cnt = len(await db.session.scalars(select(TeamMember).where(
|
||
TeamMember.team_id == t.id, TeamMember.status == "active")))
|
||
items.append(_team_dict(t, cnt))
|
||
joined = (await db.session.scalars(
|
||
select(Team).join(TeamMember, TeamMember.team_id == Team.id).where(
|
||
TeamMember.user_id == user["id"], TeamMember.status == "active",
|
||
Team.owner_user_id != user["id"]))).all()
|
||
for t in joined:
|
||
if t.id in seen:
|
||
continue
|
||
cnt = len(await db.session.scalars(select(TeamMember).where(
|
||
TeamMember.team_id == t.id, TeamMember.status == "active")))
|
||
items.append(_team_dict(t, cnt))
|
||
return {"items": items}
|
||
|
||
|
||
@router.get("/{tid}", summary="团队详情 + 成员列表")
|
||
async def team_detail(
|
||
tid: str,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
team = await _get_team(db, tid)
|
||
members = (await db.session.scalars(select(TeamMember).where(
|
||
TeamMember.team_id == tid, TeamMember.status == "active")
|
||
.order_by(TeamMember.created_at))).all()
|
||
mlist = []
|
||
for m in members:
|
||
d = _member_dict(m)
|
||
u = await db.users.get_by_id(m.user_id)
|
||
if u:
|
||
d["username"] = u.get("username", "")
|
||
d["nickname"] = u.get("nickname", "")
|
||
mlist.append(d)
|
||
my = await db.session.scalar(select(TeamMember).where(
|
||
TeamMember.team_id == tid, TeamMember.user_id == user["id"]))
|
||
return {"team": _team_dict(team, len(members)), "members": mlist,
|
||
"my_role": my.role if my else "", "is_member": bool(my and my.status == "active")}
|
||
|
||
|
||
@router.put("/{tid}", summary="编辑团队资料(owner/admin)")
|
||
async def update_team(
|
||
tid: str,
|
||
body: TeamUpdate,
|
||
request: Request,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
team = await _get_team(db, tid)
|
||
if not await _can_manage(db, team, user):
|
||
raise HTTPException(status_code=403, detail="仅团队管理员可编辑")
|
||
if body.name.strip():
|
||
team.name = body.name.strip()
|
||
team.bio = body.bio.strip()
|
||
await db.session.commit()
|
||
await write_audit(db, action="team.update", resource="team",
|
||
resource_id=tid, user=user, request=request)
|
||
return {"ok": True}
|
||
|
||
|
||
@router.post("/{tid}/members", summary="添加成员(owner/admin,按用户搜索)")
|
||
async def add_member(
|
||
tid: str,
|
||
body: TeamMemberAdd,
|
||
request: Request,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
team = await _get_team(db, tid)
|
||
if not await _can_manage(db, team, user):
|
||
raise HTTPException(status_code=403, detail="仅团队管理员可添加成员")
|
||
target = None
|
||
if body.user_id.strip():
|
||
target = await db.users.get_by_id(body.user_id.strip())
|
||
elif body.keyword.strip():
|
||
hits = await db.users.search(body.keyword.strip(), limit=1)
|
||
target = hits[0] if hits else None
|
||
if target is None:
|
||
raise HTTPException(status_code=404, detail="用户不存在(可按用户名/手机号搜索)")
|
||
existing = await db.session.scalar(select(TeamMember).where(
|
||
TeamMember.team_id == tid, TeamMember.user_id == target["id"]))
|
||
if existing is not None:
|
||
if existing.status == "active":
|
||
raise HTTPException(status_code=400, detail="该用户已在团队中")
|
||
existing.status = "active"
|
||
existing.left_at = ""
|
||
await db.session.commit()
|
||
else:
|
||
db.session.add(TeamMember(id=new_id("tm"), team_id=tid, user_id=target["id"],
|
||
role="member", status="active", created_at=_now()))
|
||
await db.session.commit()
|
||
await write_audit(db, action="team.add_member", resource="team",
|
||
resource_id=tid, detail=f"target={target.get('username','')}",
|
||
user=user, request=request)
|
||
return {"ok": True}
|
||
|
||
|
||
@router.delete("/{tid}/members/{uid}", summary="移出成员(owner/admin,owner 不可移出)")
|
||
async def remove_member(
|
||
tid: str,
|
||
uid: str,
|
||
request: Request,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
team = await _get_team(db, tid)
|
||
if not await _can_manage(db, team, user):
|
||
raise HTTPException(status_code=403, detail="仅团队管理员可移出成员")
|
||
m = await db.session.scalar(select(TeamMember).where(
|
||
TeamMember.team_id == tid, TeamMember.user_id == uid, TeamMember.status == "active"))
|
||
if m is None:
|
||
raise HTTPException(status_code=404, detail="成员不存在")
|
||
if m.role == "owner":
|
||
raise HTTPException(status_code=400, detail="不能移出团队创建者")
|
||
m.status = "left"
|
||
m.left_at = _now()
|
||
await db.session.commit()
|
||
await write_audit(db, action="team.remove_member", resource="team",
|
||
resource_id=tid, detail=f"target={uid}", user=user, request=request)
|
||
return {"ok": True}
|
||
|
||
|
||
@router.post("/{tid}/leave", summary="退出团队")
|
||
async def leave_team(
|
||
tid: str,
|
||
request: Request,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
team = await _get_team(db, tid)
|
||
if team.owner_user_id == user["id"]:
|
||
raise HTTPException(status_code=400, detail="团队创建者不能退出,可解散或升级")
|
||
m = await db.session.scalar(select(TeamMember).where(
|
||
TeamMember.team_id == tid, TeamMember.user_id == user["id"], TeamMember.status == "active"))
|
||
if m is None:
|
||
raise HTTPException(status_code=404, detail="你不在该团队中")
|
||
m.status = "left"
|
||
m.left_at = _now()
|
||
await db.session.commit()
|
||
await write_audit(db, action="team.leave", resource="team",
|
||
resource_id=tid, user=user, request=request)
|
||
return {"ok": True}
|
||
|
||
|
||
class TeamUpgrade(BaseModel):
|
||
"""升级为企业:走企业入驻申请流程(复用 /admin/ent/companies/apply)。"""
|
||
entry_type: str = "park_entered" # park_entered/independent
|
||
tenant_id: str = ""
|
||
company_name: str = ""
|
||
legal_person: str = ""
|
||
legal_phone: str = ""
|
||
contact_phone: str = ""
|
||
address: str = ""
|
||
industry: str = ""
|
||
registered_capital: str = ""
|
||
company_type: str = ""
|
||
bio: str = ""
|
||
docs: dict = {}
|
||
|
||
|
||
@router.post("/{tid}/upgrade", summary="团队升级为企业(提交企业入驻申请)")
|
||
async def upgrade_team(
|
||
tid: str,
|
||
body: TeamUpgrade,
|
||
request: Request,
|
||
db: Database = Depends(get_db),
|
||
user: dict = Depends(get_current_user),
|
||
):
|
||
team = await _get_team(db, tid)
|
||
if team.owner_user_id != user["id"]:
|
||
raise HTTPException(status_code=403, detail="仅团队创建者可升级企业")
|
||
if team.status == "upgraded":
|
||
raise HTTPException(status_code=400, detail="该团队已升级为企业")
|
||
# 复用企业入驻申请
|
||
from .rbac_ent_onboard import CompanyApply, _apply_company_impl
|
||
payload = CompanyApply(
|
||
entry_type=body.entry_type, tenant_id=body.tenant_id,
|
||
company_name=body.company_name, legal_person=body.legal_person,
|
||
legal_phone=body.legal_phone, contact_phone=body.contact_phone,
|
||
address=body.address, industry=body.industry,
|
||
registered_capital=body.registered_capital, company_type=body.company_type,
|
||
bio=body.bio, docs=body.docs, upgrade_from_team_id=tid,
|
||
)
|
||
return await _apply_company_impl(db, user, request, payload)
|