groupe-meditation/backend/app/routers/litterature.py
2026-03-13 09:53:55 -04:00

124 lines
4.3 KiB
Python

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