"""Routes S3 — Vente de littérature.""" from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, desc from app.core.database import get_db from app.core.ownership import ensure_owned_or_404 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.vente_litterature import VenteLitterature from app.models.membre import Membre from app.models.affectation import Affectation from app.models.poste import Poste from app.models.transaction_comptable import TransactionComptable from app.models.reunion import Reunion from app.schemas.phase6 import VenteCreate, VenteOut router = APIRouter(prefix="/ventes-litterature", tags=["S3 — Vente 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, "S3", db) 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.groupe_id == membre.groupe_id, Affectation.statut == "actif", Poste.groupe_id == membre.groupe_id, (Poste.nom.ilike("%littérature%") | (Poste.categorie == "executif")), ) ) if not result.scalars().first(): raise HTTPException(status_code=403, detail="Réservé au responsable de la littérature") return membre @router.post("/", response_model=VenteOut) async def enregistrer_vente( req: VenteCreate, membre=Depends(require_resp_litterature), db: AsyncSession = Depends(get_db), ): """S3 — Enregistrer une vente de littérature. Le stock est décrémenté automatiquement.""" # Vérifier le titre result = await db.execute( select(Litterature).where( Litterature.id == req.litterature_id, Litterature.groupe_id == membre.groupe_id, ) ) item = result.scalar_one_or_none() if not item: raise HTTPException(status_code=404, detail="Titre introuvable") if req.quantite <= 0: raise HTTPException(status_code=400, detail="La quantité doit être positive") if item.quantite_stock < req.quantite: raise HTTPException( status_code=400, detail=f"Stock insuffisant ({item.quantite_stock} disponible, {req.quantite} demandé)" ) # Décrémenter le stock item.quantite_stock -= req.quantite vendeur_id = req.vendu_par_id or membre.id vendeur = await db.get(Membre, vendeur_id) if not vendeur or vendeur.groupe_id != membre.groupe_id or not vendeur.actif: raise HTTPException(status_code=404, detail="Vendeur introuvable") if req.reunion_id: await ensure_owned_or_404(db, Reunion, req.reunion_id, membre.groupe_id, "Réunion introuvable") # Créer la vente vente = VenteLitterature( litterature_id=req.litterature_id, groupe_id=membre.groupe_id, reunion_id=req.reunion_id, quantite=req.quantite, montant=req.montant, date_vente=req.date_vente, vendu_par=vendeur_id, ) db.add(vente) await db.flush() return VenteOut( id=vente.id, litterature_titre=item.titre, quantite=vente.quantite, montant=float(vente.montant), date_vente=vente.date_vente, vendu_par_prenom=vendeur.prenom, cree_le=vente.cree_le, ) @router.get("/", response_model=list[VenteOut]) async def liste_ventes( limite: int = 20, membre=Depends(get_current_membre), db: AsyncSession = Depends(get_db), ): """Lister les ventes récentes.""" result = await db.execute( select(VenteLitterature) .where(VenteLitterature.groupe_id == membre.groupe_id) .order_by(desc(VenteLitterature.date_vente)) .limit(limite) ) ventes = result.scalars().all() out = [] for v in ventes: item = await db.get(Litterature, v.litterature_id) vendeur = await db.get(Membre, v.vendu_par) if v.vendu_par else None out.append(VenteOut( id=v.id, litterature_titre=item.titre if item else "?", quantite=v.quantite, montant=float(v.montant), date_vente=v.date_vente, vendu_par_prenom=vendeur.prenom if vendeur else None, remis_tresorier=v.remis_tresorier, cree_le=v.cree_le, )) return out @router.get("/a-recevoir") async def ventes_a_recevoir( membre=Depends(get_current_membre), db: AsyncSession = Depends(get_db), ): """Ventes dont le montant n'a pas encore été remis au trésorier.""" result = await db.execute( select(VenteLitterature) .where( VenteLitterature.groupe_id == membre.groupe_id, VenteLitterature.remis_tresorier == False, ) .order_by(desc(VenteLitterature.date_vente)) ) ventes = result.scalars().all() out = [] for v in ventes: item = await db.get(Litterature, v.litterature_id) vendeur = await db.get(Membre, v.vendu_par) if v.vendu_par else None out.append({ "id": str(v.id), "litterature_titre": item.titre if item else "?", "quantite": v.quantite, "montant": float(v.montant), "date_vente": str(v.date_vente), "vendu_par_prenom": vendeur.prenom if vendeur else None, }) return out @router.post("/{vente_id}/recevoir") async def confirmer_reception_vente( vente_id: str, membre=Depends(get_current_membre), db: AsyncSession = Depends(get_db), ): """Le trésorier confirme la réception du montant de la vente.""" # Vérifier que c'est le trésorier ou exécutif if not is_sysadmin(membre) and not await membre_a_module(membre.id, "S1T", db): result = await db.execute( select(Affectation).join(Poste).where( Affectation.membre_id == membre.id, Affectation.groupe_id == membre.groupe_id, Affectation.statut == "actif", Poste.groupe_id == membre.groupe_id, (Poste.nom.ilike("%trésorier%") | (Poste.categorie == "executif")), ) ) if not result.scalars().first(): raise HTTPException(status_code=403, detail="Réservé au trésorier ou à l'exécutif") result = await db.execute( select(VenteLitterature).where( VenteLitterature.id == vente_id, VenteLitterature.groupe_id == membre.groupe_id, ) ) vente = result.scalar_one_or_none() if not vente: raise HTTPException(status_code=404, detail="Vente introuvable") if vente.remis_tresorier: raise HTTPException(status_code=400, detail="Déjà reçue") from datetime import date as date_type vente.remis_tresorier = True vente.recu_par = membre.id vente.date_remise = date_type.today() db.add(TransactionComptable( groupe_id=vente.groupe_id, date_transaction=vente.date_vente, type="vente_litterature", compte="encaisse", montant=vente.montant, sens="debit", source_table="ventes_litterature", source_id=vente.id, note="Vente de littérature reçue", cree_par=membre.id, )) await db.flush() return {"message": "Réception confirmée"}