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>
77 lines
2.9 KiB
Python
77 lines
2.9 KiB
Python
"""Version HTML des emails — même thème que l'appli (couleurs/typographie de
|
|
`frontend/tailwind.config.js`), logo de l'édition en en-tête si défini.
|
|
|
|
Chaque email reste envoyé en `multipart/alternative` (texte + HTML, voir
|
|
`app/email/sender.py:build_message`) : le texte brut reste la source de
|
|
vérité (accessibilité, clients sans HTML, RGPD — rien de plus dans le HTML
|
|
que dans le texte déjà journalisable). Pas de CSS externe ni de police web
|
|
(rarement chargées par les clients mail) : mise en page par tableau, styles
|
|
en ligne, polices système en repli du style « Archivo »/« DM Mono »."""
|
|
|
|
import html as _html
|
|
|
|
_INK = "#14110E"
|
|
_CREAM = "#F5EFE1"
|
|
_PAPER = "#FFFDF7"
|
|
_YELLOW = "#FFD23F"
|
|
|
|
_FONT_DISPLAY = "Arial, Helvetica, sans-serif"
|
|
_FONT_MONO = "'Courier New', Courier, monospace"
|
|
|
|
|
|
def _paragraphs(body: str) -> str:
|
|
"""`\n\n` sépare les paragraphes, un simple `\n` reste un retour à la
|
|
ligne — même convention que les modèles texte (`app/email/templates.py`)."""
|
|
blocks = body.strip().split("\n\n")
|
|
return "".join(
|
|
f'<p style="margin:0 0 14px;">{_html.escape(block).replace(chr(10), "<br>")}</p>'
|
|
for block in blocks
|
|
if block.strip()
|
|
)
|
|
|
|
|
|
def render_html(*, subject: str, body: str, logo_url: str | None) -> str:
|
|
"""`logo_url` : voir `app/email/branding.py:edition_logo_url` — `None`
|
|
tant que l'édition n'a pas importé de logo, l'en-tête retombe alors sur
|
|
un simple bandeau texte (le sujet, en capitales)."""
|
|
if logo_url:
|
|
header = (
|
|
f'<img src="{_html.escape(logo_url)}" alt="" width="160" '
|
|
'style="max-height:64px;width:auto;display:block;margin:0 auto;'
|
|
'border:0;">'
|
|
)
|
|
else:
|
|
header = (
|
|
f'<span style="font-family:{_FONT_DISPLAY};font-weight:800;'
|
|
f"text-transform:uppercase;letter-spacing:1px;color:{_YELLOW};"
|
|
f'font-size:14px;">{_html.escape(subject)}</span>'
|
|
)
|
|
return f"""<!doctype html>
|
|
<html lang="fr">
|
|
<head><meta charset="utf-8"></head>
|
|
<body style="margin:0;padding:24px 16px;background:{_CREAM};">
|
|
<table role="presentation" width="100%" cellpadding="0" cellspacing="0">
|
|
<tr>
|
|
<td align="center">
|
|
<table role="presentation" width="520" cellpadding="0" cellspacing="0"
|
|
style="max-width:520px;width:100%;">
|
|
<tr>
|
|
<td style="background:{_INK};padding:18px;text-align:center;
|
|
border:3px solid {_INK};">
|
|
{header}
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="background:{_PAPER};border:3px solid {_INK};
|
|
border-top:none;padding:22px;font-size:13px;
|
|
line-height:1.6;color:{_INK};font-family:{_FONT_MONO};">
|
|
{_paragraphs(body)}
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</body>
|
|
</html>"""
|