08 oct. 2025·8 min de lecture

Numérotation concurrente des factures : éviter doublons et numéros manquants

Apprenez des approches pratiques pour une numérotation de factures sûre en concurrence afin que plusieurs utilisateurs puissent créer des factures ou tickets sans doublons ni trous inattendus.

Numérotation concurrente des factures : éviter doublons et numéros manquants

Ce qui se passe quand deux personnes créent des enregistrements en même temps

Imaginez un bureau animé à 16h55. Deux personnes terminent une facture et cliquent sur Enregistrer à moins d'une seconde d'écart. Les deux écrans affichent brièvement « Invoice #1042 ». Un enregistrement passe, l'autre échoue, ou pire, les deux sont sauvegardés avec le même numéro. C'est le symptôme le plus courant en conditions réelles : des doublons qui n'apparaissent qu'en charge.

Les tickets se comportent de la même façon. Deux agents créent un nouveau ticket pour le même client en même temps, et votre système « choisit le numéro suivant » en regardant le dernier enregistrement. Si les deux requêtes lisent la même valeur « la plus récente » avant qu'aucune n'écrive, elles peuvent choisir toutes les deux le même numéro suivant.

Le second symptôme est plus subtil : des numéros sautés. Vous pouvez voir #1042, puis #1044, et #1043 manque. Cela arrive souvent après une erreur ou une nouvelle tentative. Une requête réserve un numéro, puis la sauvegarde échoue à cause d'une erreur de validation, d'un timeout, ou parce que l'utilisateur ferme l'onglet. Ou bien un job en arrière-plan retente après une coupure réseau et récupère un nouveau numéro alors que la première tentative avait déjà consommé le précédent.

Pour les factures, c'est important car la numérotation fait partie de la piste d'audit. Les comptables s'attendent à ce que chaque facture soit identifiée de manière unique, et les clients peuvent référencer des numéros de facture dans des paiements ou des emails de support. Pour les tickets, le numéro est la référence utilisée dans les conversations, rapports et exports. Les doublons créent de la confusion. Les numéros manquants peuvent poser des questions lors des revues, même s'il n'y a rien de malhonnête.

Voici l'attente clé à définir tôt : toutes les méthodes de numérotation ne peuvent pas être à la fois sûres en concurrence et sans trous. La numérotation sûre en concurrence (pas de doublons, même avec beaucoup d'utilisateurs) est réalisable et devrait être non négociable. La numérotation sans trous est également possible, mais elle nécessite des règles supplémentaires et change souvent la façon dont vous gérez les brouillons, les échecs et les annulations.

