Facturation électronique · France · 2026

La chaîne,
pas le contenu.

Une application de facturation écrite pour se conformer soi-même à l'obligation française. Ce qu'elle démontre n'est pas une liste de fonctions, mais une position de conception, et le chemin qui y mène.

6 877lignes de code
16verrous de schéma
10migrations
3états, un seul critère chacun
1 047lignes d'architecture
Le point de départ

Trois erreurs que presque tous les outils commettent

Avant d'écrire une ligne, il a fallu défaire ce que la documentation ambiante répète. Les trois corrections ci-dessous ne sont pas des détails de vocabulaire : chacune change ce que le logiciel doit faire.

Ce qu'on lit partoutCe qui est vrai
« Logiciel homologué », « facture certifiée » Il n'existe aucune étape d'homologation ni de signature d'une facture par un logiciel de facturation. Ce qui est réglementé, c'est la transmission par une plateforme agréée1. Un moteur appelé en local ne transmet rien et n'apporte donc aucune conformité. L'attestation prévue par l'art. 286-I-3° bis du CGI est autre chose : elle vise les systèmes de caisse, pour des ventes à des particuliers2.
« Envoi via Chorus Pro » Chorus Pro est le portail B2G, celui de la facturation vers la sphère publique. Ce n'est pas une plateforme agréée pour le B2B, et les confondre fait concevoir le mauvais raccordement.
« Franchise en base : une case à cocher » C'est un régime daté, avec deux seuils aux effets distincts : 37 500 € fait basculer au 1er janvier suivant, 41 250 € le jour même3. Le régime doit donc être résolu à la date du document et figé dedans, jamais recalculé à l'affichage.

La troisième a une conséquence que peu d'outils tirent : le chiffre d'affaires d'une micro-entreprise se compte à l'encaissement, pas à la facturation. Le livre des recettes et la détection de franchissement de seuil s'ordonnent donc sur les règlements, et non sur les factures émises.

La thèse

Ce qu'il faut figer, et ce qu'il ne faut pas

La tentation, quand on lit « inaltérabilité », est d'interdire toute modification dès qu'un document est émis. C'est ce que faisait la première version : écran grisé, champs verrouillés, correction impossible.

C'était une erreur, et elle a été corrigée par l'utilisateur de l'application, qui est aussi celui qui facture :

« Ce qu'il faut figer, c'est la chaîne. Le contenu, lui, peut être sujet à erreur humaine, donc à une réécriture. »

La correction qui a redéfini le modèle

L'inaltérabilité comptable n'a jamais voulu dire qu'on ne corrige pas. Elle veut dire qu'on ne corrige pas en douce. Un projet fait des allers-retours : on envoie un devis, le client demande un ajustement, on renvoie. Interdire la correction ne supprime pas l'erreur de saisie : elle pousse à créer un second document, c'est-à-dire à casser la numérotation pour préserver une immobilité qui n'était pas demandée.

Figé : la chaîneRéécrivable : le contenu
Le numéro attribué, jamais rendu ni réutiliséMontants, descriptions, lignes
La continuité des trois sériesCoordonnées du client
L'existence du documentDates, objet, conditions de paiement
L'historique des modifications, en ajout seul
Le modèle

Trois crans, un seul critère par cran

Chaque frontière est un fait objectif que la base connaît déjà. Aucune ne dépend d'un jugement, d'un réglage ou d'un statut qu'on pourrait poser à tort.

Brouillon

critère : référence provisoire
  • Modification libre
  • Suppression libre
  • Aucune trace, le document n'est jamais sorti
  • Aucun numéro consommé

Émis

critère : numéro attribué
  • Le numéro ne bouge plus
  • Le document ne se supprime plus
  • Correction possible, chaque changement laisse une révision
  • La série reste continue, sans rupture

Encaissé

critère : au moins un règlement
  • Le contenu se fige
  • Correction par avoir uniquement
  • Le montant est passé au livre des recettes

