> import uuid > from fastapi import HTTPException, status > from sqlalchemy import func, select > from sqlalchemy.ext.asyncio import AsyncSession > from app.models.category import Category, CategoryType > from app.models.transaction import Transaction > from app.schemas.category import CategoryCreate, CategoryUpdate > from app.utils import utcnow # Default categories created at registration > _DEFAULT_CATEGORIES = [ > {"name": "Alimentation", "type": CategoryType.expense, "color": "#22c55e", "icon": "utensils"}, > {"name": "Transport", "type": CategoryType.expense, "color": "#3b82f6", "icon": "car"}, > {"name": "Logement", "type": CategoryType.expense, "color": "#f59e0b", "icon": "home"}, > {"name": "Santé", "type": CategoryType.expense, "color": "#ef4444", "icon": "heart-pulse"}, > {"name": "Loisirs", "type": CategoryType.expense, "color": "#a855f7", "icon": "gamepad-2"}, > {"name": "Divers", "type": CategoryType.expense, "color": "#6b7280", "icon": "package"}, > {"name": "Salaire", "type": CategoryType.income, "color": "#10b981", "icon": "briefcase"}, > {"name": "Freelance", "type": CategoryType.income, "color": "#06b6d4", "icon": "laptop"}, > {"name": "Remboursement", "type": CategoryType.income, "color": "#8b5cf6", "icon": "refresh-cw"}, > ] > async def create_default_categories( > session: AsyncSession, user_id: uuid.UUID > ) -> list[Category]: > """Create the default categories for a newly registered user.""" > categories = [] > for data in _DEFAULT_CATEGORIES: > cat = Category(user_id=user_id, is_default=True, **data) > session.add(cat) > categories.append(cat) > await session.flush() ! return categories > async def list_categories( > session: AsyncSession, > user_id: uuid.UUID, > *, > type_filter: CategoryType | None = None, > ) -> list[Category]: > query = select(Category).where( > Category.user_id == user_id, > Category.deleted_at.is_(None), > ) > if type_filter is not None: > query = query.where(Category.type == type_filter) > query = query.order_by(Category.name) > result = await session.execute(query) ! return list(result.scalars().all()) > async def get_category( > session: AsyncSession, > user_id: uuid.UUID, > category_id: uuid.UUID, > ) -> Category: > result = await session.execute( > select(Category).where( > Category.id == category_id, > Category.user_id == user_id, > Category.deleted_at.is_(None), > ) > ) ! cat = result.scalar_one_or_none() ! if cat is None: ! raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Category not found") ! return cat > async def create_category( > session: AsyncSession, > user_id: uuid.UUID, > data: CategoryCreate, > ) -> Category: > cat = Category( > user_id=user_id, > name=data.name, > type=data.type, > color=data.color, > icon=data.icon, > is_default=False, > ) > session.add(cat) > await session.commit() ! await session.refresh(cat) ! return cat > async def update_category( > session: AsyncSession, > user_id: uuid.UUID, > category_id: uuid.UUID, > data: CategoryUpdate, > ) -> Category: > cat = await get_category(session, user_id, category_id) ! if data.name is not None: ! cat.name = data.name ! if data.color is not None: ! cat.color = data.color ! if data.icon is not None: ! cat.icon = data.icon ! await session.commit() ! await session.refresh(cat) ! return cat > async def delete_category( > session: AsyncSession, > user_id: uuid.UUID, > category_id: uuid.UUID, > ) -> None: > cat = await get_category(session, user_id, category_id) # Refuse deletion if active transactions exist ! count_result = await session.execute( ! select(func.count()).where( ! Transaction.category_id == category_id, ! Transaction.user_id == user_id, ! Transaction.deleted_at.is_(None), ! ) ! ) ! active_count = count_result.scalar_one() ! if active_count > 0: ! raise HTTPException( ! status_code=status.HTTP_409_CONFLICT, ! detail=f"Cannot delete category: {active_count} active transaction(s) are linked to it", ! ) ! cat.deleted_at = utcnow() ! await session.commit()