Une bonne façon d'encadrer le problème est de se demander ce que vos numéros doivent garantir :

  • Ne doivent jamais se répéter (toujours uniques)
  • Doivent être majoritairement croissants (c'est un plus)
  • Ne doivent jamais sauter (seulement si vous le concevez ainsi)

Une fois la règle choisie, la solution technique devient beaucoup plus simple à sélectionner.

Pourquoi doublons et trous arrivent

La plupart des applications suivent un schéma simple : l'utilisateur clique sur Enregistrer, l'app demande le prochain numéro de facture ou de ticket, puis insère le nouvel enregistrement avec ce numéro. Ça fonctionne parfaitement quand une seule personne le fait.

Le problème commence quand deux sauvegardes se produisent presque en même temps. Les deux requêtes peuvent arriver à l'étape « obtenir le numéro suivant » avant qu'aucune n'ait fini l'insertion. Si les deux lectures voient la même valeur « suivante », elles essaient d'écrire le même numéro. C'est une condition de course : le résultat dépend du calendrier, pas de la logique.

Un scénario typique ressemble à ceci :

  • Request A lit le prochain numéro : 1042
  • Request B lit le prochain numéro : 1042
  • Request A insère la facture 1042
  • Request B insère la facture 1042 (ou échoue si une contrainte d'unicité la bloque)

Les doublons surviennent lorsque rien dans la base de données n'empêche la seconde insertion. Si vous ne vérifiez « ce numéro est-il pris ? » que dans le code applicatif, vous pouvez encore perdre la course entre la vérification et l'insertion.

Les trous sont un autre problème. Ils apparaissent lorsque votre système « réserve » un numéro mais que l'enregistrement ne devient jamais une facture ou un ticket réel et engagé. Causes courantes : paiements échoués, erreurs de validation découvertes tardivement, timeouts, ou un utilisateur qui ferme l'onglet après l'attribution du numéro. Même si l'insertion échoue et que rien n'est sauvegardé, le numéro peut déjà être consommé.

La concurrence invisible aggrave cela parce qu'il ne s'agit rarement de « deux humains qui cliquent sur Enregistrer ». Vous pouvez aussi avoir :

  • des clients API créant des enregistrements en parallèle
  • des imports qui s'exécutent en lots
  • des jobs d'arrière-plan générant des factures la nuit
  • des retries depuis des apps mobiles avec une connexion instable

Ainsi, les causes profondes sont : (1) des conflits de timing quand plusieurs requêtes lisent la même valeur de compteur, et (2) des numéros alloués avant d'être certain que la transaction réussira. Tout plan pour une numérotation sûre en concurrence doit décider quel résultat vous pouvez tolérer : pas de doublons, pas de trous, ou les deux, et dans quelles circonstances (brouillons, retries, annulations).

Décidez votre règle de numérotation avant de choisir une solution

Avant de concevoir la numérotation sûre en concurrence, notez ce que le numéro doit signifier pour votre métier. L'erreur la plus fréquente est de choisir d'abord une méthode technique, puis de découvrir que les règles comptables ou légales attendent autre chose.

Commencez par séparer deux objectifs souvent confondus :

  • Unique : aucune facture ou ticket ne partage jamais le même numéro.
  • Sans trous : les numéros sont uniques et aussi strictement consécutifs (pas de numéros manquants).

Beaucoup de systèmes réels visent le « unique uniquement » et acceptent des trous. Les trous peuvent arriver pour des raisons normales : un utilisateur ouvre un brouillon et l'abandonne, un paiement échoue après réservation du numéro, ou un enregistrement est créé puis annulé. Pour les tickets, les trous importent rarement. Même pour les factures, de nombreuses équipes acceptent des trous si elles peuvent les expliquer avec une piste d'audit (annulé, void, test, etc.). La numérotation sans trous est possible, mais elle impose des règles supplémentaires et ajoute souvent de la friction.

Ensuite, décidez de la portée du compteur. De petits détails modifient beaucoup le design :

  • Une séquence globale pour tout, ou des séquences séparées par entreprise/tenant ?
  • Réinitialiser chaque année (2026-000123) ou ne jamais réinitialiser ?
  • Différentes séries pour factures vs avoirs vs tickets ?
  • Besoin d'un format lisible par l'humain (préfixes, séparateurs) ou juste un numéro interne ?

Un exemple concret : un produit SaaS multi-client peut nécessiter des numéros de facture uniques par entreprise et réinitialisés chaque année, tandis que les tickets sont uniques globalement et ne sont jamais réinitialisés. Ce sont deux compteurs différents avec des règles distinctes, même si l'interface ressemble.

Si vous avez vraiment besoin de « sans trous », soyez explicite sur les événements autorisés après l'attribution d'un numéro. Par exemple : une facture peut-elle être supprimée, ou seulement annulée ? Les utilisateurs peuvent-ils enregistrer des brouillons sans numéro et n'attribuer le numéro qu'à l'approbation finale ? Ces choix comptent souvent plus que la technique de base de données.

Rédigez la règle en une courte spécification avant de construire :

  • Quels types d'enregistrements utilisent la séquence ?
  • Qu'est-ce qui rend un numéro « utilisé » (brouillon, envoyé, payé) ?
  • Quelle est la portée (global, par entreprise, par année, par série) ?
  • Comment gérez-vous les annulations et corrections ?

Dans AppMaster, ce type de règle se place à côté de votre modèle de données et du flux métier, pour que l'équipe implémente le même comportement partout (API, web UI, mobile) sans surprises.

Approches courantes et ce que chacune garantit

Quand on parle de « numérotation des factures », on mélange souvent deux objectifs : (1) ne jamais générer deux fois le même numéro, et (2) ne jamais avoir de trous. La plupart des systèmes peuvent garantir le premier assez facilement. Le second est beaucoup plus difficile, car des trous peuvent apparaître n'importe quand qu'une transaction échoue, qu'un brouillon soit abandonné, ou qu'un enregistrement soit annulé.

Approche 1 : séquence de base de données (unicité rapide)

Une séquence PostgreSQL est la façon la plus simple d'obtenir des numéros uniques et croissants sous charge. Elle évolue bien car la base est conçue pour distribuer des valeurs de séquence rapidement, même avec beaucoup d'utilisateurs.

Ce que vous obtenez : unicité et ordre (majoritairement croissant). Ce que vous n'obtenez pas : l'absence de trous. Si une insertion échoue après l'attribution d'un numéro, ce numéro est « brûlé » et vous verrez un trou.

Approche 2 : contrainte d'unicité + retry (laissez la base décider)

Ici vous générez un numéro candidat (depuis la logique applicative), vous essayez de le sauvegarder, et vous comptez sur une contrainte UNIQUE pour rejeter les doublons. En cas de conflit, vous réessayez avec un nouveau numéro.

Cela peut fonctionner, mais devient bruyant sous forte concurrence. Vous pouvez finir avec plus de retries, plus de transactions échouées et des pics difficiles à diagnostiquer. Cela ne garantit pas non plus l'absence de trous à moins d'ajouter des règles strictes de réservation, ce qui augmente la complexité.

Approche 3 : ligne de compteur avec verrouillage (viser le sans trous)

Si vous avez réellement besoin d'une numérotation sans trous, le patron habituel est une table de compteurs dédiée (une ligne par portée, par exemple par année ou par entreprise). Vous verrouillez cette ligne dans une transaction, l'incrémentez et utilisez la nouvelle valeur.

C'est la méthode la plus proche du sans trous dans une conception de base de données classique, mais cela a un coût : cela crée un point chaud unique sur lequel tous les écrivains doivent attendre. Cela augmente aussi le risque opérationnel (transactions longues, timeouts, deadlocks).

Approche 4 : service de réservation séparé (pour cas spécifiques)

Un « service de numérotation » autonome peut centraliser les règles entre plusieurs applications ou bases de données. Cela vaut le coup quand vous avez plusieurs systèmes émettant des numéros et que vous ne pouvez pas consolider les écritures.

Le compromis est un risque opérationnel : vous avez ajouté un service supplémentaire qui doit être correct, hautement disponible et cohérent.

Voici une façon pratique de penser aux garanties pour la numérotation sûre en concurrence :

  • Séquence : unique, rapide, accepte les trous
  • Unique + retry : unique, simple à faible charge, peut thrash sous forte charge
  • Ligne de compteur verrouillée : peut être sans trous, plus lente sous forte concurrence
  • Service séparé : flexible entre systèmes, complexité et modes de défaillance élevés

Si vous construisez cela dans un outil no-code comme AppMaster, les mêmes choix s'appliquent : la base de données est l'endroit où se trouvent les garanties. La logique applicative peut aider avec les retries et des messages d'erreur clairs, mais la garantie finale doit venir des contraintes et des transactions.

Pas à pas : empêcher les doublons avec des séquences et des contraintes d'unicité

Transformez les règles en workflows
Automatisez la création de factures, les retries et les changements d'état avec des étapes claires et auditées.
Démarrer

Si votre but principal est d'empêcher les doublons (et non de garantir l'absence de trous), le patron le plus simple et fiable est : laissez la base générer un identifiant interne, et imposez l'unicité sur le numéro visible client.

Commencez par bien séparer les deux concepts. Utilisez une valeur générée par la base (identity/sequence) comme clé primaire pour les jointures, les modifications et les exports. Conservez invoice_no ou ticket_no comme colonne séparée affichée aux utilisateurs.

Une configuration pratique sous PostgreSQL

Voici une approche PostgreSQL courante qui garde la logique du « numéro suivant » à l'intérieur de la base, où la concurrence est gérée correctement.

-- Internal, never-shown primary key
create table invoices (
  id bigint generated always as identity primary key,
  invoice_no text not null,
  created_at timestamptz not null default now()
);

-- Business-facing uniqueness guarantee
create unique index invoices_invoice_no_uniq on invoices (invoice_no);

-- Sequence for the visible number
create sequence invoice_no_seq;

Générez ensuite le numéro d'affichage au moment de l'INSERT (pas en faisant select max(invoice_no) + 1). Un patron simple est de formater la valeur de séquence dans l'INSERT :

insert into invoices (invoice_no)
values (
  'INV-' || lpad(nextval('invoice_no_seq')::text, 8, '0')
)
returning id, invoice_no;

Même si 50 utilisateurs cliquent sur « Créer la facture » en même temps, chaque insert reçoit une valeur de séquence différente, et l'index unique empêche tout doublon accidentel.

Que faire en cas de collision

Avec une séquence simple, les collisions sont rares. Elles arrivent généralement quand vous ajoutez des règles supplémentaires comme « réinitialisation par année », « par tenant », ou des numéros modifiables par l'utilisateur. C'est pourquoi la contrainte d'unicité est toujours importante.

Au niveau applicatif, gérez une erreur de violation d'unicité avec une petite boucle de retry bornée :

  • Tenter l'insertion
  • Si violation de contrainte unique sur invoice_no, réessayer
  • Arrêter après un petit nombre de tentatives et afficher une erreur claire

Cela fonctionne bien parce que les retries ne s'activent que quand quelque chose d'inhabituel se produit, comme deux chemins de code produisant le même format de numéro.

Réduire la fenêtre de course

Ne calculez pas le numéro dans l'UI, et ne « réservez » pas de numéros en lisant d'abord puis en insérant plus tard. Générez-le aussi près que possible de l'écriture en base.

Si vous utilisez AppMaster avec PostgreSQL, vous pouvez modéliser l'id comme clé primaire identity dans le Data Designer, ajouter une contrainte unique pour invoice_no, et générer invoice_no pendant le flux de création pour qu'il ait lieu conjointement avec l'insert. Ainsi, la base reste la source de vérité et les problèmes de concurrence restent confinés là où PostgreSQL est le plus fort.

Pas à pas : construire un compteur sans trous avec verrouillage de ligne

Ajoutez l'unicité de la bonne manière
Modélisez visuellement des tables PostgreSQL et des contraintes d'unicité, sans écrire de migrations manuellement.
Commencer

Si vous avez vraiment besoin de numéros sans trous (aucun numéro de facture ou ticket manquant), vous pouvez utiliser une table de compteurs transactionnelle et le verrouillage de ligne. L'idée est simple : une seule transaction à la fois peut prendre le numéro suivant pour une portée donnée, donc les numéros sont attribués dans l'ordre.

D'abord, décidez de la portée. Beaucoup d'équipes ont besoin de séquences séparées par entreprise, par année ou par série (INV vs CRN). La table de compteurs stocke le dernier numéro utilisé pour chaque portée.

Voici un patron pratique pour la numérotation sûre en concurrence avec des verrous de ligne PostgreSQL :

  1. Créez une table, par exemple number_counters, avec des colonnes company_id, year, series, last_number, et une clé unique sur (company_id, year, series).
  2. Démarrez une transaction en base.
  3. Verrouillez la ligne de compteur pour votre portée avec SELECT last_number FROM number_counters WHERE ... FOR UPDATE.
  4. Calculez next_number = last_number + 1, mettez à jour la ligne de compteur à last_number = next_number.
  5. Insérez la facture ou le ticket en utilisant next_number, puis commit.

La clé est FOR UPDATE. Sous charge, vous n'obtenez pas de doublons. Vous n'obtenez pas non plus de trous dus à « deux utilisateurs ayant le même numéro », parce que la seconde transaction ne peut pas lire et incrémenter la même ligne de compteur tant que la première n'a pas commit (ou rollback). La seconde requête attendra brièvement. Cet attente est le prix du sans trous.

Initialiser une nouvelle portée

Il faut aussi prévoir la première apparition d'une portée (nouvelle entreprise, nouvelle année, nouvelle série). Deux options courantes :

  • Pré-créer les lignes de compteur à l'avance (par exemple, créer les lignes de l'année suivante en décembre).
  • Créer à la demande : tenter d'insérer la ligne de compteur avec last_number = 0, et si elle existe déjà, retomber sur le flux classique de lock-and-increment.

