"""Router API pour les objets d'inventaire.""" from decimal import Decimal from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy.ext.asyncio import AsyncSession from app.core.database import get_db from app.models.item import ItemStatus from app.repositories.category import CategoryRepository from app.repositories.item import ItemRepository from app.repositories.location import LocationRepository from app.schemas.common import PaginatedResponse, SuccessResponse from app.schemas.item import ( ItemCreate, ItemResponse, ItemUpdate, ItemWithRelations, ) router = APIRouter(prefix="/items", tags=["Items"]) @router.get("", response_model=PaginatedResponse[ItemWithRelations]) async def list_items( page: int = Query(default=1, ge=1), page_size: int = Query(default=20, ge=1, le=100), search: str | None = Query(default=None, min_length=2), category_id: int | None = None, location_id: int | None = None, status: ItemStatus | None = None, min_price: Decimal | None = None, max_price: Decimal | None = None, db: AsyncSession = Depends(get_db), ) -> PaginatedResponse[ItemWithRelations]: """Liste les objets avec filtres et pagination.""" repo = ItemRepository(db) skip = (page - 1) * page_size items = await repo.search( query=search or "", category_id=category_id, location_id=location_id, status=status, min_price=min_price, max_price=max_price, skip=skip, limit=page_size, ) total = await repo.count_filtered( query=search, category_id=category_id, location_id=location_id, status=status, min_price=min_price, max_price=max_price, ) result_items = [ItemWithRelations.model_validate(item) for item in items] return PaginatedResponse.create(items=result_items, total=total, page=page, page_size=page_size) @router.get("/{item_id}", response_model=ItemWithRelations) async def get_item( item_id: int, db: AsyncSession = Depends(get_db), ) -> ItemWithRelations: """Récupère un objet par son ID avec ses relations.""" repo = ItemRepository(db) item = await repo.get_with_relations(item_id) if item is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Objet {item_id} non trouvé", ) return ItemWithRelations.model_validate(item) @router.post("", response_model=ItemResponse, status_code=status.HTTP_201_CREATED) async def create_item( data: ItemCreate, db: AsyncSession = Depends(get_db), ) -> ItemResponse: """Crée un nouvel objet.""" item_repo = ItemRepository(db) category_repo = CategoryRepository(db) location_repo = LocationRepository(db) # Vérifier que la catégorie existe if not await category_repo.exists(data.category_id): raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Catégorie {data.category_id} non trouvée", ) # Vérifier que l'emplacement existe if not await location_repo.exists(data.location_id): raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Emplacement {data.location_id} non trouvé", ) # Vérifier l'unicité du numéro de série si fourni if data.serial_number: existing = await item_repo.get_by_serial_number(data.serial_number) if existing: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail=f"Un objet avec le numéro de série '{data.serial_number}' existe déjà", ) item = await item_repo.create( name=data.name, description=data.description, quantity=data.quantity, status=data.status, brand=data.brand, model=data.model, serial_number=data.serial_number, price=data.price, purchase_date=data.purchase_date, notes=data.notes, category_id=data.category_id, location_id=data.location_id, ) await db.commit() return ItemResponse.model_validate(item) @router.put("/{item_id}", response_model=ItemResponse) async def update_item( item_id: int, data: ItemUpdate, db: AsyncSession = Depends(get_db), ) -> ItemResponse: """Met à jour un objet.""" item_repo = ItemRepository(db) category_repo = CategoryRepository(db) location_repo = LocationRepository(db) # Vérifier que l'objet existe existing = await item_repo.get(item_id) if existing is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Objet {item_id} non trouvé", ) # Vérifier la catégorie si changée if data.category_id is not None and data.category_id != existing.category_id: if not await category_repo.exists(data.category_id): raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Catégorie {data.category_id} non trouvée", ) # Vérifier l'emplacement si changé if data.location_id is not None and data.location_id != existing.location_id: if not await location_repo.exists(data.location_id): raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Emplacement {data.location_id} non trouvé", ) # Vérifier l'unicité du numéro de série si changé if data.serial_number and data.serial_number != existing.serial_number: existing_with_serial = await item_repo.get_by_serial_number(data.serial_number) if existing_with_serial and existing_with_serial.id != item_id: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail=f"Un objet avec le numéro de série '{data.serial_number}' existe déjà", ) item = await item_repo.update( item_id, name=data.name, description=data.description, quantity=data.quantity, status=data.status, brand=data.brand, model=data.model, serial_number=data.serial_number, price=data.price, purchase_date=data.purchase_date, notes=data.notes, category_id=data.category_id, location_id=data.location_id, ) await db.commit() return ItemResponse.model_validate(item) @router.delete("/{item_id}", response_model=SuccessResponse) async def delete_item( item_id: int, db: AsyncSession = Depends(get_db), ) -> SuccessResponse: """Supprime un objet et ses documents associés.""" repo = ItemRepository(db) # Vérifier que l'objet existe if not await repo.exists(item_id): raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Objet {item_id} non trouvé", ) await repo.delete(item_id) await db.commit() return SuccessResponse(message="Objet supprimé avec succès", id=item_id) @router.patch("/{item_id}/status", response_model=ItemResponse) async def update_item_status( item_id: int, new_status: ItemStatus, db: AsyncSession = Depends(get_db), ) -> ItemResponse: """Met à jour le statut d'un objet.""" repo = ItemRepository(db) item = await repo.update(item_id, status=new_status) if item is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Objet {item_id} non trouvé", ) await db.commit() return ItemResponse.model_validate(item) @router.patch("/{item_id}/location", response_model=ItemResponse) async def move_item( item_id: int, new_location_id: int, db: AsyncSession = Depends(get_db), ) -> ItemResponse: """Déplace un objet vers un nouvel emplacement.""" item_repo = ItemRepository(db) location_repo = LocationRepository(db) # Vérifier que le nouvel emplacement existe if not await location_repo.exists(new_location_id): raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Emplacement {new_location_id} non trouvé", ) item = await item_repo.update(item_id, location_id=new_location_id) if item is None: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Objet {item_id} non trouvé", ) await db.commit() return ItemResponse.model_validate(item)