La forme encode l'état : pointillé pour ce qui n'engage rien, trait plein pour ce qui est acquis, aplat pour ce qui est scellé.

Ce que ça rend impossible

Un trou dans la numérotation. Un brouillon n'a consommé aucun numéro : l'effacer ne laisse rien. Un numéro attribué ne se rend jamais. Il n'y a donc aucun état d'où une rupture puisse naître.

Décisions

Trois arbitrages, et leurs raisons

Le numéro est attribué à l'émission, pas à la création

L'article 242 nonies A de l'annexe II au CGI impose à chaque facture un numéro unique fondé sur une séquence chronologique et continue, sans rupture4. Un trou doit pouvoir s'expliquer. Le texte admet des séries distinctes quand l'activité le justifie, ce qui fonde les trois séries tenues séparément ici.

Numéroter dès la création fabrique des trous en usage normal : on attribue FAC-0007, on corrige en supprimant et en refaisant, le 7 manque. Pas une fois par accident : à chaque correction.

Règle

Un brouillon porte BROUILLON-<id> et ne consomme rien. Quatre chemins peuvent l'émettre : émission explicite, changement de statut, facturation d'un devis, premier encaissement. Tous passent par la même porte.

Les règles vivent dans le schéma, pas dans l'application

Tant que les règles vivent dans le programme, il suffit d'ouvrir le fichier de données avec n'importe quel outil pour passer à côté. Seize règles ont donc été descendues dans la base elle-même : elles s'appliquent quoi qu'on utilise pour y écrire, y compris un outil qui ignore tout de l'application.

La règle, telle qu'elle est écrite dans la base
-- Un document encaissé ne change plus. Avant l'encaissement,
-- le contenu se corrige : c'est l'aller-retour normal d'un projet.
CREATE TRIGGER verrou_document_fige
BEFORE UPDATE ON documents
WHEN EXISTS (SELECT 1 FROM paiements p WHERE p.document_id = OLD.id)
 AND (OLD.montant_ht IS NOT NEW.montant_ht OR …)
BEGIN
    SELECT RAISE(ABORT,
        'Document encaissé : son contenu ne change plus. '
        || 'Le corriger par un avoir.');
END;
Ce que ça n'est pas

Un coffre-fort. Qui peut écrire dans le fichier peut aussi en retirer les règles. Ce qu'elles arrêtent, c'est l'erreur et la retouche désinvolte, pas un effacement déterminé. La preuve qu'on pourrait opposer à un contrôle, c'est le chaînage d'empreintes : chaque document scellé porte une signature calculée à partir de celle du précédent, si bien qu'en retirer un seul rompt la suite de façon visible. Il n'est pas encore fait.

Le régime de TVA est résolu à la date du document, et figé dedans

Une facture émise en franchise en base porte la mention « TVA non applicable, art. 293 B du CGI ». Si le régime bascule six mois plus tard, cette facture doit continuer à l'afficher : elle était juste au moment où elle a été émise.

Le régime et les mentions légales sont donc écrits dans le document à sa création, au même titre que les coordonnées du client. Rien n'est recalculé à l'affichage.

Cette décision va être mise à l'épreuve : la recodification de la TVA dans le code des impositions sur les biens et services remplace l'art. 293 B du CGI par l'art. L. 223-3 du CIBS, au 1er janvier 20278. Les factures émises avant cette date devront continuer d'afficher l'ancienne référence, celles émises après la nouvelle. Une mention figée par document traverse ce changement sans rien réécrire ; une mention recalculée à l'affichage aurait faussé rétroactivement toutes les factures antérieures.

Ce que ça a permis de trouver

Un écran de contrôle, écrit pour voir ce qui est réellement enregistré, a révélé que le régime de TVA restait vide sur toute facture issue d'un devis. Ces factures se seraient imprimées sans aucune mention légale, et rien ne l'aurait laissé voir à l'écran.

