"""Routes S2 — Remise de jeton.""" 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.jeton import Jeton from app.models.vente_jeton import VenteJeton 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.phase5 import JetonCreate, JetonOut, TYPES_JETONS, VenteJetonCreate router = APIRouter(prefix="/jetons", tags=["S2 — Remise de jeton"]) async def require_resp_jetons(membre=Depends(get_current_membre), db: AsyncSession = Depends(get_db)): """Vérifie que le membre est responsable des jetons.""" if is_sysadmin(membre) or await membre_a_module(membre.id, "S2", 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("%jeton%") | (Poste.categorie == "executif")), ) ) if not result.scalars().first(): raise HTTPException(status_code=403, detail="Réservé au responsable des jetons") return membre @router.get("/types") async def types_jetons(): """Liste des types de jetons disponibles.""" return TYPES_JETONS @router.post("/", response_model=JetonOut) async def remettre_jeton( req: JetonCreate, membre=Depends(require_resp_jetons), db: AsyncSession = Depends(get_db), ): """S2 — Enregistrer la remise d'un jeton.""" # Validation types_valides = [t["value"] for t in TYPES_JETONS] if req.type_jeton not in types_valides: raise HTTPException(status_code=400, detail=f"Type de jeton invalide. Valeurs: {types_valides}") if req.type_jeton == "multiple" and (not req.annees or req.annees < 2): raise HTTPException(status_code=400, detail="Le nombre d'années est requis (≥ 2) pour un jeton multiple") # Vérifier que le membre existe si spécifié membre_prenom = None if req.membre_id: cible = await db.get(Membre, req.membre_id) if not cible or cible.groupe_id != membre.groupe_id or not cible.actif: raise HTTPException(status_code=404, detail="Membre introuvable") membre_prenom = cible.prenom if req.reunion_id: await ensure_owned_or_404(db, Reunion, req.reunion_id, membre.groupe_id, "Réunion introuvable") jeton = Jeton( groupe_id=membre.groupe_id, reunion_id=req.reunion_id, membre_id=req.membre_id, type_jeton=req.type_jeton, date_remise=req.date_remise, annees=req.annees if req.type_jeton == "multiple" else None, remis_par=membre.id, ) db.add(jeton) await db.flush() return JetonOut( id=jeton.id, membre_prenom=membre_prenom, type_jeton=jeton.type_jeton, date_remise=jeton.date_remise, annees=jeton.annees, remis_par_prenom=membre.prenom, cree_le=jeton.cree_le, ) @router.post("/ventes") async def enregistrer_vente_jeton( req: VenteJetonCreate, membre=Depends(require_resp_jetons), db: AsyncSession = Depends(get_db), ): """Enregistrer une vente de jetons. Les remises ne touchent pas l'inventaire.""" types_valides = [t["value"] for t in TYPES_JETONS] if req.type_jeton not in types_valides: raise HTTPException(status_code=400, detail=f"Type de jeton invalide. Valeurs: {types_valides}") if req.quantite <= 0: raise HTTPException(status_code=400, detail="La quantité doit être positive") if req.montant < 0: raise HTTPException(status_code=400, detail="Le montant ne peut pas être négatif") 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") vente = VenteJeton( groupe_id=membre.groupe_id, reunion_id=req.reunion_id, type_jeton=req.type_jeton, quantite=req.quantite, montant=req.montant, date_vente=req.date_vente, vendu_par=vendeur_id, ) db.add(vente) await db.flush() return { "id": str(vente.id), "type_jeton": vente.type_jeton, "quantite": vente.quantite, "montant": float(vente.montant), "date_vente": str(vente.date_vente), "vendu_par_prenom": vendeur.prenom, "remis_tresorier": vente.remis_tresorier, } @router.get("/ventes/a-recevoir") async def ventes_jetons_a_recevoir( membre=Depends(get_current_membre), db: AsyncSession = Depends(get_db), ): result = await db.execute( select(VenteJeton) .where(VenteJeton.groupe_id == membre.groupe_id, VenteJeton.remis_tresorier == False) .order_by(desc(VenteJeton.date_vente)) ) out = [] for vente in result.scalars().all(): vendeur = await db.get(Membre, vente.vendu_par) if vente.vendu_par else None out.append({ "id": str(vente.id), "type_jeton": vente.type_jeton, "quantite": vente.quantite, "montant": float(vente.montant), "date_vente": str(vente.date_vente), "vendu_par_prenom": vendeur.prenom if vendeur else None, }) return out @router.post("/ventes/{vente_id}/recevoir") async def confirmer_reception_vente_jeton( vente_id: str, membre=Depends(get_current_membre), db: AsyncSession = Depends(get_db), ): 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") vente = await db.get(VenteJeton, vente_id) if not vente or vente.groupe_id != membre.groupe_id: 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_jeton", compte="encaisse", montant=vente.montant, sens="debit", source_table="ventes_jetons", source_id=vente.id, note="Vente de jetons reçue", cree_par=membre.id, )) await db.flush() return {"message": "Réception confirmée"} @router.get("/", response_model=list[JetonOut]) async def liste_jetons( limite: int = 30, membre=Depends(get_current_membre), db: AsyncSession = Depends(get_db), ): """Lister les jetons remis récemment (tout le groupe).""" result = await db.execute( select(Jeton) .where(Jeton.groupe_id == membre.groupe_id) .order_by(desc(Jeton.date_remise)) .limit(limite) ) jetons = result.scalars().all() out = [] for j in jetons: m = await db.get(Membre, j.membre_id) if j.membre_id else None r = await db.get(Membre, j.remis_par) if j.remis_par else None out.append(JetonOut( id=j.id, membre_prenom=m.prenom if m else None, type_jeton=j.type_jeton, date_remise=j.date_remise, annees=j.annees, remis_par_prenom=r.prenom if r else None, cree_le=j.cree_le, )) return out