Édition éditable (dates) + robustesse de l'écran Postes & créneaux

Back-end :
- GET /editions/{id} (tout compte de l'édition) et PATCH /editions/{id}
  (COORDINATION) : name, year, start_date, end_date, retention_days.
  Refus si end_date < start_date (edition.dates_inconsistent). Tests.

Front-end :
- api : EditionRead/Update, editions.get/update (http + mock), hooks
  useEdition / useUpdateEdition.
- ConfigGrid : le bouton « ajouter un poste » sélectionne le poste créé et
  affiche l'état (pending) + les erreurs (ErrorNote) — avant, un échec ou
  même un succès passaient inaperçus. Renommage du poste + choix de la
  catégorie + suppression dans l'en-tête. Une colonne par jour de l'édition
  (même vide) → on peut ajouter un créneau n'importe quel jour.
- ConfigRules : formulaire d'édition (nom + dates) → c'est là qu'on ajoute
  ou retire des jours ; compteur de jours ; retour d'enregistrement.
- i18n mis à jour.

144 tests back OK ; typecheck + lint + build + smoke front OK.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017uUsRg7XzopkCPWimwg9ZM
This commit is contained in:
AdamThomas
2026-09-10 20:02:50 +02:00
co-authored by Claude Sonnet 5
parent 9521034207
commit e0a0c56307
14 changed files with 517 additions and 115 deletions
+39
View File
@@ -0,0 +1,39 @@
"""Lecture et mise à jour d'une édition (dates, nom, rétention)."""
from fastapi import APIRouter, Depends, HTTPException
from sqlmodel import Session
from app.api.deps import get_edition
from app.api.schemas import EditionRead, EditionUpdate
from app.auth.deps import current_account_in_edition, require_coordinator
from app.db import get_session
from app.models import Account, Edition
router = APIRouter(prefix="/editions", tags=["editions"])
@router.get("/{edition_id}", response_model=EditionRead)
def read_edition(
edition: Edition = Depends(get_edition),
_account: Account = Depends(current_account_in_edition),
) -> Edition:
return edition
@router.patch("/{edition_id}", response_model=EditionRead)
def update_edition(
payload: EditionUpdate,
edition: Edition = Depends(get_edition),
_guard: Account = Depends(require_coordinator),
session: Session = Depends(get_session),
) -> Edition:
for field, value in payload.model_dump(exclude_unset=True).items():
setattr(edition, field, value)
if edition.end_date < edition.start_date:
raise HTTPException(
status_code=422, detail={"code": "edition.dates_inconsistent"}
)
session.add(edition)
session.commit()
session.refresh(edition)
return edition
+22
View File
@@ -19,6 +19,28 @@ class ApiModel(BaseModel):
model_config = ConfigDict(from_attributes=True)
# --------------------------------------------------------------------------- #
# Edition #
# --------------------------------------------------------------------------- #
class EditionRead(ApiModel):
id: uuid.UUID
year: int
name: str | None
start_date: dt.date
end_date: dt.date
retention_days: int | None
created_at: dt.datetime
updated_at: dt.datetime
class EditionUpdate(ApiModel):
name: str | None = None
year: int | None = None
start_date: dt.date | None = None
end_date: dt.date | None = None
retention_days: int | None = None
# --------------------------------------------------------------------------- #
# Volunteer #
# --------------------------------------------------------------------------- #
+1
View File
@@ -5,6 +5,7 @@
"error.unauthorized": "Authentification requise",
"error.validation_failed": "La validation a échoué",
"error.conflict": "Conflit avec une ressource existante.",
"edition.dates_inconsistent": "La date de fin doit être postérieure ou égale à la date de début.",
"shift.ends_not_after_starts": "La fin du créneau doit être postérieure à son début.",
"shift.position_wrong_edition": "Le poste n'appartient pas à cette édition.",
"volunteer.duplicate_email": "Un bénévole avec cet email existe déjà dans l'édition.",
+2
View File
@@ -4,6 +4,7 @@ from fastapi.middleware.cors import CORSMiddleware
from app.api.accounts import router as account_router
from app.api.assignments import router as assignment_router
from app.api.auth import router as auth_router
from app.api.editions import router as edition_router
from app.api.health import router as health_router
from app.api.imports import router as import_router
from app.api.matrix import router as matrix_router
@@ -30,6 +31,7 @@ app.add_middleware(
app.include_router(health_router)
app.include_router(auth_router)
app.include_router(edition_router)
app.include_router(account_router)
app.include_router(import_router)
app.include_router(volunteer_router)