Files
qiji/backend/app/api/users.py
T
v6ole 2187be7aeb feat: 操作日志系统 + 日期修复 + 协同去重 + 主题持久化 + 计划自动完成
本次累积提交包含以下功能:

1. 修复:el-date-picker 缺失 value-format 导致日期保存偏移(8处)
2. 新增:协同拜访 visit_group_id 去重机制(完整副本 + 周报合并 + 亮灯去重)
3. 新增:计划自动完成(创建计划检测已有拜访 / 编辑拜访触发)
4. 新增:拜访表单选择客户后自动预填过期计划内容
5. 新增:移动端主题切换按钮 + 后端持久化
6. 新增:操作日志系统 (audit_logs),全覆盖 8 个模块 CRUD
7. 新增:PC端「我的数据」支局长/领导视角客户经理列

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-30 12:01:14 +08:00

179 lines
5.7 KiB
Python

import uuid
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.database import get_db
from app.middleware.auth import get_current_user, require_director
from app.models.user import User
from app.utils.audit import log_audit
from app.schemas.customer import UserOut
router = APIRouter(prefix="/users", tags=["Users"])
class UpdateUserRoleRequest(BaseModel):
role: str # manager / director / leader
department: str = ""
require_report: bool = True
color: Optional[str] = None # hex color for manager tags
@router.get("/", response_model=list[UserOut])
async def list_users(
role: Optional[str] = Query(None),
current_user: dict = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""List all users. Used for companion selection, assignment, etc."""
query = select(User)
if role:
query = query.where(User.role == role)
query = query.order_by(User.name)
result = await db.execute(query)
return result.scalars().all()
@router.get("/me")
async def get_current_user_info(
current_user: dict = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""Get current user's full profile."""
result = await db.execute(select(User).where(User.id == uuid.UUID(current_user["user_id"])))
user = result.scalar_one_or_none()
if not user:
raise HTTPException(status_code=404, detail="User not found")
return {
"id": str(user.id),
"casdoor_id": user.casdoor_id,
"name": user.name,
"role": user.role,
"department": user.department,
"wecom_userid": user.wecom_userid,
}
@router.put("/{user_id}/role")
async def update_user_role(
user_id: str,
data: UpdateUserRoleRequest,
current_user: dict = Depends(require_director),
db: AsyncSession = Depends(get_db),
):
"""Director updates a user's role. Only director can do this."""
if data.role not in ("manager", "director", "leader"):
raise HTTPException(status_code=400, detail="Invalid role. Must be manager/director/leader")
result = await db.execute(select(User).where(User.id == uuid.UUID(user_id)))
user = result.scalar_one_or_none()
if not user:
raise HTTPException(status_code=404, detail="User not found")
user.role = data.role
if data.department:
user.department = data.department
user.require_report = data.require_report
if data.color is not None:
user.color = data.color
await db.commit()
await db.refresh(user)
# Audit log
await log_audit(db, "user", uuid.UUID(user_id), user.name, "update", current_user["user_id"], current_user["name"], f"角色更新为 {data.role}")
await db.commit()
return {
"id": str(user.id),
"name": user.name,
"role": user.role,
"department": user.department,
"require_report": user.require_report,
"color": user.color,
}
class UpdateWecomRequest(BaseModel):
wecom_userid: Optional[str] = None # None or "" to unbind
@router.put("/{user_id}/wecom")
async def update_user_wecom(
user_id: str,
data: UpdateWecomRequest,
current_user: dict = Depends(require_director),
db: AsyncSession = Depends(get_db),
):
"""Director binds or unbinds a user's WeChat Work account."""
result = await db.execute(select(User).where(User.id == uuid.UUID(user_id)))
user = result.scalar_one_or_none()
if not user:
raise HTTPException(status_code=404, detail="User not found")
new_id = (data.wecom_userid or "").strip()
# Check duplicate
if new_id:
dup = await db.execute(
select(User).where(User.wecom_userid == new_id, User.id != uuid.UUID(user_id))
)
existing = dup.scalar_one_or_none()
if existing:
raise HTTPException(status_code=400, detail=f"该企微ID已被「{existing.name}」绑定")
user.wecom_userid = new_id if new_id else None
await db.commit()
await db.refresh(user)
return {
"id": str(user.id),
"name": user.name,
"wecom_userid": user.wecom_userid,
}
@router.delete("/{user_id}")
async def delete_user(
user_id: str,
current_user: dict = Depends(require_director),
db: AsyncSession = Depends(get_db),
):
"""Director deletes a user. Cannot delete self."""
if user_id == current_user["user_id"]:
raise HTTPException(status_code=400, detail="不能删除自己")
result = await db.execute(select(User).where(User.id == uuid.UUID(user_id)))
user = result.scalar_one_or_none()
if not user:
raise HTTPException(status_code=404, detail="User not found")
user_name = user.name
await db.delete(user)
await db.commit()
# Audit log
await log_audit(db, "user", uuid.UUID(user_id), user_name, "delete", current_user["user_id"], current_user["name"])
await db.commit()
return {"detail": "deleted", "name": user_name}
class UpdateThemeRequest(BaseModel):
theme: str # editorial / light
@router.put("/me/theme")
async def update_my_theme(
data: UpdateThemeRequest,
current_user: dict = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""Update the current user's theme preference."""
if data.theme not in ("editorial", "light"):
raise HTTPException(status_code=400, detail="Invalid theme")
result = await db.execute(select(User).where(User.id == uuid.UUID(current_user["user_id"])))
user = result.scalar_one_or_none()
if not user:
raise HTTPException(status_code=404, detail="User not found")
user.theme = data.theme
await db.commit()
return {"theme": user.theme}