Lien d'inscription protégé par jeton, reçus fiscaux mécénat, infobulles et revue de sécurité
- Lien d'inscription protégé par un jeton (FormTemplate.access_token, /inscription/<token>), avec régénération depuis l'écran de configuration. - Reçus fiscaux mécénat (cerfa 11580*05) : un PDF par bénévole ayant payé en mécénat, donateur/montant (chiffres et toutes lettres en français, app/mecenat/number_words.py)/date complétés par-dessus le modèle officiel. - Jeu de démonstration : bénévoles avec cotisation classique ou mécénat payée. - Infobulles (HelpTip et détail de cellule de la grille des besoins) repositionnées en position: fixed via JS, bornées à l'écran. - Revue de sécurité : logo des emails en pièce jointe inline au lieu d'une URL (fuyait le jeton d'inscription), sujet retiré du log de remise des emails (pouvait porter un nom de bénévole), photo du formulaire public revalidée par décodage réel avant stockage, export Excel protégé contre l'injection de formule, sondage du statut de paiement limité en fréquence, pyjwt mis à jour. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
a2b748566b
commit
096b7cd26a
@@ -1,6 +1,6 @@
|
||||
from sqlmodel import select
|
||||
|
||||
from app.models import Edition, Volunteer
|
||||
from app.models import Edition, FeePaymentStatus, Volunteer
|
||||
from app.seed import DEMO_EDITION_NAME, build
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ def test_build_creates_a_realistic_dataset(session):
|
||||
assert summary.position_wishes > 0
|
||||
assert summary.artist_wishes > 0
|
||||
assert summary.companion_wishes > 0
|
||||
# Paiement de la cotisation : au moins un exemple de chaque chemin.
|
||||
assert summary.fee_paid_classique > 0
|
||||
assert summary.fee_paid_mecenat > 0
|
||||
|
||||
edition = session.exec(
|
||||
select(Edition).where(Edition.name == DEMO_EDITION_NAME)
|
||||
@@ -30,6 +33,19 @@ def test_build_creates_a_realistic_dataset(session):
|
||||
).all()
|
||||
assert len(volunteers) == 120
|
||||
|
||||
paid = [v for v in volunteers if v.fee_payment_status == FeePaymentStatus.PAID]
|
||||
assert len(paid) == summary.fee_paid_classique + summary.fee_paid_mecenat
|
||||
mecenat = [v for v in paid if v.mecenat_address is not None]
|
||||
assert len(mecenat) == summary.fee_paid_mecenat
|
||||
for volunteer in mecenat:
|
||||
assert volunteer.mecenat_city and volunteer.mecenat_postal_code
|
||||
assert volunteer.mecenat_country == "France"
|
||||
assert volunteer.fee_payment_label == "Mécénat"
|
||||
classique = [v for v in paid if v.mecenat_address is None]
|
||||
for volunteer in classique:
|
||||
assert volunteer.fee_payment_label == "Cotisation classique"
|
||||
assert volunteer.fee_amount == 15.0
|
||||
|
||||
|
||||
def test_build_is_deterministic_and_idempotent(session):
|
||||
first = build(session)
|
||||
@@ -47,6 +63,8 @@ def test_build_is_deterministic_and_idempotent(session):
|
||||
assert second.position_wishes == first.position_wishes
|
||||
assert second.artist_wishes == first.artist_wishes
|
||||
assert second.companion_wishes == first.companion_wishes
|
||||
assert second.fee_paid_classique == first.fee_paid_classique
|
||||
assert second.fee_paid_mecenat == first.fee_paid_mecenat
|
||||
|
||||
editions = session.exec(
|
||||
select(Edition).where(Edition.name == DEMO_EDITION_NAME)
|
||||
|
||||
Reference in New Issue
Block a user