Files
benevoles/backend/app/api/matrix.py
T
AdamThomasandClaude Sonnet 5 74e35f0195 Étape 5 : API CRUD, matrice et validation branchée
- CRUD complet, scopé par édition, sur Position, Shift, Volunteer, Assignment
  (routers app/api/*, schémas Pydantic, dépendance get_edition partagée)
- Volunteer : gestion des compétences par codes (création à la volée),
  suppression = effacement complet en cascade (RGPD)
- Shift : contrôle fin > début, poste de la même édition, compétences requises
- Assignment : toute écriture active passe par le moteur de validation ;
  violation bloquante -> 422 error.validation_failed, avertissements renvoyés
  dans la réponse. Endpoint POST .../assignments/validate en simulation.
  app/validation/loader.py construit la ValidationInput depuis la base.
- GET /editions/{id}/matrix : postes -> créneaux -> {min, ideal, confirmed,
  proposed}, source de l'écran principal
- 30 tests d'API (CRUD, 404/409/422, cascade, blocage, avertissements, matrice,
  loader) ; 104 tests au total ; alembic check toujours vert
- Clés de traduction shift.* / assignment.* / volunteer.* ajoutées

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
2026-09-09 22:54:22 +02:00

76 lines
2.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Matrice poste × créneau d'une édition : source de l'écran principal."""
from fastapi import APIRouter, Depends
from sqlalchemy import func
from sqlmodel import Session, select
from app.api.deps import get_edition
from app.api.schemas import MatrixPosition, MatrixResponse, MatrixShift
from app.db import get_session
from app.models import Assignment, AssignmentStatus, Edition, Position, Shift
router = APIRouter(prefix="/editions/{edition_id}/matrix", tags=["matrix"])
@router.get("", response_model=MatrixResponse)
def get_matrix(
edition: Edition = Depends(get_edition),
session: Session = Depends(get_session),
) -> MatrixResponse:
positions = list(
session.exec(
select(Position)
.where(Position.edition_id == edition.id)
.order_by(Position.name)
)
)
shifts = list(
session.exec(
select(Shift)
.where(Shift.edition_id == edition.id)
.order_by(Shift.starts_at)
)
)
counts = {
(shift_id, status): total
for shift_id, status, total in session.exec(
select(
Assignment.shift_id, Assignment.status, func.count()
)
.where(Assignment.edition_id == edition.id)
.group_by(Assignment.shift_id, Assignment.status)
)
}
shifts_by_position: dict = {}
for shift in shifts:
shifts_by_position.setdefault(shift.position_id, []).append(
MatrixShift(
shift_id=shift.id,
starts_at=shift.starts_at,
ends_at=shift.ends_at,
min_staff=shift.min_staff,
ideal_staff=shift.ideal_staff,
confirmed=counts.get(
(shift.id, AssignmentStatus.CONFIRMED), 0
),
proposed=counts.get(
(shift.id, AssignmentStatus.PROPOSED), 0
),
)
)
return MatrixResponse(
edition_id=edition.id,
positions=[
MatrixPosition(
id=position.id,
name=position.name,
category=position.category,
location=position.location,
shifts=shifts_by_position.get(position.id, []),
)
for position in positions
],
)