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>
113 lines
3.3 KiB
Python
113 lines
3.3 KiB
Python
"""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()
|