"""Passage d'un `Volunteer` à un `MecenatReceipt` — partagé par l'export coordination (`app/api/volunteers.py:export_mecenat_receipts`) et l'envoi du reçu au bénévole à la confirmation de son paiement (`app/api/forms.py: _promote_pending`) — et attribution de son numéro d'ordre.""" from sqlalchemy import func, text from sqlmodel import Session, select from app.mecenat.pdf import MecenatReceipt from app.models import Volunteer RECEIPT_FILENAME = "recu-fiscal-mecenat.pdf" # Verrou applicatif PostgreSQL (`pg_advisory_xact_lock`) sérialisant # l'attribution des numéros : le webhook SumUp et le sondage du navigateur # peuvent confirmer deux paiements en même temps — sans lui, les deux # liraient le même maximum et se disputeraient le même numéro. Libéré # automatiquement à la fin de la transaction. Valeur arbitraire, propre à # cet usage. _NUMBERING_LOCK = 11580_05 def assign_receipt_number(session: Session, volunteer: Volunteer) -> None: """Attribue (sans commit) le prochain numéro de l'année du paiement : `AAAA` + rang sur 5 chiffres, à la suite du plus grand déjà attribué cette année-là, toutes éditions confondues (une seule numérotation pour l'association). Ne fait rien si le bénévole en a déjà un — un numéro n'est jamais réattribué.""" if volunteer.mecenat_receipt_number: return session.exec(text("SELECT pg_advisory_xact_lock(:key)").bindparams(key=_NUMBERING_LOCK)) year = (volunteer.fee_paid_at or volunteer.created_at).year prefix = str(year) last = session.exec( select(func.max(Volunteer.mecenat_receipt_number)).where( Volunteer.mecenat_receipt_number.startswith(prefix) ) ).one() rank = int(last[len(prefix):]) + 1 if last else 1 volunteer.mecenat_receipt_number = f"{prefix}{rank:05d}" session.add(volunteer) session.flush() def receipt_for(volunteer: Volunteer) -> MecenatReceipt: return MecenatReceipt( last_name=volunteer.last_name, first_name=volunteer.first_name, address=volunteer.mecenat_address or "", postal_code=volunteer.mecenat_postal_code or "", city=volunteer.mecenat_city or "", country=volunteer.mecenat_country or "", amount=volunteer.fee_amount or 0.0, payment_date=(volunteer.fee_paid_at or volunteer.created_at).date(), number=volunteer.mecenat_receipt_number or "", )