"""Repository pour les emplacements.""" from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload from app.models.location import Location, LocationType from app.repositories.base import BaseRepository class LocationRepository(BaseRepository[Location]): """Repository pour les opérations sur les emplacements.""" def __init__(self, db: AsyncSession) -> None: """Initialise le repository.""" super().__init__(Location, db) async def get_with_children(self, id: int) -> Location | None: """Récupère un emplacement avec ses enfants. Args: id: ID de l'emplacement Returns: L'emplacement avec ses enfants ou None """ result = await self.db.execute( select(Location) .options(selectinload(Location.children)) .where(Location.id == id) ) return result.scalar_one_or_none() async def get_root_locations(self) -> list[Location]: """Récupère tous les emplacements racine (sans parent). Returns: Liste des emplacements racine """ result = await self.db.execute( select(Location) .where(Location.parent_id.is_(None)) .order_by(Location.name) ) return list(result.scalars().all()) async def get_children(self, parent_id: int) -> list[Location]: """Récupère les enfants directs d'un emplacement. Args: parent_id: ID du parent Returns: Liste des enfants """ result = await self.db.execute( select(Location) .where(Location.parent_id == parent_id) .order_by(Location.name) ) return list(result.scalars().all()) async def get_by_type(self, type: LocationType) -> list[Location]: """Récupère tous les emplacements d'un type donné. Args: type: Type d'emplacement Returns: Liste des emplacements """ result = await self.db.execute( select(Location) .where(Location.type == type) .order_by(Location.path) ) return list(result.scalars().all()) async def get_full_tree(self) -> list[Location]: """Récupère l'arborescence complète des emplacements. Returns: Liste des emplacements racine avec enfants chargés récursivement """ # Charger tous les emplacements avec leurs enfants result = await self.db.execute( select(Location) .options(selectinload(Location.children)) .order_by(Location.path) ) all_locations = list(result.scalars().all()) # Retourner seulement les racines (les enfants sont déjà chargés) return [loc for loc in all_locations if loc.parent_id is None] async def get_with_item_count(self, id: int) -> tuple[Location, int] | None: """Récupère un emplacement avec le nombre d'objets. Args: id: ID de l'emplacement Returns: Tuple (emplacement, nombre d'objets) ou None """ result = await self.db.execute( select(Location) .options(selectinload(Location.items)) .where(Location.id == id) ) location = result.scalar_one_or_none() if location is None: return None return location, len(location.items) async def create_with_path( self, name: str, type: LocationType, parent_id: int | None = None, description: str | None = None, ) -> Location: """Crée un emplacement avec calcul automatique du chemin. Args: name: Nom de l'emplacement type: Type d'emplacement parent_id: ID du parent (None si racine) description: Description optionnelle Returns: L'emplacement créé """ # Calculer le chemin if parent_id is None: path = name else: parent = await self.get(parent_id) if parent is None: path = name else: path = f"{parent.path} > {name}" return await self.create( name=name, type=type, parent_id=parent_id, path=path, description=description, ) async def update_paths_recursive(self, location: Location) -> None: """Met à jour récursivement les chemins après modification. Args: location: Emplacement modifié """ # Mettre à jour le chemin de cet emplacement if location.parent_id is None: location.path = location.name else: parent = await self.get(location.parent_id) if parent: location.path = f"{parent.path} > {location.name}" else: location.path = location.name # Mettre à jour les enfants children = await self.get_children(location.id) for child in children: child.path = f"{location.path} > {child.name}" await self.update_paths_recursive(child)