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:
co-authored by
Claude Sonnet 5
parent
81609eecc5
commit
be51770b51
@@ -17,11 +17,10 @@ colonne, route et variable utilise la colonne « Code / base ».
|
||||
|------------------------------------|-----------------------------------------------|
|
||||
| Édition | `Edition` |
|
||||
| Bénévole | `Volunteer` |
|
||||
| Compétence | `Skill` |
|
||||
| Poste | `Position` |
|
||||
| Référent d'un poste | `Position.lead_account_id` → `Account` |
|
||||
| Créneau | `Shift` |
|
||||
| Disponibilité | `Availability` |
|
||||
| Disponibilité (fenêtre horaire libre) | `Availability` (`date`, `start_time`, `end_time`) |
|
||||
| Affectation | `Assignment` |
|
||||
| Artiste programmé | `Artist` (`name`, `performance_date`) |
|
||||
| Vœu « je veux voir cet artiste » | `VolunteerArtist` (N-N `Volunteer` ↔ `Artist`) |
|
||||
@@ -37,13 +36,12 @@ colonne, route et variable utilise la colonne « Code / base ».
|
||||
| Origine d'affectation | `SOLVER` / `MANUAL` |
|
||||
| Verrouillée | `Assignment.locked: bool` |
|
||||
| Rôle | `COORDINATOR` / `LEAD` / `VOLUNTEER` |
|
||||
| Disponibilité (tri-état) | `AVAILABLE` / `IF_NEEDED` / `UNAVAILABLE` |
|
||||
| Catégorie de poste | `BAR` / `TICKETING` / `WELCOME` / `PARKING` / `CLEANING` / `CATERING` / `SETUP` / `STAGE` / `OTHER` |
|
||||
|
||||
### Distinction critique (ne jamais confondre, ni en base ni dans l'UI)
|
||||
|
||||
- **`Availability`** = une *déclaration* du bénévole : « je peux venir tel
|
||||
jour, telle demi-journée ».
|
||||
- **`Availability`** = une *déclaration* du bénévole : « le samedi, je peux
|
||||
venir de 14 h à 23 h » — fenêtre horaire libre, plusieurs par jour.
|
||||
- **`Assignment`** = une *décision* de l'organisation : « tu tiens le bar
|
||||
samedi 18 h – 22 h ».
|
||||
|
||||
@@ -63,17 +61,16 @@ les tables sauf les tables d'association.
|
||||
- **`DayPart`** (« créneau type ») : découpage d'une journée propre à l'édition
|
||||
(nombre et bornes libres). `code`, `label`, `start_time`, `end_time`
|
||||
(`end_time < start_time` ⇒ passe minuit), `order`. Unique (`edition_id`,
|
||||
`code`) ; `code` figé après création (clé de l'import des disponibilités).
|
||||
`code`) ; `code` figé après création. Sert à générer les créneaux 0/0 des
|
||||
postes (pas les disponibilités, qui sont des fenêtres libres).
|
||||
CRUD `GET/POST/PATCH/DELETE /editions/{id}/day-parts` (COORDINATION pour
|
||||
l'écriture).
|
||||
- **`Volunteer`** : `email` (unique **par édition**, nullable pour les
|
||||
bénévoles créés à la main — plusieurs `NULL` autorisés), `first_name`,
|
||||
`last_name`, `phone`, `comment`. Pas de date de naissance, ni de taille de
|
||||
t-shirt / régime alimentaire. Porte trois listes de vœux, exposées par la
|
||||
fiche (`skill_codes`, `artist_ids`, `wished_position_ids` ; `None`/absent =
|
||||
inchangé, `[]` = vider).
|
||||
- **`Skill`** : référentiel par édition (`code`, `label`). N-N `Volunteer`
|
||||
(`volunteer_skill`). N-N `Shift` (`shift_required_skill`).
|
||||
`last_name`, `phone`, `comment`. Pas de date de naissance, taille de
|
||||
t-shirt, régime, ni compétences. Porte deux listes de vœux, exposées par la
|
||||
fiche (`artist_ids`, `wished_position_ids` ; `None`/absent = inchangé,
|
||||
`[]` = vider).
|
||||
- **`Artist`** : programmation de l'édition (`name`, `performance_date`).
|
||||
Unique (`edition_id`, `name`, `performance_date`). La coordination gère la
|
||||
liste ; elle est présentée par jour. Les bénévoles y choisissent les
|
||||
@@ -87,13 +84,18 @@ les tables sauf les tables d'association.
|
||||
`lead_account_id` (→ `Account`, `SET NULL` à la suppression du compte).
|
||||
- **`Shift`** : `position_id`, `day_part_id` (nullable ; le créneau type dont
|
||||
il est issu, `SET NULL` si le type est supprimé), `starts_at`, `ends_at`
|
||||
(peut finir le lendemain), `min_staff`, `ideal_staff`, compétences requises
|
||||
(N-N `Skill`). À la création d'un `Position`, un `Shift` à 0/0 est généré par
|
||||
jour de l'édition et par `DayPart` ;
|
||||
`POST /editions/{id}/positions/{pid}/generate-shifts` recrée les manquants
|
||||
(après ajout d'un créneau type ou extension des dates).
|
||||
- **`Availability`** : `volunteer_id`, `date`, `day_part_id`, `status`
|
||||
(`AvailabilityStatus`). Unique (`volunteer_id`, `date`, `day_part_id`).
|
||||
(peut finir le lendemain), `min_staff`, `ideal_staff`. À la création d'un
|
||||
`Position`, un `Shift` à 0/0 est généré par jour de l'édition et par
|
||||
`DayPart` ; `POST /editions/{id}/positions/{pid}/generate-shifts` recrée les
|
||||
manquants (après ajout d'un créneau type ou extension des dates).
|
||||
- **`Availability`** : `volunteer_id`, `date`, `start_time`, `end_time`
|
||||
(`end_time <= start_time` ⇒ la fenêtre se termine le lendemain). Fenêtre
|
||||
horaire **libre** ; plusieurs par (`volunteer`, `date`). Unique
|
||||
(`volunteer_id`, `date`, `start_time`, `end_time`). Éditée en bloc :
|
||||
`GET`/`PUT /editions/{id}/volunteers/{vid}/availability` (lecture : COORD /
|
||||
LEAD ou le bénévole lui-même ; `PUT` : COORDINATION, remplace tout).
|
||||
`GET /editions/{id}/availability` liste celles de toute l'édition (COORD /
|
||||
LEAD) — pour la grille des besoins.
|
||||
- **`Assignment`** : `volunteer_id`, `shift_id`, `source` (`AssignmentSource`,
|
||||
défaut `MANUAL`), `locked` (défaut `false`). Unique (`volunteer_id`,
|
||||
`shift_id`). **Pas de statut** : l'affectation est valide dès sa création
|
||||
@@ -110,9 +112,9 @@ Conventions techniques :
|
||||
type ENUM natif PostgreSQL.
|
||||
- Suppression en cascade au niveau base (`ON DELETE CASCADE`) sur tous les
|
||||
`edition_id` et sur les liens vers `volunteer`, `shift`, `position`,
|
||||
`skill`, `artist`, `account`. La suppression complète d'un bénévole (RGPD)
|
||||
efface donc ses `Availability`, `Assignment`, liens de compétence, vœux
|
||||
(artistes, postes) et son `Account`.
|
||||
`artist`, `account`. La suppression complète d'un bénévole (RGPD) efface
|
||||
donc ses `Availability`, `Assignment`, vœux (artistes, postes) et son
|
||||
`Account`.
|
||||
- Dates/heures **naïves**, heure locale Europe/Paris (`TZ` du conteneur +
|
||||
`tzdata`). `app.models.local_now()` pour les horodatages.
|
||||
|
||||
@@ -135,6 +137,7 @@ Matrice appliquée par les endpoints :
|
||||
| `assignments` écriture | ✔ | ses postes | — |
|
||||
| `assignments` lecture | ✔ | ✔ | les siennes |
|
||||
| `artists` lecture / écriture | ✔ / ✔ | ✔ / — | ✔ / — |
|
||||
| `availability` lecture / écriture | ✔ / ✔ | ✔ / — | la sienne / — |
|
||||
| `matrix`, `import`, `accounts` | ✔ / ✔ / ✔ | ✔ / — / — | — |
|
||||
|
||||
**Authentification — deux systèmes selon le compte**, une même session JWT
|
||||
@@ -209,17 +212,20 @@ traduction se fait via `locales/fr.json`).
|
||||
| Code | Sévérité | Règle |
|
||||
|---------------------------|------------|-------------------------------------------------------------|
|
||||
| `SHIFT_OVERLAP` | `BLOCKING` | Deux créneaux d'un même bénévole se chevauchent. |
|
||||
| `OUTSIDE_AVAILABILITY` | `WARNING` | Un `DayPart` couvert par le créneau est `UNAVAILABLE` ou sans déclaration. |
|
||||
| `OUTSIDE_AVAILABILITY` | `WARNING` | Le créneau n'est pas entièrement couvert par les fenêtres de disponibilité déclarées ce jour-là (params : `date`, `uncovered_minutes`). |
|
||||
| `DAILY_HOURS_EXCEEDED` | `WARNING` | > 8 h 00 d'affectations cumulées sur une même journée. |
|
||||
| `INSUFFICIENT_REST` | `WARNING` | < 8 h 00 entre la fin d'un créneau et le début du suivant. |
|
||||
| `MISSING_REQUIRED_SKILL` | `WARNING` | Le bénévole n'a pas une compétence requise par le créneau. |
|
||||
|
||||
Sémantique figée :
|
||||
|
||||
- Toutes les affectations du bénévole comptent pour chevauchement / cumul /
|
||||
repos (une affectation = une décision, toujours active).
|
||||
- `OUTSIDE_AVAILABILITY` : on prend l'**union** des fenêtres `Availability` du
|
||||
bénévole (converties en instants, minuit géré) ; les minutes du créneau hors
|
||||
de cette union déclenchent l'avertissement. Aucune fenêtre déclarée ⇒
|
||||
créneau entièrement à découvert.
|
||||
- « Journée » (cumul 8 h) : du début du premier `DayPart` du jour au début du
|
||||
premier `DayPart` du lendemain.
|
||||
premier `DayPart` du lendemain (le `DayPart` ne sert plus qu'à ça).
|
||||
- Cumul : `> 8 h 00` strict (8 h 00 pile passe).
|
||||
- Repos : `< 8 h 00` strict.
|
||||
- Créneaux jointifs (`end_at == start_at`) : **pas** un chevauchement.
|
||||
@@ -234,7 +240,8 @@ Sémantique figée :
|
||||
### Premier incrément (en cours)
|
||||
|
||||
1. Modèle de données complet + migrations Alembic.
|
||||
2. Import CSV/Excel (un seul fichier bénévoles + disponibilités), en deux
|
||||
2. Import CSV/Excel (identité des bénévoles : nom, email, téléphone,
|
||||
commentaire — pas les disponibilités, qui se déclarent dans l'app), en deux
|
||||
temps : analyse (colonnes détectées, aperçu) puis exécution avec mapping
|
||||
validé. Rapport ligne par ligne. Détection des doublons : upsert sur
|
||||
`email` ; sans email, détection sur (`last_name`, `first_name`) signalée
|
||||
@@ -277,12 +284,12 @@ fonctions d'IA. *(L'envoi d'emails par SMTP est en place — voir § 2.)*
|
||||
│ │ ├── logging_config.py # RGPD : pas de PII dans les logs
|
||||
│ │ ├── api/ # routers + schemas Pydantic + deps
|
||||
│ │ │ # health, auth, accounts, editions,
|
||||
│ │ │ # day_parts, imports, artists,
|
||||
│ │ │ # volunteers, positions, shifts,
|
||||
│ │ │ # day_parts, imports, artists, volunteers,
|
||||
│ │ │ # availability, positions, shifts,
|
||||
│ │ │ # assignments, matrix, notifications
|
||||
│ │ ├── auth/ # mot de passe + lien magique + JWT + rôles
|
||||
│ │ ├── email/ # SMTP (sender) + rendu des emails (templates)
|
||||
│ │ ├── importing/ # import CSV/Excel (parser, values, service)
|
||||
│ │ ├── importing/ # import CSV/Excel (parser, text, service)
|
||||
│ │ ├── models/ # tables SQLModel
|
||||
│ │ ├── validation/ # moteur pur (models, engine) + loader (base)
|
||||
│ │ ├── bootstrap.py # instance vierge : édition + 1er compte
|
||||
@@ -371,6 +378,7 @@ Aucune dépendance locale requise hors Docker.
|
||||
| 11 | `GET`/`PATCH /editions/{id}` (nom, dates) — ajouter/retirer des jours ; UX config front | validée |
|
||||
| 12 | Créneaux types (`DayPart` CRUD) + génération auto des créneaux 0/0 à la création d'un poste ; case « à définir » / « sureffectif » sur la grille | à valider |
|
||||
| 13 | Bénévole : retrait taille de t-shirt / régime ; entité `Artist` (CRUD, par jour) + vœux des bénévoles (artistes, postes) | à valider |
|
||||
| 14 | Retrait des compétences (`Skill` et règle `MISSING_REQUIRED_SKILL`) ; disponibilités = fenêtres horaires libres par jour (`GET`/`PUT` availability), prises en compte par `OUTSIDE_AVAILABILITY` | à valider |
|
||||
|
||||
**Reste hors périmètre** (modèle prêt à les accueillir) : solveur OR-Tools,
|
||||
SMS, constructeur de formulaire, pointage sur site, IA. Frontend :
|
||||
|
||||
Reference in New Issue
Block a user