"""用户菜单收藏 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)