import ali

This commit is contained in:
2026-02-01 01:45:51 +01:00
parent bdbfa4e25a
commit 46d6d88ce5
48 changed files with 6714 additions and 185 deletions
+29 -22
View File
@@ -3,7 +3,7 @@
from decimal import Decimal
from typing import Any
from sqlalchemy import or_, select
from sqlalchemy import or_, select, text
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
@@ -33,6 +33,7 @@ class ItemRepository(BaseRepository[Item]):
selectinload(Item.category),
selectinload(Item.location),
selectinload(Item.documents),
selectinload(Item.parent_item),
)
.where(Item.id == id)
)
@@ -55,6 +56,8 @@ class ItemRepository(BaseRepository[Item]):
.options(
selectinload(Item.category),
selectinload(Item.location),
selectinload(Item.documents),
selectinload(Item.parent_item),
)
.offset(skip)
.limit(limit)
@@ -91,20 +94,24 @@ class ItemRepository(BaseRepository[Item]):
stmt = select(Item).options(
selectinload(Item.category),
selectinload(Item.location),
selectinload(Item.documents),
selectinload(Item.parent_item),
)
# Recherche textuelle
# Recherche full-text via FTS5
if query:
search_term = f"%{query}%"
stmt = stmt.where(
or_(
Item.name.ilike(search_term),
Item.description.ilike(search_term),
Item.brand.ilike(search_term),
Item.model.ilike(search_term),
Item.notes.ilike(search_term),
)
)
# Échapper les caractères spéciaux FTS5 et ajouter le préfixe *
safe_query = query.replace('"', '""').strip()
if safe_query:
# Recherche par préfixe pour résultats en temps réel
fts_terms = " ".join(f'"{word}"*' for word in safe_query.split() if word)
stmt = stmt.where(
Item.id.in_(
select(text("rowid")).select_from(text("fts_items")).where(
text("fts_items MATCH :fts_query")
)
)
).params(fts_query=fts_terms)
# Filtres
if category_id is not None:
@@ -141,16 +148,16 @@ class ItemRepository(BaseRepository[Item]):
stmt = select(func.count(Item.id))
if query:
search_term = f"%{query}%"
stmt = stmt.where(
or_(
Item.name.ilike(search_term),
Item.description.ilike(search_term),
Item.brand.ilike(search_term),
Item.model.ilike(search_term),
Item.notes.ilike(search_term),
)
)
safe_query = query.replace('"', '""').strip()
if safe_query:
fts_terms = " ".join(f'"{word}"*' for word in safe_query.split() if word)
stmt = stmt.where(
Item.id.in_(
select(text("rowid")).select_from(text("fts_items")).where(
text("fts_items MATCH :fts_query")
)
)
).params(fts_query=fts_terms)
if category_id is not None:
stmt = stmt.where(Item.category_id == category_id)
+50
View File
@@ -0,0 +1,50 @@
"""Repository pour les boutiques."""
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from app.models.shop import Shop
from app.repositories.base import BaseRepository
class ShopRepository(BaseRepository[Shop]):
"""Repository pour les opérations sur les boutiques."""
def __init__(self, db: AsyncSession) -> None:
super().__init__(Shop, db)
async def get_by_name(self, name: str) -> Shop | None:
result = await self.db.execute(
select(Shop).where(Shop.name == name)
)
return result.scalar_one_or_none()
async def get_with_item_count(self, id: int) -> tuple[Shop, int] | None:
result = await self.db.execute(
select(Shop).options(selectinload(Shop.items)).where(Shop.id == id)
)
shop = result.scalar_one_or_none()
if shop is None:
return None
return shop, len(shop.items)
async def get_all_with_item_count(
self, skip: int = 0, limit: int = 100
) -> list[tuple[Shop, int]]:
result = await self.db.execute(
select(Shop)
.options(selectinload(Shop.items))
.offset(skip)
.limit(limit)
.order_by(Shop.name)
)
shops = result.scalars().all()
return [(shop, len(shop.items)) for shop in shops]
async def name_exists(self, name: str, exclude_id: int | None = None) -> bool:
query = select(func.count(Shop.id)).where(Shop.name == name)
if exclude_id is not None:
query = query.where(Shop.id != exclude_id)
result = await self.db.execute(query)
return result.scalar_one() > 0