Bénévole : vœux d'artistes (entité Artist) et vœux de postes

Nouvelle entité `Artist` (nom + jour de passage), CRUD
`/editions/{id}/artists` (lecture ouverte à tout compte de l'édition,
écriture COORDINATION), unicité (édition, nom, date).

La fiche bénévole porte deux listes de vœux, sur le modèle de
`skill_codes` (None = inchangé, [] = vider) :
- `artist_ids` : artistes que le bénévole veut voir (`volunteer_artist`) ;
- `wished_position_ids` : postes qu'il voudrait tenir
  (`volunteer_position_wish`), distinct de l'affectation.

Un id étranger à l'édition renvoie 422 (`artist.unknown` /
`position.unknown`). Suppression d'un artiste : cascade sur les vœux.
Migration 0006. Jeu de démonstration et frontend inchangés (le front
suivra).

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:12:55 +02:00
co-authored by Claude Sonnet 5
parent 609514369a
commit 24caece7df
13 changed files with 618 additions and 11 deletions
+23 -6
View File
@@ -23,6 +23,9 @@ colonne, route et variable utilise la colonne « Code / base ».
| Créneau | `Shift` |
| Disponibilité | `Availability` |
| Affectation | `Assignment` |
| Artiste programmé | `Artist` (`name`, `performance_date`) |
| Vœu « je veux voir cet artiste » | `VolunteerArtist` (N-N `Volunteer` ↔ `Artist`) |
| Vœu « je voudrais ce poste » | `VolunteerPositionWish` (N-N `Volunteer` ↔ `Position`) |
| Compte (utilisateur connecté) | `Account` |
| Découpage de journée (paramétrable)| `DayPart` (`code`, `label`, `start_time`, `end_time`, `order`) |
| Lien magique | `MagicLinkToken` |
@@ -66,9 +69,20 @@ les tables sauf les tables d'association.
- **`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.
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`).
- **`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
artistes qu'ils veulent voir (`volunteer_artist`). CRUD
`GET/POST/PATCH/DELETE /editions/{id}/artists` (lecture : tout compte de
l'édition ; écriture : COORDINATION).
- Vœux de postes : `volunteer_position_wish` (« postes que le bénévole
voudrait tenir »), distinct de `Assignment` (décision) et de `Position.
lead_account_id` (référent).
- **`Position`** : `name`, `location`, `category` (`PositionCategory`),
`lead_account_id` (→ `Account`, `SET NULL` à la suppression du compte).
- **`Shift`** : `position_id`, `day_part_id` (nullable ; le créneau type dont
@@ -96,8 +110,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`, `account`. La suppression complète d'un bénévole (RGPD) efface donc
ses `Availability`, `Assignment`, liens de compétence et son `Account`.
`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`.
- Dates/heures **naïves**, heure locale Europe/Paris (`TZ` du conteneur +
`tzdata`). `app.models.local_now()` pour les horodatages.
@@ -119,6 +134,7 @@ Matrice appliquée par les endpoints :
| `positions`/`shifts` écriture | ✔ | ses postes | — |
| `assignments` écriture | ✔ | ses postes | — |
| `assignments` lecture | ✔ | ✔ | les siennes |
| `artists` lecture / écriture | ✔ / ✔ | ✔ / — | ✔ / — |
| `matrix`, `import`, `accounts` | ✔ / ✔ / ✔ | ✔ / — / — | — |
**Authentification — deux systèmes selon le compte**, une même session JWT
@@ -261,9 +277,9 @@ 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, volunteers,
│ │ │ # positions, shifts, assignments,
│ │ │ # matrix, notifications
│ │ │ # day_parts, imports, artists,
│ │ │ # volunteers, 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)
@@ -354,6 +370,7 @@ Aucune dépendance locale requise hors Docker.
| 10 | Envoi d'emails par SMTP : lien magique + récapitulatif de planning | validée |
| 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 |
**Reste hors périmètre** (modèle prêt à les accueillir) : solveur OR-Tools,
SMS, constructeur de formulaire, pointage sur site, IA. Frontend :