Profondeur de champ

Rien d'original : c'est le modèle qu'Odoo implémente

Nous nous appuyons sur les méthodes d'Odoo Community pour rendre notre code plus fiable. Le modèle décrit plus haut est le leur : mêmes états, même point de gel, mêmes raisons. Il tourne dans un logiciel comptable utilisé par des milliers d'entreprises.

Les trois correspondances ci-dessous sont lues dans le source, numéros de ligne à l'appui.

Notre décisionOdoo 18.0Source
Brouillon sans numéro,
numéro à l'émission
name = '/' tant que l'écriture n'est pas validée ; _set_next_sequence() n'est appelé que si state != 'draft' account_move.py
:927-939
Chaînage d'empreintes
pas encore fait
sha256(previous_hash + current_record), stocké dans inalterable_hash, indexé par secure_sequence_number account_move.py
:4204-4236
Retour en arrière possible
tant que rien n'est scellé
show_reset_to_draft_button = not inalterable_hash and (state == 'cancel' or posted) account_move.py
:1789-1796

La troisième est la plus instructive. La règle « on peut revenir en arrière tant que rien n'est scellé » est celle d'Odoo, au mot près. Elle dit quelque chose que l'intuition contredit : le gel arrive à un événement, jamais à l'émission. S'aligner sur un logiciel qui a déjà tranché cette question évite de la retrancher soi-même, et mal.

Là où les chemins divergent, et pourquoi

Odoo pose la même règle, mais dans son programme, pas dans sa base : quand un document est scellé, toute tentative de modification est arrêtée par un contrôle écrit en Python.

Le contrôle d'Odoo, dans son code
violated_fields = set(vals).intersection(
    move._get_integrity_hash_fields() + ['inalterable_hash'])
if move.inalterable_hash and violated_fields:
    raise UserError(_(
        "This document is protected by a hash. "
        "Therefore, you cannot edit the following fields: %s.", …))

account_move.py:3379-3385

La différence se voit dès qu'on n'utilise plus Odoo pour écrire : son contrôle ne s'applique plus, celui inscrit dans la base tient toujours.

Ce n'est pas « mieux ». Odoo a de bonnes raisons : il doit fonctionner au-dessus de plusieurs types de bases de données, et ses milliers de modules complémentaires étendent les règles en passant tous par le même endroit. Les enfouir dans la base les rendrait invisibles à ces modules. C'est un arbitrage. Savoir lequel on prend, et ce qu'on paie pour lui, vaut mieux que d'avoir raison.

Ce que ça dit d'une intégration

Intégrer Odoo sur ce terrain, ce n'est pas y déverser des règles comptables : c'est savoir où elles vivent déjà. La numérotation, le scellement et la liste des champs protégés existent, chacun à un endroit précis du code. Un développement qui ignore ces points de passage réécrit ce qui est déjà là, et finit par le contredire.

La couture est déjà en place

Le modèle à trois crans n'a pas été dessiné pour Odoo. Il s'y emboîte quand même, terme à terme, et c'est ce qui rend un raccordement envisageable sans rien tordre.

IciOdooLe point de jonction
Brouillon state = 'draft', name = '/' Rien à transmettre : le document n'existe pour personne
Émission action_post() C'est le même instant : celui où le numéro est attribué et où la pièce part
Encaissement Sécurisation, inalterable_hash posé Le gel, des deux côtés

Une intégration consisterait donc à pousser le document dans account.move au moment de l'émission, pas à le convertir ni à le rejouer. Les deux modèles sont déjà d'accord sur quand un document cesse d'être un projet.

Ce qu'un raccordement apporterait, et ce qu'il n'apporterait pas

Il faut être exact : une facture est valide ou elle ne l'est pas. Elle l'est si elle porte les mentions obligatoires, un numéro continu, si elle est émise en temps voulu et conservée. Aucun logiciel n'ajoute de la validité à une facture qui l'a déjà, et aucun n'en donne à une facture qui ne l'a pas.

