2026-03-04 14:01:00 -05:00
|
|
|
"""Routes M6 — Commander des jetons."""
|
|
|
|
|
from datetime import date, timedelta
|
|
|
|
|
from fastapi import APIRouter, Depends
|
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
from sqlalchemy import select, func
|
|
|
|
|
|
|
|
|
|
from app.core.database import get_db
|
2026-05-25 17:39:38 -04:00
|
|
|
from app.core.security import get_current_membre, is_sysadmin
|
2026-05-26 09:05:58 -04:00
|
|
|
from app.core.roles import membre_a_module
|
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
|
|
|
from app.models.vente_jeton import VenteJeton
|
2026-03-04 14:01:00 -05:00
|
|
|
from app.models.affectation import Affectation
|
|
|
|
|
from app.models.poste import Poste
|
|
|
|
|
from app.schemas.phase5 import StockJetonsOut, TYPES_JETONS
|
|
|
|
|
from fastapi import HTTPException
|
|
|
|
|
|
|
|
|
|
router = APIRouter(prefix="/commandes-jetons", tags=["M6 — Commander jetons"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def require_resp_jetons(membre=Depends(get_current_membre), db: AsyncSession = Depends(get_db)):
|
|
|
|
|
"""Vérifie que le membre est responsable des jetons."""
|
2026-05-26 09:05:58 -04:00
|
|
|
if is_sysadmin(membre) or await membre_a_module(membre.id, "M6", db):
|
2026-05-25 17:39:38 -04:00
|
|
|
return membre
|
2026-03-04 14:01:00 -05:00
|
|
|
result = await db.execute(
|
|
|
|
|
select(Affectation).join(Poste).where(
|
|
|
|
|
Affectation.membre_id == membre.id,
|
2026-06-01 06:35:09 -04:00
|
|
|
Affectation.groupe_id == membre.groupe_id,
|
2026-03-04 14:01:00 -05:00
|
|
|
Affectation.statut == "actif",
|
2026-06-01 06:35:09 -04:00
|
|
|
Poste.groupe_id == membre.groupe_id,
|
2026-03-04 14:01:00 -05:00
|
|
|
Poste.nom.ilike("%jeton%"),
|
|
|
|
|
)
|
|
|
|
|
)
|
2026-03-13 09:53:55 -04:00
|
|
|
if not result.scalars().first():
|
2026-03-04 14:01:00 -05:00
|
|
|
raise HTTPException(status_code=403, detail="Réservé au responsable des jetons")
|
|
|
|
|
return membre
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/stock", response_model=list[StockJetonsOut])
|
|
|
|
|
async def etat_stock_jetons(
|
|
|
|
|
membre=Depends(require_resp_jetons),
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
|
|
|
|
):
|
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
|
|
|
"""M6 — État du stock de jetons (basé sur les ventes, pas les remises)."""
|
2026-03-04 14:01:00 -05:00
|
|
|
il_y_a_30_jours = date.today() - timedelta(days=30)
|
|
|
|
|
|
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
|
|
|
# Compter les jetons vendus par type dans les 30 derniers jours.
|
|
|
|
|
# Les remises sont statistiques et ne décrémentent pas l'inventaire.
|
2026-03-04 14:01:00 -05:00
|
|
|
result = await db.execute(
|
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
|
|
|
select(VenteJeton.type_jeton, func.coalesce(func.sum(VenteJeton.quantite), 0))
|
2026-03-04 14:01:00 -05:00
|
|
|
.where(
|
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
|
|
|
VenteJeton.groupe_id == membre.groupe_id,
|
|
|
|
|
VenteJeton.date_vente >= il_y_a_30_jours,
|
2026-03-04 14:01:00 -05:00
|
|
|
)
|
Intégrer rencontres et assemblées pilotées par tuiles
Refond les pages Rencontres et Assemblées autour de tuiles ouvrables avec actions locales, PDF serveur, ventes, collectes, présences, décisions, postes et contributions.
Ajoute les invitations publiques d'assemblée en lecture seule avec lien expirant, l'adoption formelle du rapport du trésorier avec proposeur/secondeur, et bloque les contributions tant que ce rapport n'est pas adopté.
Uniformise la navigation et les entêtes, retire les modules redondants des contextes, ajoute la création de comptes membres par exécutif et les données démo reliées aux nouveaux processus.
Ce commit capture un lot fonctionnel important; une refactorisation ciblée du module Assemblées, puis Trésorerie/Gouvernance, est maintenant pertinente pour isoler les règles métier et faciliter les tests.
2026-05-31 09:02:02 -04:00
|
|
|
.group_by(VenteJeton.type_jeton)
|
2026-03-04 14:01:00 -05:00
|
|
|
)
|
|
|
|
|
usage = {row[0]: row[1] for row in result.all()}
|
|
|
|
|
|
|
|
|
|
stock = []
|
|
|
|
|
for t in TYPES_JETONS:
|
|
|
|
|
remis = usage.get(t["value"], 0)
|
|
|
|
|
# Suggestion simple : si plus de 2 remis dans le mois, suggérer commande
|
|
|
|
|
suggestion = "Bas — commander" if remis >= 2 else "OK"
|
|
|
|
|
# Le jeton de désir est toujours à surveiller (remis chaque semaine)
|
|
|
|
|
if t["value"] == "desir" and remis >= 4:
|
|
|
|
|
suggestion = "Bas — commander"
|
|
|
|
|
|
|
|
|
|
stock.append(StockJetonsOut(
|
|
|
|
|
type_jeton=t["value"],
|
|
|
|
|
label=f"{t['emoji']} {t['label']}",
|
|
|
|
|
remis_dernier_mois=remis,
|
|
|
|
|
stock_suggestion=suggestion,
|
|
|
|
|
))
|
|
|
|
|
return stock
|