Numéro d'ordre unique sur chaque reçu fiscal mécénat
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>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
5a0209ce43
commit
ccd769d04e
@@ -1,13 +1,46 @@
|
||||
"""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`)."""
|
||||
_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(
|
||||
@@ -19,4 +52,5 @@ def receipt_for(volunteer: Volunteer) -> MecenatReceipt:
|
||||
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 "",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user