Skip to content

Boucle infinie d'extournes quand fr_ctc_auto_reverse est actif et que la plateforme rejette systématiquement #87

Description

@am-technix

Contexte

  • Module : l10n_fr_einvoicing 18.0, constaté sur le commit 6f61127. Le code concerné est
    inchangé sur ff06ad0 (HEAD de la branche 18.0 au 20/09/2026), où le défaut reste donc présent.
  • Option société : Auto Reverse Invoice if Refused/Rejected (fr_ctc_auto_reverse) activée
  • Plateforme : rejet technique répété sur un même destinataire (non-conformité PDF/A-3 du Factur-X)

Symptôme

Une facture client rejetée par la plateforme est extournée automatiquement. L'extourne est
transmise à son tour, rejetée pour le même motif, puis extournée le lendemain. La chaîne
progresse d'un document par jour, sans fin.

Constaté en production : 10 documents comptables créés en 5 jours sur un seul client, répartis
sur deux chaînes parallèles.

F68828  (facture d'origine)
  -> AV/2026/0198  extourne, rejetée
    -> F68883      extourne de l'avoir, rejetée
      -> AV/2026/0200  rejetée
        -> F68890      rejetée
          -> AV/2026/0202  ...

Effet de bord comptable : la chaîne alterne facture et avoir en partant d'une facture et en
terminant par un avoir. Le net est donc un avoir, qui annule la créance d'origine. Le client
ne doit plus rien alors que la facture initiale est toujours due.

Mécanisme

_in_cron() importe un CDAR de statut rejected ou refused et appelle _auto_reverse_invoice(),
qui crée une extourne postée. Le document produit est repris par _out_cron() et retransmis à la
plateforme, qui le rejette pour le même motif que l'original. Le cycle recommence au passage suivant.

La garde existante est inopérante dans ce cas :

# fr_einvoicing_flow.py:924
if move.reversal_move_ids:
    msg = f"No auto-reversal because invoice ... has already been reversed"
    return False

Elle empêche d'extourner deux fois le même document, mais chaque extourne est un document neuf
qui n'a jamais été extourné. La condition n'est donc jamais vraie le long de la chaîne.

Le marquage fr_einvoicing_internal = True posé par _auto_reverse_invoice() n'empêche pas la
retransmission : il ajoute BT-21 = BAR / ARCHIVEONLY au document, mais _out_cron() génère et
envoie le flux comme pour n'importe quelle autre pièce.

Pistes de correction

  1. Ne pas extourner une extourne automatique. Tester move.fr_einvoicing_internal, ou remonter
    la chaîne via reversed_entry_id, avant d'appeler _auto_reverse_invoice().

  2. Ne pas transmettre les documents fr_einvoicing_internal. Un document marqué ARCHIVEONLY
    n'a pas vocation à repartir vers la plateforme ; l'exclure de _out_cron() casse la boucle même
    si le point 1 est contourné.

  3. Distinguer rejected de refused. Les deux statuts déclenchent aujourd'hui le même
    traitement, alors qu'ils sont de nature opposée :

    • refused est un refus métier du destinataire, l'avoir est justifié ;
    • rejected est un rejet technique, le document n'a jamais été remis au destinataire. La
      réponse attendue est de corriger puis de renvoyer la même facture, pas d'émettre un avoir.

    Un rejet de format ne devrait pas produire de pièce comptable.

À défaut d'un correctif, un garde-fou sur le nombre d'extournes automatiques consécutives par
facture d'origine éviterait au moins l'emballement.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions