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()