Si vous construisez cela dans un outil no-code comme AppMaster, gardez toute la séquence « lock, increment, insert » dans une seule transaction dans votre logique métier, pour que tout se fasse ou rien ne se fasse.

Cas limites : brouillons, sauvegardes échouées, annulations et modifications

La plupart des bugs de numérotation apparaissent dans les parties désordonnées : brouillons non publiés, sauvegardes échouées, factures annulées, et enregistrements modifiés après que quelqu'un ait déjà vu le numéro. Si vous voulez une numérotation sûre en concurrence, il vous faut une règle claire sur le moment où le numéro devient « réel ».

La décision la plus importante est le moment d'attribution. Si vous attribuez un numéro dès que quelqu'un clique sur « Nouvelle facture », vous aurez des trous dus aux brouillons abandonnés. Si vous n'attribuez qu'au moment où la facture est finalisée (postée, émise, envoyée ou ce que signifie « final » dans votre métier), vous pouvez garder les numéros plus serrés et plus faciles à expliquer.

Les sauvegardes échouées et les rollbacks sont là où les attentes entrent souvent en conflit avec le comportement de la base. Avec une séquence typique, une fois un numéro pris, il est pris, même si la transaction échoue ensuite. C'est normal et sûr, mais cela peut créer des trous. Si votre politique exige des numéros sans trous, le numéro doit être attribué seulement à l'étape finale et seulement si la transaction commit. Cela signifie généralement verrouiller une ligne de compteur unique, écrire le numéro final, et committer en une seule unité. Si une étape échoue, rien n'est attribué.

