Retrait des compétences ; disponibilités en fenêtres horaires libres

Compétences supprimées de bout en bout :
- tables `skill`, `volunteer_skill`, `shift_required_skill` ;
- `Volunteer.skill_codes`, `Shift.required_skill_codes`, colonne `skills`
  de l'import, règle de validation `MISSING_REQUIRED_SKILL`, jeu de démo.

Disponibilités repensées : `Availability` passe de (`day_part_id`,
`status` tri-état) à une **fenêtre horaire libre** (`date`, `start_time`,
`end_time` ; `end <= start` ⇒ passe minuit), plusieurs par jour.
- Nouveaux endpoints `GET`/`PUT /editions/{id}/volunteers/{vid}/availability`
  (PUT = remplacement complet, COORDINATION) et
  `GET /editions/{id}/availability` (toute l'édition, pour la grille).
- `OUTSIDE_AVAILABILITY` (toujours WARNING) : compare le créneau à l'union
  des fenêtres du bénévole ce jour-là ; renvoie `date` + `uncovered_minutes`.
  `DayPart` ne sert plus qu'à la borne de « journée » du cumul 8 h.
- L'import ne gère plus les disponibilités (fenêtres saisies dans l'app) :
  `values.py` supprimé, mapping réduit à nom/email/téléphone/commentaire.

Migration 0007 (recrée `availability`). `AvailabilityStatus` supprimé.
158 tests, `alembic check` et `ruff` au vert. Front à suivre.

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 22:46:45 +02:00
co-authored by Claude Sonnet 5
parent 81609eecc5
commit be51770b51
36 changed files with 800 additions and 1199 deletions
+1 -31
View File
@@ -1,13 +1,10 @@
"""Helpers partagés par les routers CRUD."""
import uuid
from collections.abc import Iterable
from fastapi import HTTPException
from sqlalchemy import delete
from sqlmodel import Session, select
from app.models import Skill
from sqlmodel import Session
def owned_or_404(instance, edition_id: uuid.UUID):
@@ -17,33 +14,6 @@ def owned_or_404(instance, edition_id: uuid.UUID):
return instance
def resolve_skills(
session: Session, edition_id: uuid.UUID, codes: Iterable[str]
) -> list[Skill]:
"""Récupère ou crée les compétences de l'édition pour les codes donnés."""
existing = {
s.code: s
for s in session.exec(
select(Skill).where(Skill.edition_id == edition_id)
)
}
resolved: list[Skill] = []
seen: set[str] = set()
for raw in codes:
code = raw.strip()
if not code or code in seen:
continue
seen.add(code)
skill = existing.get(code)
if skill is None:
skill = Skill(edition_id=edition_id, code=code, label=code)
session.add(skill)
session.flush()
existing[code] = skill
resolved.append(skill)
return resolved
def replace_links(
session: Session, link_model, *, filter_column, filter_value, pairs
) -> None: