You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
225 lines
8.3 KiB
225 lines
8.3 KiB
import uuid
|
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy.orm import selectinload
|
|
from database import get_db
|
|
from models import Department, User, UserRole, Role
|
|
from schemas import (
|
|
DepartmentCreate, DepartmentUpdate, DepartmentOut,
|
|
UserCreate, UserUpdate, UserOut,
|
|
)
|
|
from modules.auth.router import hash_password, get_user_roles
|
|
|
|
router = APIRouter(prefix="/api/org", tags=["org"])
|
|
|
|
|
|
@router.get("/departments", response_model=list[DepartmentOut])
|
|
async def get_departments(request: Request, db: AsyncSession = Depends(get_db)):
|
|
result = await db.execute(
|
|
select(Department).where(Department.parent_id.is_(None)).order_by(Department.sort_order)
|
|
)
|
|
roots = result.scalars().all()
|
|
return [await _build_department_tree(db, d) for d in roots]
|
|
|
|
|
|
async def _build_department_tree(db: AsyncSession, dept: Department, _visited: set[uuid.UUID] = None) -> DepartmentOut:
|
|
if _visited is None:
|
|
_visited = set()
|
|
if dept.id in _visited:
|
|
return DepartmentOut(id=dept.id, name=dept.name, parent_id=dept.parent_id, path=dept.path, level=dept.level, sort_order=dept.sort_order, children=[])
|
|
_visited.add(dept.id)
|
|
children_result = await db.execute(
|
|
select(Department).where(Department.parent_id == dept.id).order_by(Department.sort_order)
|
|
)
|
|
children = children_result.scalars().all()
|
|
return DepartmentOut(
|
|
id=dept.id, name=dept.name, parent_id=dept.parent_id,
|
|
path=dept.path, level=dept.level, sort_order=dept.sort_order,
|
|
children=[await _build_department_tree(db, c, _visited) for c in children],
|
|
)
|
|
|
|
|
|
@router.post("/departments", response_model=DepartmentOut)
|
|
async def create_department(
|
|
req: DepartmentCreate, request: Request, db: AsyncSession = Depends(get_db)
|
|
):
|
|
parent_path = "/"
|
|
level = 0
|
|
if req.parent_id:
|
|
parent_result = await db.execute(select(Department).where(Department.id == req.parent_id))
|
|
parent = parent_result.scalar_one_or_none()
|
|
if not parent:
|
|
raise HTTPException(404, "父部门不存在")
|
|
parent_path = parent.path
|
|
level = parent.level + 1
|
|
|
|
dept = Department(
|
|
name=req.name, parent_id=req.parent_id,
|
|
path=f"{parent_path}/{req.name}".replace("//", "/"),
|
|
level=level, sort_order=req.sort_order,
|
|
)
|
|
db.add(dept)
|
|
await db.flush()
|
|
return DepartmentOut(
|
|
id=dept.id, name=dept.name, parent_id=dept.parent_id,
|
|
path=dept.path, level=dept.level, sort_order=dept.sort_order,
|
|
children=[],
|
|
)
|
|
|
|
|
|
@router.put("/departments/{dept_id}", response_model=DepartmentOut)
|
|
async def update_department(
|
|
dept_id: uuid.UUID, req: DepartmentUpdate,
|
|
request: Request, db: AsyncSession = Depends(get_db),
|
|
):
|
|
result = await db.execute(select(Department).where(Department.id == dept_id))
|
|
dept = result.scalar_one_or_none()
|
|
if not dept:
|
|
raise HTTPException(404, "部门不存在")
|
|
if req.name is not None:
|
|
dept.name = req.name
|
|
if req.parent_id is not None:
|
|
dept.parent_id = req.parent_id
|
|
if req.sort_order is not None:
|
|
dept.sort_order = req.sort_order
|
|
return DepartmentOut(
|
|
id=dept.id, name=dept.name, parent_id=dept.parent_id,
|
|
path=dept.path, level=dept.level, sort_order=dept.sort_order,
|
|
children=[],
|
|
)
|
|
|
|
|
|
@router.delete("/departments/{dept_id}")
|
|
async def delete_department(dept_id: uuid.UUID, request: Request, db: AsyncSession = Depends(get_db)):
|
|
result = await db.execute(select(Department).where(Department.id == dept_id))
|
|
dept = result.scalar_one_or_none()
|
|
if not dept:
|
|
raise HTTPException(404, "部门不存在")
|
|
await db.delete(dept)
|
|
return {"code": 200, "message": "删除成功"}
|
|
|
|
|
|
@router.get("/users", response_model=list[UserOut])
|
|
async def get_users(request: Request, db: AsyncSession = Depends(get_db)):
|
|
user_ctx = request.state.user
|
|
result = await db.execute(select(User))
|
|
users = result.scalars().all()
|
|
|
|
if user_ctx["data_scope"] == "self_only":
|
|
users = [u for u in users if str(u.id) == user_ctx["id"]]
|
|
elif user_ctx["data_scope"] == "subordinate_only":
|
|
sub_ids = await _get_subordinate_ids(db, uuid.UUID(user_ctx["id"]))
|
|
sub_ids.add(uuid.UUID(user_ctx["id"]))
|
|
users = [u for u in users if u.id in sub_ids]
|
|
|
|
return [await _user_to_out(db, u) for u in users]
|
|
|
|
|
|
@router.get("/users/{user_id}", response_model=UserOut)
|
|
async def get_user(user_id: uuid.UUID, request: Request, db: AsyncSession = Depends(get_db)):
|
|
result = await db.execute(select(User).where(User.id == user_id))
|
|
user = result.scalar_one_or_none()
|
|
if not user:
|
|
raise HTTPException(404, "用户不存在")
|
|
return await _user_to_out(db, user)
|
|
|
|
|
|
@router.post("/users", response_model=UserOut)
|
|
async def create_user(req: UserCreate, request: Request, db: AsyncSession = Depends(get_db)):
|
|
existing = await db.execute(select(User).where(User.username == req.username))
|
|
if existing.scalar_one_or_none():
|
|
raise HTTPException(400, "用户名已存在")
|
|
|
|
user = User(
|
|
username=req.username,
|
|
password_hash=hash_password(req.password),
|
|
display_name=req.display_name,
|
|
email=req.email, phone=req.phone,
|
|
wecom_user_id=req.wecom_user_id,
|
|
department_id=req.department_id,
|
|
position=req.position, manager_id=req.manager_id,
|
|
)
|
|
db.add(user)
|
|
await db.flush()
|
|
|
|
if req.role_ids:
|
|
for role_id in req.role_ids:
|
|
db.add(UserRole(user_id=user.id, role_id=role_id))
|
|
await db.flush()
|
|
|
|
return await _user_to_out(db, user)
|
|
|
|
|
|
@router.put("/users/{user_id}", response_model=UserOut)
|
|
async def update_user(
|
|
user_id: uuid.UUID, req: UserUpdate,
|
|
request: Request, db: AsyncSession = Depends(get_db),
|
|
):
|
|
result = await db.execute(select(User).where(User.id == user_id))
|
|
user = result.scalar_one_or_none()
|
|
if not user:
|
|
raise HTTPException(404, "用户不存在")
|
|
|
|
if req.display_name is not None:
|
|
user.display_name = req.display_name
|
|
if req.email is not None:
|
|
user.email = req.email
|
|
if req.phone is not None:
|
|
user.phone = req.phone
|
|
if req.department_id is not None:
|
|
user.department_id = req.department_id
|
|
if req.position is not None:
|
|
user.position = req.position
|
|
if req.manager_id is not None:
|
|
user.manager_id = req.manager_id
|
|
if req.status is not None:
|
|
user.status = req.status
|
|
|
|
if req.role_ids is not None:
|
|
await db.execute(select(UserRole).where(UserRole.user_id == user.id))
|
|
existing_urs = (await db.execute(
|
|
select(UserRole).where(UserRole.user_id == user.id)
|
|
)).scalars().all()
|
|
for ur in existing_urs:
|
|
await db.delete(ur)
|
|
for role_id in req.role_ids:
|
|
db.add(UserRole(user_id=user.id, role_id=role_id))
|
|
|
|
return await _user_to_out(db, user)
|
|
|
|
|
|
@router.get("/subordinates", response_model=list[UserOut])
|
|
async def get_subordinates(request: Request, db: AsyncSession = Depends(get_db)):
|
|
user_ctx = request.state.user
|
|
manager_id = uuid.UUID(user_ctx["id"])
|
|
sub_ids = await _get_subordinate_ids(db, manager_id)
|
|
|
|
result = await db.execute(select(User).where(User.id.in_(sub_ids)))
|
|
users = result.scalars().all()
|
|
return [await _user_to_out(db, u) for u in users]
|
|
|
|
|
|
async def _get_subordinate_ids(db: AsyncSession, manager_id: uuid.UUID, _visited: set[uuid.UUID] = None) -> set[uuid.UUID]:
|
|
if _visited is None:
|
|
_visited = set()
|
|
if manager_id in _visited:
|
|
return set()
|
|
_visited.add(manager_id)
|
|
result = await db.execute(select(User).where(User.manager_id == manager_id))
|
|
direct = result.scalars().all()
|
|
ids = {u.id for u in direct}
|
|
for sub in direct:
|
|
ids.update(await _get_subordinate_ids(db, sub.id, _visited))
|
|
return ids
|
|
|
|
|
|
async def _user_to_out(db: AsyncSession, user: User) -> UserOut:
|
|
roles = await get_user_roles(db, user.id)
|
|
return UserOut(
|
|
id=user.id, username=user.username, display_name=user.display_name,
|
|
email=user.email, phone=user.phone, wecom_user_id=user.wecom_user_id,
|
|
department_id=user.department_id, position=user.position,
|
|
manager_id=user.manager_id, status=user.status,
|
|
roles=roles, created_at=user.created_at,
|
|
)
|