Files
home_stock/backend/app/schemas/item.py
2026-02-01 01:45:51 +01:00

159 lines
5.6 KiB
Python

"""Schémas Pydantic pour les objets d'inventaire.
Définit les schémas de validation pour les requêtes et réponses API.
"""
from datetime import date, datetime
from decimal import Decimal
from typing import Any
from pydantic import BaseModel, ConfigDict, Field, model_validator
from app.models.item import ItemStatus
from app.schemas.category import CategoryResponse
from app.schemas.location import LocationResponse
class ItemBase(BaseModel):
"""Schéma de base pour les objets."""
name: str = Field(..., min_length=1, max_length=200, description="Nom de l'objet")
description: str | None = Field(None, description="Description détaillée")
quantity: int = Field(default=1, ge=0, description="Quantité en stock")
status: ItemStatus = Field(default=ItemStatus.IN_STOCK, description="Statut de l'objet")
brand: str | None = Field(None, max_length=100, description="Marque")
model: str | None = Field(None, max_length=100, description="Modèle")
serial_number: str | None = Field(None, max_length=100, description="Numéro de série")
url: str | None = Field(None, max_length=500, description="Lien vers page produit")
price: Decimal | None = Field(None, ge=0, decimal_places=2, description="Prix d'achat")
purchase_date: date | None = Field(None, description="Date d'achat")
characteristics: dict[str, str] | None = Field(None, description="Caractéristiques techniques (clé-valeur)")
notes: str | None = Field(None, description="Notes libres")
class ItemCreate(ItemBase):
"""Schéma pour la création d'un objet."""
category_id: int = Field(..., description="ID de la catégorie")
location_id: int = Field(..., description="ID de l'emplacement")
parent_item_id: int | None = Field(None, description="ID de l'objet parent (si intégré)")
shop_id: int | None = Field(None, description="ID de la boutique d'achat")
class ItemUpdate(BaseModel):
"""Schéma pour la mise à jour d'un objet (tous les champs optionnels)."""
name: str | None = Field(None, min_length=1, max_length=200)
description: str | None = None
quantity: int | None = Field(None, ge=0)
status: ItemStatus | None = None
brand: str | None = Field(None, max_length=100)
model: str | None = Field(None, max_length=100)
serial_number: str | None = Field(None, max_length=100)
url: str | None = Field(None, max_length=500)
price: Decimal | None = Field(None, ge=0)
purchase_date: date | None = None
characteristics: dict[str, str] | None = None
notes: str | None = None
category_id: int | None = None
location_id: int | None = None
parent_item_id: int | None = None
shop_id: int | None = None
class ItemResponse(ItemBase):
"""Schéma de réponse pour un objet."""
model_config = ConfigDict(from_attributes=True)
id: int
category_id: int
location_id: int
parent_item_id: int | None = None
shop_id: int | None = None
created_at: datetime
updated_at: datetime
class ItemWithRelations(ItemResponse):
"""Schéma de réponse avec les relations (catégorie et emplacement)."""
category: CategoryResponse
location: LocationResponse
thumbnail_id: int | None = None
parent_item_name: str | None = None
@model_validator(mode="before")
@classmethod
def extract_computed_fields(cls, data: Any) -> Any:
"""Extrait les champs calculés : thumbnail et nom du parent."""
from sqlalchemy.orm import InstanceState
thumbnail_id = None
parent_item_name = None
# Vérifier que les relations sont chargées (éviter lazy load en async)
loaded_relations: set[str] = set()
if hasattr(data, "_sa_instance_state"):
state: InstanceState = data._sa_instance_state
loaded_relations = set(state.dict.keys())
if "documents" in loaded_relations:
for doc in data.documents:
if doc.type.value == "photo":
thumbnail_id = doc.id
break
if "parent_item" in loaded_relations and data.parent_item is not None:
parent_item_name = data.parent_item.name
if isinstance(data, dict):
if thumbnail_id:
data["thumbnail_id"] = thumbnail_id
if parent_item_name:
data["parent_item_name"] = parent_item_name
elif thumbnail_id or parent_item_name:
result = {}
for k in dir(data):
if k.startswith("_"):
continue
# Ne pas accéder aux relations non chargées
if k in ("documents", "parent_item", "children", "shop"):
continue
try:
result[k] = getattr(data, k)
except Exception:
pass
if thumbnail_id:
result["thumbnail_id"] = thumbnail_id
if parent_item_name:
result["parent_item_name"] = parent_item_name
return result
return data
class ItemSummary(BaseModel):
"""Schéma résumé pour les listes d'objets."""
model_config = ConfigDict(from_attributes=True)
id: int
name: str
quantity: int
status: ItemStatus
brand: str | None
category_id: int
location_id: int
class ItemFilter(BaseModel):
"""Schéma pour filtrer les objets."""
category_id: int | None = None
location_id: int | None = None
status: ItemStatus | None = None
search: str | None = Field(None, min_length=2, description="Recherche textuelle")
min_price: Decimal | None = None
max_price: Decimal | None = None