from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from typing import List, Optional import uuid import re from database import get_db from models.category import Category from models.product import Product from models.user import User from schemas.category import CategoryResponse, CategoryCreate, CategoryUpdate, CategoryTreeResponse from routers.auth import get_current_user, is_admin_user router = APIRouter() def generate_level_code() -> str: """生成6位字母数字组合编码""" # 使用UUID生成并截取前6位,确保包含字母和数字 while True: code = uuid.uuid4().hex[:6].lower() # 检查是否同时包含字母和数字 if re.search(r'[a-z]', code) and re.search(r'[0-9]', code): return code def get_category_level_by_code(db: Session, category_code: str, company_code: str) -> int: """通过编码获取分类的层级,0为顶级""" if not category_code: return 0 # 通过下划线数量判断层级 return category_code.count('_') def build_category_code(db: Session, parent_code: str, company_code: str) -> str: """构建级联编码""" if not parent_code: # 顶级分类,直接生成编码 return generate_level_code() parent = db.query(Category).filter( Category.code == parent_code, Category.company_code == company_code ).first() if not parent: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Parent category not found" ) # 生成当前层级编码,拼接父级编码 level_code = generate_level_code() return f"{parent_code}_{level_code}" def build_category_tree(categories: List[Category], parent_code: str = "") -> List[CategoryTreeResponse]: """构建分类树形结构""" tree = [] for cat in categories: if cat.parent_code == parent_code: children = build_category_tree(categories, cat.code) tree.append(CategoryTreeResponse( **cat.__dict__, children=children )) return tree @router.get("/", response_model=List[CategoryResponse]) async def get_categories( parent_code: Optional[str] = None, current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """获取分类列表""" query = db.query(Category) if not is_admin_user(current_user): query = query.filter(Category.company_code == current_user.company_code) if parent_code is not None: query = query.filter(Category.parent_code == parent_code) categories = query.order_by(Category.sort_order, Category.id).all() # 计算每个分类的产品数量 result = [] for category in categories: product_count = db.query(Product).filter( Product.category_id == category.id ) if not is_admin_user(current_user): product_count = product_count.filter(Product.company_code == current_user.company_code) result.append(CategoryResponse( **category.__dict__, product_count=product_count.count() )) return result @router.get("/tree", response_model=List[CategoryTreeResponse]) async def get_category_tree( current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """获取分类树形结构""" query = db.query(Category) if not is_admin_user(current_user): query = query.filter(Category.company_code == current_user.company_code) categories = query.order_by(Category.sort_order, Category.id).all() return build_category_tree(categories, "") @router.get("/{category_id}", response_model=CategoryResponse) async def get_category( category_id: int, current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """获取单个分类详情""" query = db.query(Category).filter(Category.id == category_id) if not is_admin_user(current_user): query = query.filter(Category.company_code == current_user.company_code) category = query.first() if not category: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Category not found" ) # 计算产品数量 product_count = db.query(Product).filter(Product.category_id == category.id) if not is_admin_user(current_user): product_count = product_count.filter(Product.company_code == current_user.company_code) return CategoryResponse( **category.__dict__, product_count=product_count.count() ) @router.post("/", response_model=CategoryResponse, status_code=status.HTTP_201_CREATED) async def create_category( category: CategoryCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """创建新分类""" # 检查层级限制(最多三级) level = get_category_level_by_code(db, category.parent_code, current_user.company_code) if level >= 3: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Maximum category level is 3" ) # 检查同级分类名称是否已存在 existing = db.query(Category).filter( Category.name == category.name, Category.parent_code == category.parent_code, Category.company_code == current_user.company_code ).first() if existing: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Category name already exists in the same level" ) # 构建编码 code = build_category_code(db, category.parent_code, current_user.company_code) # 创建分类 category_data = category.model_dump() if not is_admin_user(current_user): category_data['company_code'] = current_user.company_code category_data['code'] = code db_category = Category(**category_data) db.add(db_category) db.commit() db.refresh(db_category) return CategoryResponse( **db_category.__dict__, product_count=0 ) @router.put("/{category_id}", response_model=CategoryResponse) async def update_category( category_id: int, category_update: CategoryUpdate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """更新分类""" query = db.query(Category).filter(Category.id == category_id) if not is_admin_user(current_user): query = query.filter(Category.company_code == current_user.company_code) db_category = query.first() if not db_category: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Category not found" ) update_data = category_update.model_dump(exclude_unset=True) # 检查是否需要更新父分类 new_parent_code = update_data.get('parent_code', db_category.parent_code) if new_parent_code != db_category.parent_code: # 检查层级限制(最多三级) level = get_category_level_by_code(db, new_parent_code, current_user.company_code) if level >= 3: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Maximum category level is 3" ) # 检查是否有子分类,如果有则不能改变父分类 child_count = db.query(Category).filter( Category.parent_code == db_category.code, Category.company_code == current_user.company_code ).count() if child_count > 0: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot change parent category of category with children" ) # 重新构建编码 new_code = build_category_code(db, new_parent_code, current_user.company_code) update_data['code'] = new_code # 检查新名称是否与同级分类冲突 new_name = update_data.get('name', db_category.name) if new_name != db_category.name or new_parent_code != db_category.parent_code: existing = db.query(Category).filter( Category.name == new_name, Category.parent_code == new_parent_code, Category.company_code == current_user.company_code, Category.id != category_id ).first() if existing: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Category name already exists in the same level" ) # 更新分类 for field, value in update_data.items(): setattr(db_category, field, value) db.commit() db.refresh(db_category) # 计算产品数量 product_count = db.query(Product).filter(Product.category_id == db_category.id) if not is_admin_user(current_user): product_count = product_count.filter(Product.company_code == current_user.company_code) return CategoryResponse( **db_category.__dict__, product_count=product_count.count() ) @router.delete("/{category_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_category( category_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): """删除分类""" query = db.query(Category).filter(Category.id == category_id) if not is_admin_user(current_user): query = query.filter(Category.company_code == current_user.company_code) db_category = query.first() if not db_category: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Category not found" ) # 检查是否有子分类 child_count = db.query(Category).filter( Category.parent_code == db_category.code, Category.company_code == current_user.company_code ).count() if child_count > 0: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Cannot delete category with {child_count} child categories" ) # 检查是否有产品属于该分类 product_count = db.query(Product).filter( Product.category_id == category_id, Product.company_code == current_user.company_code ).count() if product_count > 0: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Cannot delete category with {product_count} products" ) db.delete(db_category) db.commit() return None