generated from gilles/template-webapp
claude code
This commit is contained in:
264
backend/app/routers/items.py
Normal file
264
backend/app/routers/items.py
Normal file
@@ -0,0 +1,264 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user