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>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
import uuid
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy import select, func
|
||||
from app.database import get_db
|
||||
from app.middleware.auth import get_current_user
|
||||
from app.models.audit_log import AuditLog
|
||||
|
||||
router = APIRouter(prefix="/audit-logs", tags=["AuditLogs"])
|
||||
|
||||
ENTITY_LABELS: dict[str, str] = {
|
||||
"customer": "客户",
|
||||
"visit": "拜访记录",
|
||||
"daily_note": "今日纪要",
|
||||
"work_plan": "工作计划",
|
||||
"mini_business": "小微商机",
|
||||
"key_visit": "要客拜访",
|
||||
"user": "用户",
|
||||
"leave": "请假",
|
||||
}
|
||||
|
||||
|
||||
@router.get("/")
|
||||
async def list_audit_logs(
|
||||
entity_type: Optional[str] = Query(None),
|
||||
action: Optional[str] = Query(None),
|
||||
operator_id: Optional[str] = Query(None),
|
||||
date_from: Optional[str] = Query(None),
|
||||
date_to: Optional[str] = Query(None),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(50, ge=10, le=200),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""List audit logs. Only director/leader can access."""
|
||||
if current_user["role"] not in ("director", "leader"):
|
||||
return {"items": [], "total": 0}
|
||||
|
||||
base = select(AuditLog)
|
||||
count_q = select(func.count(AuditLog.id))
|
||||
|
||||
if entity_type:
|
||||
base = base.where(AuditLog.entity_type == entity_type)
|
||||
count_q = count_q.where(AuditLog.entity_type == entity_type)
|
||||
if action:
|
||||
base = base.where(AuditLog.action == action)
|
||||
count_q = count_q.where(AuditLog.action == action)
|
||||
if operator_id:
|
||||
base = base.where(AuditLog.operator_id == uuid.UUID(operator_id))
|
||||
count_q = count_q.where(AuditLog.operator_id == uuid.UUID(operator_id))
|
||||
if date_from:
|
||||
base = base.where(AuditLog.created_at >= date_from)
|
||||
count_q = count_q.where(AuditLog.created_at >= date_from)
|
||||
if date_to:
|
||||
base = base.where(AuditLog.created_at < date_to + "T23:59:59")
|
||||
count_q = count_q.where(AuditLog.created_at < date_to + "T23:59:59")
|
||||
|
||||
total = (await db.execute(count_q)).scalar() or 0
|
||||
|
||||
query = base.order_by(AuditLog.created_at.desc()).offset((page - 1) * page_size).limit(page_size)
|
||||
rows = (await db.execute(query)).scalars().all()
|
||||
|
||||
items = []
|
||||
for r in rows:
|
||||
items.append({
|
||||
"id": str(r.id),
|
||||
"entity_type": r.entity_type,
|
||||
"entity_type_label": ENTITY_LABELS.get(r.entity_type, r.entity_type),
|
||||
"entity_id": str(r.entity_id),
|
||||
"entity_name": r.entity_name,
|
||||
"action": r.action,
|
||||
"operator_id": str(r.operator_id),
|
||||
"operator_name": r.operator_name,
|
||||
"summary": r.summary or "",
|
||||
"details": r.details,
|
||||
"created_at": str(r.created_at),
|
||||
})
|
||||
|
||||
return {"items": items, "total": total}
|
||||
Reference in New Issue
Block a user