"""Routes M2 — Présences en réunion d'affaires.""" from datetime import date as dt_date from calendar import monthrange from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, desc, extract from app.core.database import get_db from app.core.security import get_current_membre from app.models.reunion import Reunion from app.models.groupe import Groupe from app.models.presence import Presence from app.models.membre import Membre from app.models.affectation import Affectation from app.models.poste import Poste from app.schemas.phase4 import ( PresencesBatch, PresenceOut, ReunionAffairesCreate, ReunionAffairesOut, ) router = APIRouter(prefix="/presences", tags=["M2 — Présences affaires"]) JOURS_SEMAINE = { "lundi": 0, "mardi": 1, "mercredi": 2, "jeudi": 3, "vendredi": 4, "samedi": 5, "dimanche": 6, } def _premier_jour_du_mois(annee: int, mois: int, jour_cible: int) -> dt_date: """Retourne la date du premier 'jour_cible' (0=lundi) du mois.""" premier = dt_date(annee, mois, 1) diff = (jour_cible - premier.weekday()) % 7 return dt_date(annee, mois, 1 + diff) async def require_secretaire(membre=Depends(get_current_membre), db=Depends(get_db)): """Vérifie que le membre est secrétaire ou exécutif.""" result = await db.execute( select(Affectation).join(Poste).where( Affectation.membre_id == membre.id, Affectation.statut == "actif", (Poste.nom.ilike("%secrétaire%") | (Poste.categorie == "executif")), ) ) if not result.scalars().first(): raise HTTPException(status_code=403, detail="Réservé au secrétaire ou à l'exécutif") return membre @router.post("/reunion", response_model=ReunionAffairesOut) async def creer_reunion_affaires( req: ReunionAffairesCreate, membre=Depends(require_secretaire), db: AsyncSession = Depends(get_db), ): """Créer une réunion d'affaires (point de départ pour présences et PV).""" groupe = await db.get(Groupe, membre.groupe_id) # ── Validation : premier jour de réunion du mois, une seule par mois ── dt = req.date_reunion if groupe.jour_reunion: jour_attendu = JOURS_SEMAINE.get(groupe.jour_reunion.lower()) if jour_attendu is not None: premier = _premier_jour_du_mois(dt.year, dt.month, jour_attendu) if dt.date() != premier: raise HTTPException( status_code=400, detail=f"La réunion d'affaires doit avoir lieu le premier {groupe.jour_reunion} du mois ({premier.isoformat()})", ) # Une seule réunion d'affaires par mois result = await db.execute( select(Reunion).where( Reunion.groupe_id == membre.groupe_id, Reunion.type == "affaires", extract("year", Reunion.date_reunion) == dt.year, extract("month", Reunion.date_reunion) == dt.month, ) ) if result.scalars().first(): raise HTTPException( status_code=400, detail=f"Il y a déjà une réunion d'affaires pour {dt.strftime('%B %Y')}", ) reunion = Reunion( groupe_id=membre.groupe_id, type="affaires", date_reunion=req.date_reunion, note=req.note, ) db.add(reunion) await db.flush() return ReunionAffairesOut(id=reunion.id, date_reunion=reunion.date_reunion, note=reunion.note) @router.get("/reunions", response_model=list[ReunionAffairesOut]) async def liste_reunions_affaires( limite: int = 12, membre=Depends(get_current_membre), db: AsyncSession = Depends(get_db), ): """Lister les réunions d'affaires récentes avec leurs présences.""" result = await db.execute( select(Reunion).where( Reunion.groupe_id == membre.groupe_id, Reunion.type == "affaires", ).order_by(desc(Reunion.date_reunion)).limit(limite) ) reunions = result.scalars().all() out = [] for r in reunions: pres_result = await db.execute( select(Presence).where(Presence.reunion_id == r.id) ) presences = pres_result.scalars().all() pres_out = [] for p in presences: mbr = await db.get(Membre, p.membre_id) pres_out.append(PresenceOut( membre_id=p.membre_id, membre_prenom=mbr.prenom if mbr else "?", present=p.present, )) out.append(ReunionAffairesOut( id=r.id, date_reunion=r.date_reunion, note=r.note, presences=pres_out, )) return out @router.post("/", response_model=list[PresenceOut]) async def enregistrer_presences( req: PresencesBatch, membre=Depends(require_secretaire), db: AsyncSession = Depends(get_db), ): """M2 — Enregistrer les présences pour une réunion d'affaires.""" # Vérifier que la réunion existe et est de type affaires result = await db.execute( select(Reunion).where( Reunion.id == req.reunion_id, Reunion.groupe_id == membre.groupe_id, ) ) reunion = result.scalar_one_or_none() if not reunion: raise HTTPException(status_code=404, detail="Réunion introuvable") # Supprimer les présences existantes pour cette réunion (pour pouvoir re-saisir) existing = await db.execute( select(Presence).where(Presence.reunion_id == req.reunion_id) ) for p in existing.scalars().all(): await db.delete(p) # Insérer les nouvelles out = [] for item in req.presences: pres = Presence( reunion_id=req.reunion_id, membre_id=item.membre_id, present=item.present, ) db.add(pres) mbr = await db.get(Membre, item.membre_id) out.append(PresenceOut( membre_id=item.membre_id, membre_prenom=mbr.prenom if mbr else "?", present=item.present, )) await db.flush() return out