Les annulations et voids ne doivent presque jamais « réutiliser » un numéro. Conservez le numéro et changez le statut. Les auditeurs et clients s'attendent à ce que l'historique reste cohérent, même lorsqu'un document est corrigé.

Les modifications sont plus simples : une fois qu'un numéro est visible en dehors du système, considérez-le comme permanent. Ne renumérotez jamais une facture ou un ticket après qu'il ait été partagé, exporté ou imprimé. Si vous devez corriger, créez un nouveau document et référencez l'ancien (par exemple, un avoir), mais ne réécrivez pas l'histoire.

Un ensemble de règles pratiques adopté par de nombreuses équipes :

  • Les brouillons n'ont pas de numéro final (utilisez un ID interne ou « DRAFT »).
  • Attribuez le numéro seulement lors de la « Publication/Émission », à l'intérieur de la même transaction que le changement de statut.
  • Les annulations et suppressions conservent le numéro, mais reçoivent un statut et une raison clairs.
  • Les numéros imprimés/emaillés ne changent jamais.
  • Les imports conservent les numéros originaux et mettent à jour le compteur pour démarrer après la valeur maximale importée.

Les migrations et imports méritent une attention particulière. Si vous migrez depuis un autre système, conservez les numéros de facture existants tels quels, puis positionnez votre compteur pour démarrer après la valeur maximale importée. Décidez aussi quoi faire avec des formats conflictuels (préfixes différents par année). Il est généralement préférable de stocker le « numéro d'affichage » exactement comme il était et de garder une clé primaire interne séparée.

