Volunteer.mecenat_receipt_number (migration 0020, unique) : année du paiement + rang sur 5 chiffres (202600001…), une seule numérotation pour toutes les éditions, repartant à 1 chaque année. Attribué à la confirmation du paiement, ou au premier export pour les bénévoles payés avant (dans l'ordre des paiements), puis jamais réattribué — un reçu regénéré garde son numéro. Attribution sérialisée par un verrou pg_advisory_xact_lock (webhook et sondage peuvent confirmer deux paiements à la fois). Imprimé centré dans la case « Numéro d'ordre du reçu » en page 1 du cerfa. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
57 lines
2.4 KiB
Python
57 lines
2.4 KiB
Python
"""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 "",
|
|
)
|