Ce qui changerait vraiment est ailleurs, et c'est substantiel :

Deux choses à ne pas confondre

Être une plateforme agréée, ce qu'Odoo est, signifie être immatriculé pour émettre, recevoir et transmettre des factures électroniques. Attester de la conformité d'un logiciel est autre chose : l'attestation que publie Odoo SA porte sur son logiciel et concerne le régime des systèmes de caisse, pour des ventes à des particuliers2. Ni l'une ni l'autre ne « certifie » une facture prise isolément.

Et le raccordement se paie en dépendance : le module l10n_fr_pdp n'est pas présent dans ce dépôt Community 18.0, et son enrôlement depuis une instance auto-hébergée suppose un compte côté Odoo6. Ce n'est pas une bibliothèque qu'on installe : c'est un service auquel on s'abonne.

Ce que ça donne

Le parcours, sur des données réelles

Un devis de refonte, trois phases, dix mille euros. Ce qu'il faut regarder n'est pas ce qui s'affiche, mais ce qui ne se passe pas.

# Le devis existe, il est complet, il est chiffré.
POST /api/documents
  → { "ref": "BROUILLON-1", "numerote": false, "montant_ht": 10025.0 }

# Et pourtant :
séquences : devis=0, facture=0, avoir=0
révisions : 0
supprimable : oui

Aucun numéro consommé. Aucune trace à conserver : le document n'est jamais sorti. Il peut être jeté sans laisser de rupture. Il prendra DEV-0001 au moment de l'émission, pas avant.

ÉtapeCe qui change
Émission du devisDEV-0001 attribué ; le devis ne se supprime plus
FacturationTotal, acompte ventilé par taux de TVA, ou solde avec déduction des acomptes déjà émis
EncaissementLa facture se fige ; le montant entre au livre des recettes à sa date de règlement
Franchissement de seuilDétecté au moment de l'encaissement : un événement daté et réversible, jamais une bascule automatique du régime
Factur-XPDF/A-3 avec XML CII EN 16931 embarqué, profil en16931, refusé sur un document non émis
Un bug que seul le format a révélé

Une facture de solde sortait à 5 876 € au lieu de 3 164 € dans le fichier électronique, alors qu'elle affichait le bon montant à l'écran. Les lignes qui déduisent un acompte déjà versé sont légitimement négatives, et le programme les remettait au positif. Seul un avoir doit voir ses signes inversés ; une facture garde les siens.

Honnêteté

Ce qui n'est pas fait, et pourquoi

En tant que produit, c'est incomplet, et ça le restera un moment. Voici ce qui manque, et ce qui ne peut pas exister aujourd'hui.

Sur les quatorze chantiers identifiés, huit sont faits. Les six qui restent ne sont pas des oublis : quatre dépendent d'un raccordement externe, et les deux autres conditionnent la première utilisation réelle. Le document d'architecture les tient à jour, avec ce qui les bloque.

Fabrication

Ce qui a été écrit, et ce qui existait déjà

Le travail difficile, ici, n'a pas été écrit. Le format Factur-X, le PDF/A-3, les clés de contrôle SIRET, le moteur de base de données et ses déclencheurs : tout cela existait, éprouvé et maintenu par d'autres. Réécrire l'un de ces morceaux aurait été long, moins sûr, et sans le moindre intérêt.

CoucheChoixRaison
InterfaceHTML / CSS / JS natifAucune dépendance, aucune étape de construction
APIFlask 3.1Démarrage instantané, surface minimale
DonnéesSQLiteUn fichier ; la sauvegarde est une copie
Factur-Xfactur-x + reportlabLa première embarque un XML dans un PDF, elle n'en produit aucun, d'où la seconde, qui dessine la facture lisible
Contrôlespython-stdnumClés SIRET et SIREN, sans appel réseau