Exemple : un helpdesk crée des tickets rapidement, mais beaucoup sont des brouillons. Attribuez le numéro du ticket uniquement quand l'agent clique sur « Envoyer au client ». Cela évite de gaspiller des numéros sur des brouillons abandonnés et maintient la séquence visible alignée avec la communication réelle au client. Dans un outil no-code comme AppMaster, la même idée s'applique : conservez les brouillons sans numéro public, puis générez le numéro final pendant l'étape « soumettre » du Business Process qui commit la transaction.

Erreurs communes qui provoquent doublons ou trous inattendus

Obtenez du vrai code sans dette
Générez du code prêt pour la production tout en gardant votre logique métier centralisée.
Créer maintenant

La plupart des problèmes de numérotation viennent d'une idée simple : traiter un numéro comme une valeur d'affichage plutôt que comme un état partagé. Quand plusieurs personnes sauvegardent en même temps, le système a besoin d'un lieu clair pour décider du numéro suivant, et d'une règle unique sur ce qui se passe en cas d'échec.

Une erreur classique est d'utiliser SELECT MAX(number) + 1 dans le code applicatif. Ça semble fonctionner en test mono-utilisateur, mais deux requêtes peuvent lire le même MAX avant qu'aucune ne commit. Les deux génèrent la même valeur suivante et vous obtenez un doublon. Même en ajoutant un « check then retry », vous pouvez créer une charge supplémentaire et des pics étranges en période de trafic élevé.

