"""Routes C4 / M5 — Littérature : inventaire, ajustement, gestion catalogue.""" from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select from app.core.database import get_db from app.core.security import get_current_membre, is_sysadmin from app.core.roles import membre_a_module from app.models.litterature import Litterature from app.models.affectation import Affectation from app.models.poste import Poste from app.schemas.phase6 import LitteratureCreate, LitteratureOut, LitteratureUpdate, AjustementStock router = APIRouter(prefix="/litterature", tags=["C4/M5 — Littérature"]) async def require_resp_litterature(membre=Depends(get_current_membre), db: AsyncSession = Depends(get_db)): """Vérifie que le membre est responsable de la littérature.""" if is_sysadmin(membre) or await membre_a_module(membre.id, "M5", db): return membre result = await db.execute( select(Affectation).join(Poste).where( Affectation.membre_id == membre.id, Affectation.statut == "actif", Poste.nom.ilike("%littérature%"), ) ) if not result.scalars().first(): raise HTTPException(status_code=403, detail="Réservé au responsable de la littérature") return membre def _to_out(item: Litterature) -> LitteratureOut: return LitteratureOut( id=item.id, titre=item.titre, categorie=item.categorie, prix_coutant=float(item.prix_coutant) if item.prix_coutant else None, quantite_stock=item.quantite_stock, seuil_commande=item.seuil_commande, code_bsg=item.code_bsg, stock_bas=item.quantite_stock <= item.seuil_commande, ) @router.get("/", response_model=list[LitteratureOut]) async def inventaire( membre=Depends(get_current_membre), db: AsyncSession = Depends(get_db), ): """C4 — Inventaire de la littérature du groupe avec prix.""" result = await db.execute( select(Litterature) .where(Litterature.groupe_id == membre.groupe_id) .order_by(Litterature.categorie, Litterature.titre) ) return [_to_out(item) for item in result.scalars().all()] @router.post("/", response_model=LitteratureOut) async def ajouter_titre( req: LitteratureCreate, membre=Depends(require_resp_litterature), db: AsyncSession = Depends(get_db), ): """Ajouter un titre au catalogue du groupe.""" categories_valides = ["livre", "brochure", "depliant", "pamphlet"] if req.categorie not in categories_valides: raise HTTPException(status_code=400, detail=f"Catégorie invalide. Valeurs: {categories_valides}") item = Litterature( groupe_id=membre.groupe_id, titre=req.titre, categorie=req.categorie, prix_coutant=req.prix_coutant, quantite_stock=req.quantite_stock, seuil_commande=req.seuil_commande, code_bsg=req.code_bsg, ) db.add(item) await db.flush() return _to_out(item) @router.patch("/{item_id}", response_model=LitteratureOut) async def modifier_titre( item_id: str, req: LitteratureUpdate, membre=Depends(require_resp_litterature), db: AsyncSession = Depends(get_db), ): """Modifier les infos d'un titre.""" result = await db.execute( select(Litterature).where(Litterature.id == item_id, Litterature.groupe_id == membre.groupe_id) ) item = result.scalar_one_or_none() if not item: raise HTTPException(status_code=404, detail="Titre introuvable") for field, value in req.model_dump(exclude_none=True).items(): setattr(item, field, value) await db.flush() return _to_out(item) @router.patch("/{item_id}/stock", response_model=LitteratureOut) async def ajuster_stock( item_id: str, req: AjustementStock, membre=Depends(require_resp_litterature), db: AsyncSession = Depends(get_db), ): """M5 — Ajuster le stock après réception d'une commande ou correction.""" result = await db.execute( select(Litterature).where(Litterature.id == item_id, Litterature.groupe_id == membre.groupe_id) ) item = result.scalar_one_or_none() if not item: raise HTTPException(status_code=404, detail="Titre introuvable") nouveau_stock = item.quantite_stock + req.quantite if nouveau_stock < 0: raise HTTPException(status_code=400, detail="Le stock ne peut pas être négatif") item.quantite_stock = nouveau_stock await db.flush() return _to_out(item)