Les données restent entièrement locales. Le raccordement à une plateforme agréée n'est pas une couche de cette pile : c'est un service externe, et c'est le seul moment où quoi que ce soit sortira de la machine.

Alors qu'est-ce qui a été écrit ?

Les règles, et elles seules. Quand un numéro s'attribue et quand il cesse de pouvoir changer. Ce qu'un document peut encore devenir selon qu'il est réglé ou non. À quelle date se résout un régime de TVA et pourquoi il se fige dans la pièce. Quel signe porte une ligne de déduction d'acompte.

Aucune de ces règles n'est une invention : elles sont dans les textes, et un logiciel comptable existant les applique déjà. Ce qui se joue n'est pas de les créer, c'est de les lire correctement, puis de les placer là où elles tiennent.

Notes

Sources

Consultées le 9 septembre 2026. Les références au code d'Odoo ont été lues dans le dépôt, branche 18.0 : les numéros de ligne renvoient à cette branche et bougeront avec elle.

  1. Obligation de passer par une plateforme agréée pour émettre, déposer ou transmettre les factures électroniques et les données de e-reporting. Une PA est immatriculée par l'administration pour trois ans renouvelables. impots.gouv.fr, Facturation électronique et plateformes agréées
  2. Art. 286-I-3° bis du CGI : vise les assujettis qui effectuent des livraisons et prestations à destination de clients particuliers et enregistrent les règlements au moyen d'un logiciel ou système de caisse. La conformité s'établit par un certificat d'organisme accrédité ou par une attestation individuelle de l'éditeur. BOFiP, BOI-TVA-DECLA-30-10-30 · Modèle d'attestation individuelle
  3. Franchise en base, prestations de services : seuil de base 37 500 € et seuil majoré 41 250 €, art. 293 B du CGI. Entre les deux, l'année en cours reste sans TVA et la bascule intervient au 1er janvier suivant ; au-delà du seuil majoré, elle est immédiate. economie.gouv.fr, Franchise de TVA · seuils vérifiés sur service-public.fr au 01/01/2026
  4. Art. 242 nonies A de l'annexe II au CGI : la facture porte « un numéro unique basé sur une séquence chronologique et continue ». Des séries distinctes sont admises lorsque les conditions d'exercice le justifient. Légifrance, art. 242 nonies A, annexe II CGI
  5. Odoo est immatriculé comme plateforme agréée auprès de la DGFiP ; le module l10n_fr_pdp (« France - Facturation électronique (plateforme agréée) ») porte le raccordement. La liste officielle des PA est publiée par l'administration. Odoo, annonce d'immatriculation · impots.gouv.fr, liste des plateformes agréées
  6. Le module l10n_fr_pdp est absent du dépôt Community 18.0 utilisé ici, qui ne contient que l10n_fr, l10n_fr_account, l10n_fr_facturx_chorus_pro et l10n_fr_pos_cert. Des utilisateurs auto-hébergés rapportent un refus d'enrôlement auprès du service. Constat direct sur le dépôt · Forum Odoo, « Registration denied » en Community auto-hébergé
  7. Calendrier : réception obligatoire pour toutes les entreprises assujetties au 1er septembre 2026 ; émission au 1er septembre 2026 pour les grandes entreprises et ETI, au 1er septembre 2027 pour les TPE, PME et micro-entreprises. impots.gouv.fr · economie.gouv.fr
  8. Ordonnance n° 2025-1247 du 17 décembre 2025 portant recodification de la TVA dans le code des impositions sur les biens et services : l'art. L. 223-3 du CIBS reprend la franchise en base de l'art. 293 B du CGI. L'entrée en vigueur, d'abord fixée au 1er septembre 2026, a été reportée au 1er janvier 2027 : la concomitance avec la bascule e-facturation était jugée source de confusion. Légifrance, ordonnance n° 2025-1247 · art. L. 223-3 · BOFiP, dispositions transitoires