groupe-meditation/backend/app/routers/ventes_litterature.py
2026-05-25 17:39:38 -04:00

197 lines
6.6 KiB
Python

"""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.security import get_current_membre, is_sysadmin
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.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):
return membre
result = await db.execute(
select(Affectation).join(Poste).where(
Affectation.membre_id == membre.id,
Affectation.statut == "actif",
(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
# Créer la vente
vente = VenteLitterature(
litterature_id=req.litterature_id,
groupe_id=membre.groupe_id,
quantite=req.quantite,
montant=req.montant,
date_vente=req.date_vente,
vendu_par=membre.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=membre.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):
result = await db.execute(
select(Affectation).join(Poste).where(
Affectation.membre_id == membre.id,
Affectation.statut == "actif",
(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_remise,
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"}