Une autre source courante de doublons est la génération du numéro côté client (navigateur ou mobile) avant la sauvegarde. Le client ne sait pas ce que font les autres utilisateurs et ne peut pas réserver un numéro en toute sécurité si la sauvegarde échoue. Les numéros générés côté client conviennent pour des libellés temporaires comme « Brouillon 12 », mais pas pour des identifiants officiels de facture ou de ticket.

Les trous surprennent les équipes qui supposent que les séquences sont sans trous. Dans PostgreSQL, les séquences sont conçues pour l'unicité, pas pour la continuité parfaite. Des numéros peuvent être sautés quand une transaction rollbacke, quand vous pré-récupérez des IDs, ou quand la base redémarre. C'est un comportement normal. Si votre exigence réelle est « pas de doublons », une séquence plus une contrainte d'unicité est généralement la bonne réponse. Si votre exigence est vraiment « numéros sans trous », vous aurez besoin d'un autre patron (généralement verrouillage de ligne) et d'accepter des compromis sur le débit.

Le verrouillage peut aussi se retourner contre vous s'il est trop large. Un verrou global unique pour toute la numérotation force chaque création dans une file d'attente, même si vous pourriez partitionner les compteurs par entreprise, emplacement ou type de document. Cela peut ralentir le système et donner l'impression que l'enregistrement est « bloqué au hasard ».

Voici les erreurs à vérifier quand vous implémentez une numérotation sûre en concurrence :

  • Utiliser MAX + 1 (ou « trouver le dernier numéro ») sans contrainte d'unicité au niveau base.
  • Générer les numéros finaux côté client, puis essayer de « corriger les conflits plus tard ».
  • S'attendre à ce que les séquences PostgreSQL soient sans trous et traiter les trous comme des erreurs.
  • Verrouiller un compteur partagé unique pour tout, au lieu de partitionner les compteurs quand c'est pertinent.
  • Tester uniquement en mono-utilisateur, si bien que les conditions de course n'apparaissent qu'après le lancement.

Conseil pratique de test : exécutez un test de concurrence simple qui crée 100 à 1 000 enregistrements en parallèle puis vérifie les doublons et les trous inattendus. Si vous construisez dans un outil no-code comme AppMaster, la même règle s'applique : assurez-vous que l'attribution finale du numéro se fait dans une seule transaction côté serveur, pas dans le flux UI.

Vérifications rapides avant le déploiement

Expédiez un système de tickets évolutif
Prototypez un helpdesk où les numéros restent uniques sur web, mobile et API.
Démarrer

Avant de déployer la numérotation des factures ou des tickets, faites une passe rapide sur les éléments qui échouent généralement sous vraie charge. L'objectif est simple : chaque enregistrement reçoit exactement un numéro métier, et vos règles restent vraies même quand 50 personnes cliquent sur « Créer » en même temps.

Voici une check-list pratique avant mise en production pour une numérotation sûre en concurrence :

  • Confirmez que le champ de numéro métier a une contrainte d'unicité en base (pas seulement une vérification UI). C'est votre dernière ligne de défense si deux requêtes entrent en conflit.
  • Assurez-vous que le numéro est attribué à l'intérieur de la même transaction de base qui sauvegarde l'enregistrement. Si l'attribution du numéro et la sauvegarde sont séparées, vous verrez des doublons tôt ou tard.
  • Si vous exigez l'absence de trous, n'attribuez le numéro qu'à la finalisation de l'enregistrement (par exemple, quand la facture est émise, pas quand le brouillon est créé). Les brouillons, paiements échoués et formulaires abandonnés sont les sources les plus fréquentes de trous.
  • Ajoutez une stratégie de retry pour les conflits rares. Même avec du verrouillage de ligne ou des séquences, vous pouvez rencontrer une erreur de sérialisation, un deadlock ou une violation d'unicité dans des cas limites. Un retry simple avec un court backoff suffit souvent.
  • Testez sous charge avec 20 à 100 créations simultanées sur tous les points d'entrée : UI, API publique et imports. Testez des mélanges réalistes comme des rafales, des réseaux lents et des doubles soumissions.

