213 lines
5.8 KiB
Python
213 lines
5.8 KiB
Python
"""用户菜单收藏 CRUD"""
|
||
from __future__ import annotations
|
||
|
||
import uuid
|
||
from datetime import datetime
|
||
from typing import Any, Dict, List, Optional
|
||
|
||
from sqlalchemy.orm import Session
|
||
|
||
from app.models.models import SysUserMenuFavorite
|
||
|
||
MAX_FAVORITES = 10
|
||
|
||
|
||
def normalize_menu_path(path: str) -> str:
|
||
p = (path or "").strip()
|
||
if not p:
|
||
return ""
|
||
if not p.startswith("/"):
|
||
p = "/" + p
|
||
return p
|
||
|
||
|
||
def _row_to_dict(row: SysUserMenuFavorite) -> Dict[str, Any]:
|
||
return {
|
||
"id": row.id,
|
||
"path": row.menu_path or "",
|
||
"orderNum": row.order_num,
|
||
"createTime": row.create_time.isoformat(sep=" ", timespec="seconds") if row.create_time else None,
|
||
}
|
||
|
||
|
||
def _next_order_num(db: Session, *, user_id: str, tenant_no: str) -> int:
|
||
max_order = (
|
||
db.query(SysUserMenuFavorite.order_num)
|
||
.filter(
|
||
SysUserMenuFavorite.del_flag == 0,
|
||
SysUserMenuFavorite.user_id == user_id,
|
||
SysUserMenuFavorite.tenant_no == tenant_no,
|
||
)
|
||
.order_by(SysUserMenuFavorite.order_num.desc())
|
||
.first()
|
||
)
|
||
return (max_order[0] if max_order and max_order[0] is not None else 0) + 1
|
||
|
||
|
||
def list_favorites(
|
||
db: Session,
|
||
*,
|
||
user_id: str,
|
||
tenant_no: str,
|
||
) -> List[Dict[str, Any]]:
|
||
rows = (
|
||
db.query(SysUserMenuFavorite)
|
||
.filter(
|
||
SysUserMenuFavorite.del_flag == 0,
|
||
SysUserMenuFavorite.user_id == user_id,
|
||
SysUserMenuFavorite.tenant_no == tenant_no,
|
||
)
|
||
.order_by(
|
||
SysUserMenuFavorite.order_num.asc(),
|
||
SysUserMenuFavorite.create_time.asc(),
|
||
)
|
||
.all()
|
||
)
|
||
return [_row_to_dict(r) for r in rows]
|
||
|
||
|
||
def count_active(db: Session, *, user_id: str, tenant_no: str) -> int:
|
||
return (
|
||
db.query(SysUserMenuFavorite)
|
||
.filter(
|
||
SysUserMenuFavorite.del_flag == 0,
|
||
SysUserMenuFavorite.user_id == user_id,
|
||
SysUserMenuFavorite.tenant_no == tenant_no,
|
||
)
|
||
.count()
|
||
)
|
||
|
||
|
||
def find_active(
|
||
db: Session, *, user_id: str, tenant_no: str, menu_path: str
|
||
) -> Optional[SysUserMenuFavorite]:
|
||
return (
|
||
db.query(SysUserMenuFavorite)
|
||
.filter(
|
||
SysUserMenuFavorite.del_flag == 0,
|
||
SysUserMenuFavorite.user_id == user_id,
|
||
SysUserMenuFavorite.tenant_no == tenant_no,
|
||
SysUserMenuFavorite.menu_path == menu_path,
|
||
)
|
||
.first()
|
||
)
|
||
|
||
|
||
def find_soft_deleted(
|
||
db: Session, *, user_id: str, tenant_no: str, menu_path: str
|
||
) -> Optional[SysUserMenuFavorite]:
|
||
return (
|
||
db.query(SysUserMenuFavorite)
|
||
.filter(
|
||
SysUserMenuFavorite.del_flag == 1,
|
||
SysUserMenuFavorite.user_id == user_id,
|
||
SysUserMenuFavorite.tenant_no == tenant_no,
|
||
SysUserMenuFavorite.menu_path == menu_path,
|
||
)
|
||
.first()
|
||
)
|
||
|
||
|
||
def add_favorite(
|
||
db: Session,
|
||
*,
|
||
user_id: str,
|
||
tenant_no: str,
|
||
menu_path: str,
|
||
create_dept_id: str = "",
|
||
) -> Dict[str, Any]:
|
||
path = normalize_menu_path(menu_path)
|
||
if not path:
|
||
raise ValueError("菜单路径无效")
|
||
|
||
existing = find_active(db, user_id=user_id, tenant_no=tenant_no, menu_path=path)
|
||
if existing:
|
||
return _row_to_dict(existing)
|
||
|
||
if count_active(db, user_id=user_id, tenant_no=tenant_no) >= MAX_FAVORITES:
|
||
raise ValueError(f"最多收藏 {MAX_FAVORITES} 个菜单")
|
||
|
||
now = datetime.now()
|
||
next_order = _next_order_num(db, user_id=user_id, tenant_no=tenant_no)
|
||
|
||
deleted = find_soft_deleted(db, user_id=user_id, tenant_no=tenant_no, menu_path=path)
|
||
if deleted:
|
||
deleted.del_flag = 0
|
||
deleted.order_num = next_order
|
||
deleted.update_by = user_id
|
||
deleted.update_time = now
|
||
db.commit()
|
||
db.refresh(deleted)
|
||
return _row_to_dict(deleted)
|
||
|
||
row = SysUserMenuFavorite(
|
||
id=str(uuid.uuid4()),
|
||
user_id=user_id,
|
||
menu_path=path,
|
||
tenant_no=tenant_no,
|
||
order_num=next_order,
|
||
create_by=user_id,
|
||
create_dept_id=create_dept_id or "",
|
||
create_time=now,
|
||
update_by=user_id,
|
||
update_time=now,
|
||
del_flag=0,
|
||
)
|
||
db.add(row)
|
||
db.commit()
|
||
db.refresh(row)
|
||
return _row_to_dict(row)
|
||
|
||
|
||
def soft_delete_favorite(
|
||
db: Session,
|
||
*,
|
||
user_id: str,
|
||
tenant_no: str,
|
||
menu_path: str,
|
||
) -> bool:
|
||
path = normalize_menu_path(menu_path)
|
||
row = find_active(db, user_id=user_id, tenant_no=tenant_no, menu_path=path)
|
||
if not row:
|
||
return False
|
||
row.del_flag = 1
|
||
row.update_by = user_id
|
||
row.update_time = datetime.now()
|
||
db.commit()
|
||
return True
|
||
|
||
|
||
def reorder_favorites(
|
||
db: Session,
|
||
*,
|
||
user_id: str,
|
||
tenant_no: str,
|
||
paths: List[str],
|
||
) -> List[Dict[str, Any]]:
|
||
"""按传入 paths 顺序重写 order_num(1-based)。paths 须覆盖当前全部有效收藏。"""
|
||
normalized: List[str] = []
|
||
seen = set()
|
||
for raw in paths or []:
|
||
p = normalize_menu_path(raw)
|
||
if not p or p in seen:
|
||
continue
|
||
seen.add(p)
|
||
normalized.append(p)
|
||
|
||
current = list_favorites(db, user_id=user_id, tenant_no=tenant_no)
|
||
current_paths = {item["path"] for item in current}
|
||
if set(normalized) != current_paths or len(normalized) != len(current_paths):
|
||
raise ValueError("排序列表与当前收藏不一致")
|
||
|
||
now = datetime.now()
|
||
for idx, path in enumerate(normalized, start=1):
|
||
row = find_active(db, user_id=user_id, tenant_no=tenant_no, menu_path=path)
|
||
if not row:
|
||
raise ValueError("排序列表与当前收藏不一致")
|
||
row.order_num = idx
|
||
row.update_by = user_id
|
||
row.update_time = now
|
||
|
||
db.commit()
|
||
return list_favorites(db, user_id=user_id, tenant_no=tenant_no)
|