L'assignation
Étape 1 — L'assignation
Assigner un item, c'est désigner officiellement son acheteur. C'est l'acte fondateur de tout le post-vente : sans assignation, pas de facture, pas de paiement, pas de reversement.
La machine à états : les statuts d'un item
Un item passe sa vie à changer de statut. Voici le chemin du « happy path » (tout se passe bien).
flowchart LR
a["OPEN"] --> b["CLOSED"]
b --> c["ASSIGNED"]
c --> d["INVOICED"]
d --> e["PAID"]
e --> f["CONFIRMED"]
f --> g["COMMISSIONED"]
g --> h["SUBMITTED / EXECUTED"]
h --> i["SETTLED"]
| Statut | Signification |
|---|---|
OPEN | L'enchère (ou l'offre directe) est en cours. Les acheteurs enchérissent. |
CLOSED | La date de fin est passée, l'enchère est fermée mais le gagnant n'est pas encore officiellement désigné (utile notamment en full-service où un humain valide d'abord). |
ASSIGNED | L'item est attribué au gagnant : on enregistre son enchère gagnante (winningBidShortId), son organisation et le prix final. C'est ici que le compte payeur MangoPay de l'acheteur est créé si besoin. |
INVOICED | Les factures ont été créées et validées. L'acheteur reçoit l'email de confirmation d'achat avec les instructions de paiement. |
PAID | Le paiement de l'acheteur a été reçu et rapproché de la facture. |
CONFIRMED | La vente est confirmée administrativement (manuellement ou via CONFIRM_AUTO). C'est le feu vert pour reverser le vendeur. |
COMMISSIONED | La facture de commission du vendeur a été générée ; sur MangoPay, les transferts de répartition sont lancés. |
SUBMITTED / EXECUTED | Le reversement au vendeur est soumis (virement préparé ou payout MangoPay envoyé) puis exécuté par la banque/MangoPay. |
SETTLED | Tout est réglé : l'argent est arrivé chez le vendeur, les factures sont soldées. Fin du parcours. |
Et les chemins de sortie quand ça se passe moins bien :
flowchart LR
u["UNSOLD"]
na["NOT_ASSIGNED"]
np["NOT_PAID"]
r["REIMBURSED"]
| Statut | Signification |
|---|---|
UNSOLD | Aucune enchère ni offre reçue. Le vendeur reçoit un email « invendu » (self-service). |
NOT_ASSIGNED | Il y avait des enchères mais pas d'attribution : prix de réserve non atteint, offre refusée, ou décision manuelle NO_ASSIGN (l'acheteur pressenti reçoit alors l'email « vente déclinée »). |
NOT_PAID | L'acheteur n'a pas payé dans les délais. Déclenché manuellement ou automatiquement (NO_PAY_AUTO). Vendeur et acheteur sont notifiés. |
REIMBURSED | La vente est annulée après paiement : l'acheteur est remboursé (action REIMBURSE sur la facture). |
Liste complète des statuts (enum ItemStatuses)
auctelia-models/src/schemas/constants.js
Avant-vente : DRAFT, TO_CHECK, TO_CHECK_BO (vérif back-office), TO_CHECK_EP (vérif vendeur), REFUSED, TO_MODIFY, VALIDATED, REMOVED, DELETED.
Vente & post-vente : OPEN, CLOSED, UNSOLD, NOT_ASSIGNED, ASSIGNED, INVOICED, PAID, NOT_PAID, CONFIRMED, COMMISSIONED, SUBMITTED, EXECUTED, REIMBURSED, SETTLED.
Les statuts post-clôture sont regroupés dans ItemAssignationFullStatuses.
Qui déclenche l'assignation ?
Personne n'appuie sur un bouton à minuit : un job automatique scanne les items en continu (environ toutes les 10 secondes) et déclenche :
- Ouverture (
performOpen) — ItemVALIDATEDdont la date de début est passée →OPEN. - Prolongation (
performExtend) — Enchère terminée sans aucune enchère → prolongée automatiquement (par défaut +20 min, configurable par shop viaauctionExtendTimeInMinutesWithNoBid), une seule fois. - Clôture (
performClose) — Date de fin passée → selon le cas : assignation automatique, invendu, ou attente de validation humaine. - Assignation (
performAssign) — Automatique (self-service avec réserve atteinte) ou manuelle (action admin via l'API).
Self-service vs Full-service : deux philosophies
Le vendeur gère sa vente en autonomie. À la clôture, si le prix de réserve est atteint, tout s'enchaîne sans intervention humaine :
- L'enchère gagnante est enregistrée sur l'item (
setWinningBid: id de l'enchère, organisation gagnante, prix final) ; - Statut →
ASSIGNED; - Le compte payeur MangoPay de l'acheteur est créé s'il n'existe pas (
getOrCreatePayerAccount) ; - Les factures sont créées : circuit V2 via
createInvoicesFromSeance()si l'item porte un contrat et uninvoiceMode, sinon circuit V1 historique (createInvoicetypeTHIRD_PARTY, catégorieSELF_SERVICE) ; - Chaque facture est validée (
performInvoice) → statut itemINVOICED; - L'acheteur reçoit l'email de confirmation d'achat avec les modalités de paiement.
Si la réserve n'est pas atteinte ou s'il n'y a pas d'enchère : email « invendu » au vendeur et statut UNSOLD.
Le shop accompagne le vendeur. À la clôture, rien n'est automatique :
- L'item reste
CLOSEDet le vendeur reçoit l'email d'« acceptation de vente » (acceptanceSale) : il décide s'il accepte le prix atteint ; - Un humain (admin/account manager) déclenche ensuite l'assignation via l'API (
performAssignFullItem) : enregistrement du gagnant, statutASSIGNED, création du compte payeur MangoPay ; - Les factures sont créées par le back-office (pas automatiquement à l'assignation) ;
- Si le vendeur refuse : action
NO_ASSIGN→ email « vente déclinée » à l'acheteur, effacement du gagnant, statutNOT_ASSIGNED.
Fichiers du workflow d'assignation
Workflow item : jobs/src/helpers/itemWorkflow.js — performClose(), performAssign(), performAssignSelfItem(), performAssignFullItem(), performNoAssign(), performNoSell(), setWinningBid().
Job de scan : jobs/src/controllers/jobs/items/index.js — openItems, extendItems, closeExtendedItems, closeAuctionBiddedItems, closeDirectOfferItems.
Notifications de séance (emails gagnants/perdants/vendeurs ~5 min après la fin) : jobs/src/controllers/jobs/seances.js → seanceWorkflow.performNotify().
Verrouillage anti-doublon : lock distribué Redis (lockEntity/unlockEntity, scope item, auto-libéré après 5 min).
Champs posés sur l'item : indexedMeta.saleInformation.winningBidShortId, winningBidOrganisationShortId, pricesCents.current.
Les cas particuliers
CLOSED (ou passe NOT_ASSIGNED). Le vendeur peut ensuite accepter quand même l'offre (validation via REQUEST_SALE_VALIDATION → TO_CHECK_EP) ou décliner. En full-service, l'email acceptanceSale lui présente la décision à prendre.Certains items se vendent à prix négocié plutôt qu'aux enchères (saleType = DIRECT_OFFER). Une offre suit son propre circuit de validation : NEW → TO_CHECK_SHOP (le shop vérifie) → TO_CHECK_SELLER (le vendeur décide) → APPROVED / REFUSED / EXPIRED.
À l'approbation : toutes les autres offres en cours sont refusées, l'acheteur reçoit l'email de confirmation (directOfferConfirmBuyer), l'offre gagnante est posée sur l'item (setWinningOffer) et l'assignation classique démarre (performAssign). La facturation est ensuite identique.
jobs/src/helpers/offerWorkflow.js — actions CHECK_SHOP, CHECK_SELLER, APPROVE, REFUSE, EXPIRE. Champ : indexedMeta.saleInformation.winningOfferShortId.Avant facturation : action NO_ASSIGN (performNoAssign) → email « vente déclinée » à l'acheteur, effacement de l'enchère gagnante, statut NOT_ASSIGNED. L'item peut ensuite être réassigné manuellement à un autre acheteur.
Après facturation/paiement : on ne « désassigne » plus, on passe par le circuit financier : annulation de facture (CANCEL), avoir (CREDIT_NOTE) et/ou remboursement (REIMBURSE) — voir le chapitre Avoirs & remboursements.
L'enlèvement physique du lot passe par le service appointments. Deux types de rendez-vous existent : VISIT (visite avant achat) et PICKUP (enlèvement après achat). Ils sont reliés à l'item via pickupShortIds / visitShortIds.
Les créneaux d'enlèvement ne sont pas créés automatiquement à l'assignation : ils sont configurés (adresse, plages horaires, type d'inscription REGISTRATION/APPOINTMENT/OPEN) et l'information d'enlèvement (adresse + horaires) est injectée dans les factures et les emails de confirmation d'achat (via buildPickupData dans les notifications, et pickupLines dans le PDF de facture).
| Moment | Template | Destinataire |
|---|---|---|
| Invendu (pas d'enchère / réserve) | self-noBidsSeller (« unsoldSeller ») | Vendeur |
| Clôture full-service (décision à prendre) | self-acceptanceSale | Vendeur |
| Vente déclinée | declinedSaleBuyer | Acheteur |
| Confirmation d'achat (virement classique) | full-confirmationPurchaseBuyer (IBAN/BIC du shop, horaires d'enlèvement) | Acheteur |
| Confirmation d'achat (MangoPay) | full-confirmationPurchaseBuyerMangopay (IBAN virtuel, URL de paiement carte) | Acheteur |
| Paiement reçu | full-confirmPaidBuyer / self-confirmPaidSeller | Acheteur / Vendeur |
| Reversement (facture de commission + IBAN) | self-confirmRetribution | Vendeur |
| Impayé | full-unpaidBuyer / self-unpaidSeller | Acheteur / Vendeur |
jobs/src/helpers/invoiceNotification.js et jobs/src/helpers/itemNotification.js