"""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