Refonte du créateur de badges et nombre de badges par bénévole
ci-cd / build-backend (push) Successful in 30s
ci-cd / deploy (push) Successful in 13s
ci-cd / build-frontend (push) Successful in 22s

Créateur de badges refait d'après la maquette : champs sur le recto et le
verso (BadgeField.face, verso placé en miroir dans le PDF pour le
recto-verso), fond de zone, réduction automatique du texte, photo ovale,
initiales sans photo, nom sur deux lignes (migration 0026) ; éditeur en
trois colonnes (champs, aperçu recto/verso avec de vrais bénévoles,
inspecteur), formats prédéfinis, annuler, flèches, zoom, repères de
découpe, dépôt d'image sur l'aperçu, contrôle de tous les badges avant
génération.

Nombre de badges par bénévole (Volunteer.badge_count, 1 par défaut,
migration 0027) : champ sur la fiche, autant d'exemplaires sur les
planches, total affiché à la génération, colonne dans l'export Excel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AdamThomas
2026-10-27 19:20:42 +01:00
co-authored by Claude Opus 5.5
parent 1a23881e81
commit ced0855d47
28 changed files with 3738 additions and 803 deletions
+44 -16
View File
@@ -89,7 +89,10 @@ les tables sauf les tables d'association.
paiement SumUp plus bas). `badge_text` : texte du badge propre à ce bénévole
(champ `FREE_TEXT`, voir `BadgeField`), saisi par la coordination sur la
fiche (« Texte du badge ») — jamais par le formulaire public — et qui
remplace pour lui le texte commun de l'éditeur.
remplace pour lui le texte commun de l'éditeur. `badge_count` : nombre
d'exemplaires de son badge imprimés sur les planches (1 par défaut, 1 à
100 — `CHECK >= 1` en base, borne haute à la saisie contre une faute de
frappe), « Nombre de badges » sur la fiche.
- **`Artist`** : programmation de l'édition (`name`, `performance_date`,
`start_time`, `end_time` — `end_time <= start_time` ⇒ le passage finit le
lendemain). Unique (`edition_id`, `name`, `performance_date`). La
@@ -162,15 +165,31 @@ les tables sauf les tables d'association.
et la génération retombant alors sur le format CR80 par défaut (85,6 ×
54 mm, `DEFAULT_BADGE_WIDTH_MM`/`HEIGHT_MM`). Le verso est ajusté (sans
déformation, `preserveAspectRatio=True`) à ce même format.
- **`BadgeField`** : un champ positionné sur le recto (`field_type` ∈
`FULL_NAME` / `POSITIONS` / `CATEGORY` / `ROLE` / `PHOTO` / `FREE_TEXT`,
au plus un par édition —
`PUT .../badges/template/fields` remplace tout), coordonnées `x_mm`/`y_mm`/
`width_mm`/`height_mm` en millimètres depuis le coin haut-gauche du badge,
`font_size`, `align`, `bold`, `color`. Édité visuellement (glisser-déposer)
depuis l'écran « Badges » du front-end (`frontend/src/pages/BadgesPage.tsx`),
qui envoie ces mêmes millimètres et redimensionne son aperçu au format réel
du badge (`GET .../badges/template` renvoie `badge_width_mm`/`height_mm`).
- **`BadgeField`** : un champ positionné sur le **recto ou le verso**
(`face` ∈ `FRONT` / `BACK` ; `field_type` ∈ `FULL_NAME` / `POSITIONS` /
`CATEGORY` / `ROLE` / `PHOTO` / `FREE_TEXT`, au plus un par face et par
édition — unique (`edition_id`, `face`, `field_type`), 422
`badge.duplicate_field_type` sinon ; `PUT .../badges/template/fields`
remplace tout, les deux faces dans la même liste), coordonnées `x_mm`/
`y_mm`/`width_mm`/`height_mm` en millimètres depuis le coin haut-gauche du
badge, `font_size`, `align`, `bold`, `color`, `bg_color` (fond plein de la
zone, `NULL` = transparent, `#rrggbb` sinon 422 `badge.invalid_color`),
`auto_fit` (taille réduite par pas de 0,5 pt, plancher 4 pt, jusqu'à ce que
tout tienne — `app/badges/pdf.py:fit_text` ; sans, les lignes en trop ne
sont pas imprimées), `photo_shape` (`RECT` / `OVAL`, champ `PHOTO`).
`FREE_TEXT` et `ROLE` sont centrés verticalement dans leur cadre (règle
dérivée du type), les autres en haut. `FULL_NAME` = prénom, puis nom à la
ligne. Édité visuellement depuis l'écran « Badges »
(`frontend/src/pages/badges/` — refonte d'après `design/Créateur de
badges.dc.html` : liste des champs recto/verso, aperçu des deux faces avec
les données d'un vrai bénévole, inspecteur, formats prédéfinis via
`PUT .../template/size`, annuler, flèches, zoom, repères de découpe
1,5 mm, contrôle de tous les badges avant génération — `issues.ts`).
L'aperçu reprend la mise en page du PDF à l'identique
(`badges/layout.ts`, avec les métriques Helvetica de ReportLab recopiées
dans `helveticaMetrics.ts`, pas une mesure du navigateur) : toute
règle de mise en page changée dans `app/badges/pdf.py` doit l'être aussi
dans `layout.ts`, sinon le contrôle avant génération ment.
`POSITIONS` est un **tableau** (`app/api/badges.py:_positions_table`,
dessiné par `app/badges/pdf.py:_draw_table`) des créneaux affectés,
**regroupés par jour de festival** (§ 3 — une soirée qui passe minuit
@@ -188,23 +207,30 @@ les tables sauf les tables d'association.
au ratio du cadre sans déformation, orientation EXIF appliquée et réduite
à 300 dpi (`app/badges/pdf.py:_fit_photo` — une photo de téléphone de
plusieurs Mo répétée sur 200 badges alourdirait le PDF) ; sans photo ou
photo illisible, le cadre reste vide plutôt que de faire échouer la
planche. Le texte d'un champ passe à la ligne pour tenir dans la largeur
photo illisible, les **initiales** du bénévole sont imprimées à la
place (fond `#E7E0CE`, même forme) plutôt que de faire échouer la
planche ; `OVAL` découpe la photo en ellipse. Le texte d'un champ passe à la ligne pour tenir dans la largeur
de son cadre (`app/badges/pdf.py:wrap_text` — entre deux mots, ou au
milieu d'un mot trop long seul ; les `\n` restent des retours à la
ligne), et ce qui dépasse la hauteur du cadre n'est pas imprimé — comme
l'aperçu de l'éditeur, qui fait de même côté navigateur. Avant, chaque
ligne s'imprimait d'un bloc et débordait du cadre. L'éditeur ramène tout champ à l'intérieur du badge
(`BadgesPage.tsx:fitToBadge`) et met le champ sélectionné au premier
(`badges/model.ts:fitToBadge`) et met le champ sélectionné au premier
plan : les positions par défaut visent un CR80 paysage, et sur un badge
plus étroit un champ pouvait tomber hors du badge, avec des bornes de
glisser-déposer négatives qui le bloquaient — bug corrigé une fois (photo
impossible à déplacer ni redimensionner).
`POST /editions/{id}/badges/generate` (COORDINATION, `{volunteer_ids?}` —
absent = tous) génère la planche PDF (`app/badges/pdf.py`, OR-Tools n'y est
absent = tous ; chaque bénévole y figure `badge_count` fois, à la suite)
génère la planche PDF (`app/badges/pdf.py`, OR-Tools n'y est
pour rien : juste ReportLab) : pages recto personnalisées suivies, planche
par planche, de la page de verso correspondante (même image répétée à
chaque emplacement) — impression recto-verso classique.
par planche, de la page de verso correspondante (image de verso puis
champs `BACK` de chaque bénévole) — impression recto-verso classique
(retournement bord long) : sur la page de verso, chaque badge est placé
**en miroir horizontal** (colonne `cols-1-col`, `_cell_origin(mirrored=
True)`) pour tomber derrière son recto. Indispensable depuis que le verso
est personnalisé ; avant, un verso identique pour tous masquait le
décalage.
- **Feuilles de poste** (PDF, `app/sheets/pdf.py` + `app/api/position_sheets.py`) :
aucune nouvelle entité — dérivées de `Position`/`Shift`/`Assignment`/
`Volunteer` existants. Complément des badges (un badge identifie un
@@ -953,6 +979,8 @@ Aucune dépendance locale requise hors Docker.
| 48 | Badges : texte libre saisi dans l'éditeur (`BadgeField.text`, migration `0025`), commun à tous les badges, le « Texte du badge » de la fiche le remplaçant pour un bénévole ; champs ramenés dans le badge et champ sélectionné au premier plan (la photo restait bloquée hors d'un badge plus étroit que le CR80) | à valider |
| 49 | Badges : le texte des champs (nom, postes et horaires…) passe à la ligne pour tenir dans la largeur de son cadre au lieu de déborder (`app/badges/pdf.py:wrap_text`) | à valider |
| 50 | Badges : les créneaux affectés s'affichent en tableau — le jour de festival en en-tête, puis une colonne horaire et une colonne poste (le référent n'est plus imprimé sur le badge), filets à la couleur du champ, cellules repliées dans leur colonne ; même tableau dans l'aperçu de l'éditeur | à valider |
| 51 | Refonte du créateur de badges d'après la maquette (`design/`) : champs sur le recto **et le verso** (`BadgeField.face`, verso en miroir dans le PDF), fond de zone, réduction automatique du texte, photo ovale, initiales sans photo, nom sur deux lignes (migration `0026`) ; éditeur en 3 colonnes (champs, aperçu recto/verso avec de vrais bénévoles, inspecteur), formats prédéfinis, annuler/flèches/zoom/repères, dépôt d'image sur l'aperçu, contrôle de tous les badges avant génération | à valider |
| 52 | Nombre de badges par bénévole (`Volunteer.badge_count`, 1 par défaut, migration `0027`) : champ « Nombre de badges » sur la fiche, autant d'exemplaires sur les planches, total à imprimer affiché à la génération, colonne dans l'export Excel | à valider |
**Reste hors périmètre** (modèle prêt à les accueillir) : SMS, constructeur
de formulaire, pointage sur site, IA. Frontend : squelette + 6 écrans +