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
@@ -454,13 +454,13 @@ def test_export_volunteers_is_coordinator_only(edition, make_account, as_account
|
||||
assert resp.status_code == 403
|
||||
|
||||
|
||||
def _mark_mecenat_paid(session, volunteer, *, amount=50.0):
|
||||
def _mark_mecenat_paid(session, volunteer, *, amount=50.0, paid_at=dt.datetime(2026, 3, 5, 10, 0)):
|
||||
"""Simule l'état d'un bénévole ayant payé un type de paiement « mécénat »
|
||||
(voir `app/api/forms.py:_promote_pending`) — `mecenat_address` est le
|
||||
marqueur qui distingue ce chemin d'un simple paiement classique."""
|
||||
volunteer.fee_payment_status = FeePaymentStatus.PAID
|
||||
volunteer.fee_amount = amount
|
||||
volunteer.fee_paid_at = dt.datetime(2026, 3, 5, 10, 0)
|
||||
volunteer.fee_paid_at = paid_at
|
||||
volunteer.mecenat_address = "12 rue des Lilas"
|
||||
volunteer.mecenat_city = "Paris"
|
||||
volunteer.mecenat_postal_code = "75020"
|
||||
@@ -512,6 +512,62 @@ def test_mecenat_receipts_pdf_includes_only_paid_mecenat_donors(
|
||||
assert "Petit" not in donor_page_text
|
||||
|
||||
|
||||
def test_mecenat_receipts_are_numbered_by_payment_year_and_order(client, edition, session):
|
||||
later = make_volunteer(session, edition, first_name="Alex", last_name="Martin")
|
||||
_mark_mecenat_paid(session, later, paid_at=dt.datetime(2026, 4, 1, 9, 0))
|
||||
earlier = make_volunteer(session, edition, first_name="Sam", last_name="Petit")
|
||||
_mark_mecenat_paid(session, earlier, paid_at=dt.datetime(2026, 3, 1, 9, 0))
|
||||
|
||||
resp = client.get(_url(edition, "/mecenat-receipts"))
|
||||
assert resp.status_code == 200
|
||||
|
||||
session.refresh(later)
|
||||
session.refresh(earlier)
|
||||
# Attribués dans l'ordre des paiements, pas de l'ordre alphabétique.
|
||||
assert earlier.mecenat_receipt_number == "202600001"
|
||||
assert later.mecenat_receipt_number == "202600002"
|
||||
|
||||
reader = PdfReader(io.BytesIO(resp.content))
|
||||
# Trié par nom : Martin (pages 1-2) puis Petit (pages 3-4).
|
||||
assert "202600002" in reader.pages[0].extract_text()
|
||||
assert "202600001" in reader.pages[2].extract_text()
|
||||
|
||||
|
||||
def test_mecenat_receipt_number_is_stable_across_regenerations(client, edition, session):
|
||||
volunteer = make_volunteer(session, edition, first_name="Alex", last_name="Martin")
|
||||
_mark_mecenat_paid(session, volunteer)
|
||||
client.get(_url(edition, "/mecenat-receipts"))
|
||||
session.refresh(volunteer)
|
||||
first = volunteer.mecenat_receipt_number
|
||||
|
||||
newcomer = make_volunteer(session, edition, first_name="Sam", last_name="Petit")
|
||||
_mark_mecenat_paid(session, newcomer)
|
||||
client.get(_url(edition, "/mecenat-receipts"))
|
||||
session.refresh(volunteer)
|
||||
session.refresh(newcomer)
|
||||
assert volunteer.mecenat_receipt_number == first
|
||||
assert newcomer.mecenat_receipt_number == "202600002"
|
||||
|
||||
|
||||
def test_mecenat_receipt_numbers_continue_the_existing_sequence(client, edition, session):
|
||||
numbered = make_volunteer(session, edition, first_name="Alex", last_name="Martin")
|
||||
_mark_mecenat_paid(session, numbered)
|
||||
numbered.mecenat_receipt_number = "202600041"
|
||||
session.add(numbered)
|
||||
session.commit()
|
||||
other = make_volunteer(session, edition, first_name="Sam", last_name="Petit")
|
||||
_mark_mecenat_paid(session, other)
|
||||
# Une autre année repart à 1.
|
||||
next_year = make_volunteer(session, edition, first_name="Jo", last_name="Dupuis")
|
||||
_mark_mecenat_paid(session, next_year, paid_at=dt.datetime(2027, 1, 2, 9, 0))
|
||||
|
||||
client.get(_url(edition, "/mecenat-receipts"))
|
||||
session.refresh(other)
|
||||
session.refresh(next_year)
|
||||
assert other.mecenat_receipt_number == "202600042"
|
||||
assert next_year.mecenat_receipt_number == "202700001"
|
||||
|
||||
|
||||
def test_mecenat_receipts_is_422_without_any_paid_donor(client, edition):
|
||||
resp = client.get(_url(edition, "/mecenat-receipts"))
|
||||
assert resp.status_code == 422
|
||||
|
||||
Reference in New Issue
Block a user