Inscription en deux temps, emails de confirmation et export Excel des bénévoles
Formulaire public — paiement en ligne : - Le bénévole n'est plus compté en base tant qu'un paiement en ligne choisi n'est pas confirmé (PendingFormSubmission, promu en Volunteer à la confirmation, webhook ou vérification en direct). - Type de paiement « mécénat » : étape d'adresse postale avant le paiement, conservée sur le bénévole pour un reçu fiscal (cerfa) ultérieur. - Page de confirmation au retour de SumUp, avec relance possible après échec/expiration sans ressaisir le formulaire. - « Je paierai sur place » remplace « je ne participe pas » ; tous les champs activés par la coordination sont désormais obligatoires. - La fiche bénévole n'affiche plus l'état des cases à cocher, seulement le résultat réel du paiement (libellé, montant, statut). Emails : - Email de confirmation d'inscription à la création du bénévole (tient lieu de reçu de paiement quand une cotisation est payée en ligne). - Mise en forme HTML au thème de l'application (logo de l'édition en en-tête) pour tous les emails envoyés, texte brut conservé en repli. Export Excel des bénévoles (COORDINATION) : coordonnées, vœux résolus en noms lisibles, cotisation et mécénat. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
68d42ee303
commit
a2b748566b
@@ -0,0 +1,3 @@
|
||||
from app.exporting.xlsx import generate_volunteers_xlsx
|
||||
|
||||
__all__ = ["generate_volunteers_xlsx"]
|
||||
@@ -0,0 +1,112 @@
|
||||
"""Export Excel de la liste des bénévoles d'une édition — voir
|
||||
`app/api/volunteers.py:export_volunteers`. Même bibliothèque que l'import
|
||||
(`openpyxl`, `app/importing/parser.py`), en écriture cette fois.
|
||||
|
||||
Texte en dur (en-têtes de colonnes, libellés) : comme `app/sheets/pdf.py` et
|
||||
`app/badges/pdf.py`, un document généré n'est pas un texte d'interface —
|
||||
pas de `app.i18n.t()` ici, contrairement aux réponses de l'API."""
|
||||
|
||||
import io
|
||||
import uuid
|
||||
|
||||
from openpyxl import Workbook
|
||||
from openpyxl.styles import Font
|
||||
|
||||
from app.api.schemas import VolunteerRead
|
||||
|
||||
_HEADER = [
|
||||
"Prénom",
|
||||
"Nom",
|
||||
"Email",
|
||||
"Téléphone",
|
||||
"Disponible",
|
||||
"Postes souhaités",
|
||||
"Artistes souhaités",
|
||||
"Compagnons souhaités",
|
||||
"Note de compagnon",
|
||||
"Cotisation choisie",
|
||||
"Montant (€)",
|
||||
"Statut du paiement",
|
||||
"Payée le",
|
||||
"Mécénat — adresse",
|
||||
"Mécénat — ville",
|
||||
"Mécénat — code postal",
|
||||
"Mécénat — pays",
|
||||
"Commentaire",
|
||||
"Inscrit le",
|
||||
]
|
||||
|
||||
_FEE_STATUS_LABELS = {
|
||||
"PENDING": "En attente",
|
||||
"PAID": "Payée",
|
||||
"FAILED": "Échouée",
|
||||
"EXPIRED": "Expirée",
|
||||
}
|
||||
|
||||
|
||||
def _yes_no(value: bool) -> str:
|
||||
return "Oui" if value else "Non"
|
||||
|
||||
|
||||
def _join_names(ids: list[uuid.UUID], names: dict[uuid.UUID, str]) -> str:
|
||||
return ", ".join(names[i] for i in ids if i in names)
|
||||
|
||||
|
||||
def _fmt_dt(value) -> str:
|
||||
return value.strftime("%d/%m/%Y %H:%M") if value else ""
|
||||
|
||||
|
||||
def generate_volunteers_xlsx(
|
||||
volunteers: list[VolunteerRead],
|
||||
*,
|
||||
position_names: dict[uuid.UUID, str],
|
||||
artist_names: dict[uuid.UUID, str],
|
||||
volunteer_names: dict[uuid.UUID, str],
|
||||
) -> bytes:
|
||||
"""`position_names`/`artist_names`/`volunteer_names` : cartes id → nom
|
||||
construites une fois par l'appelant (pas de requête par bénévole) —
|
||||
voir `app/api/volunteers.py:export_volunteers`."""
|
||||
workbook = Workbook()
|
||||
sheet = workbook.active
|
||||
sheet.title = "Bénévoles"
|
||||
sheet.append(_HEADER)
|
||||
for cell in sheet[1]:
|
||||
cell.font = Font(bold=True)
|
||||
|
||||
for v in volunteers:
|
||||
sheet.append(
|
||||
[
|
||||
v.first_name,
|
||||
v.last_name,
|
||||
v.email or "",
|
||||
v.phone or "",
|
||||
_yes_no(v.self_declared_available),
|
||||
_join_names(v.wished_position_ids, position_names),
|
||||
_join_names(v.artist_ids, artist_names),
|
||||
_join_names(v.wished_companion_ids, volunteer_names),
|
||||
v.companion_wish_note or "",
|
||||
v.fee_payment_label or "",
|
||||
v.fee_amount if v.fee_amount is not None else "",
|
||||
_FEE_STATUS_LABELS.get(v.fee_payment_status, ""),
|
||||
_fmt_dt(v.fee_paid_at),
|
||||
v.mecenat_address or "",
|
||||
v.mecenat_city or "",
|
||||
v.mecenat_postal_code or "",
|
||||
v.mecenat_country or "",
|
||||
v.comment or "",
|
||||
_fmt_dt(v.created_at),
|
||||
]
|
||||
)
|
||||
|
||||
for column_cells in sheet.columns:
|
||||
length = max(
|
||||
(len(str(c.value)) for c in column_cells if c.value is not None),
|
||||
default=8,
|
||||
)
|
||||
sheet.column_dimensions[column_cells[0].column_letter].width = min(
|
||||
max(length + 2, 10), 40
|
||||
)
|
||||
|
||||
buffer = io.BytesIO()
|
||||
workbook.save(buffer)
|
||||
return buffer.getvalue()
|
||||
Reference in New Issue
Block a user