Un moyen rapide de valider votre configuration est de simuler un moment chargé d'un helpdesk : deux agents ouvrent le formulaire « Nouveau ticket », l'un soumet depuis le web pendant qu'un job d'import ajoute des tickets depuis une boîte email. Après le test, vérifiez que tous les numéros sont uniques, au bon format, et que les échecs n'ont pas laissé d'enregistrements à moitié sauvés.

Si vous construisez le workflow dans AppMaster, les mêmes principes s'appliquent : gardez l'attribution du numéro en transaction dans la base, comptez sur les contraintes PostgreSQL, et testez à la fois les actions UI et les endpoints API qui créent la même entité. C'est souvent là que les équipes se sentent à l'aise en tests manuels mais sont surprises le premier jour où les utilisateurs arrivent en masse.

Exemple : tickets d'un helpdesk très occupé et étapes suivantes

Imaginez un support où les agents créent des tickets toute la journée dans l'app web, tandis qu'une intégration crée aussi des tickets depuis un outil de chat et la messagerie. Tout le monde attend des numéros comme T-2026-000123, et s'attend à ce que chaque numéro réfère à exactement un ticket.

Une approche naïve : lire « dernier numéro de ticket », ajouter 1, sauvegarder le ticket. Sous charge, deux requêtes peuvent lire le même « dernier numéro » avant qu'aucune n'enregistre. Les deux calculent le même numéro suivant et vous avez des doublons. Si vous essayez de « corriger » en réessayant après un échec, vous créez souvent des trous sans le vouloir.

La base peut empêcher les doublons même si votre code applicatif est naïf. Ajoutez une contrainte unique sur la colonne ticket_number. Ensuite, quand deux requêtes tentent le même numéro, une insertion échoue et vous pouvez réessayer proprement. C'est le cœur de la numérotation sûre en concurrence : laissez la base de données faire respecter l'unicité, pas l'UI.

La numérotation sans trous change le flux. Si vous exigez l'absence de trous, vous ne pouvez généralement pas attribuer le numéro final à la création du ticket (brouillon). Créez plutôt le ticket avec un statut Draft et sans ticket_number. Attribuez le numéro seulement quand le ticket est finalisé, ainsi les sauvegardes échouées et les brouillons abandonnés ne « brûlent » pas de numéros.

Un schéma simple :

  • tickets : id, created_at, status (Draft, Open, Closed), ticket_number (nullable), finalized_at
  • ticket_counters : key (par exemple "tickets_2026"), next_number

Dans AppMaster, vous pouvez modéliser cela dans le Data Designer avec des types PostgreSQL, puis construire la logique dans le Business Process Editor :

  • Create Ticket : insérer un ticket avec status=Draft et sans ticket_number
  • Finalize Ticket : démarrer une transaction, verrouiller la ligne de compteur, définir ticket_number, incrémenter next_number, commit
  • Test : lancer deux actions « Finalize » en même temps et confirmer qu'il n'y a jamais de doublons

Étapes suivantes : choisissez d'abord votre règle (unique seulement vs vraiment sans trous). Si vous pouvez accepter des trous, une séquence en base plus une contrainte d'unicité suffit généralement et garde le flux simple. Si vous devez être sans trous, déplacez la numérotation vers l'étape de finalisation et traitez le « brouillon » comme un état à part entière. Ensuite, testez sous charge avec plusieurs agents et les intégrations API pour voir le comportement avant que les vrais utilisateurs n'arrivent.

Facile à démarrer
Créer quelque chose d'incroyable

Expérimentez avec AppMaster avec un plan gratuit.
Lorsque vous serez prêt, vous pourrez choisir l'abonnement approprié.

Démarrer
Numérotation concurrente des factures : éviter doublons et numéros manquants | AppMaster