Aller au contenu
de Munter

Facturation électronique : des démonstrations à manipuler plutôt qu’à croire sur parole

Trois démonstrations tirées de mon banc d’essai de factures électroniques : un simulateur de facture de situation, un décodeur des règles de rejet des plateformes agréées et le parcours d’une facture. Puis quatre exemples de ce que je construis, dont ce site. Tout fonctionne dans votre navigateur : rien de ce que vous saisissez n’est envoyé nulle part.

Simulateur de facture de situation

Le 3 septembre 2026, j’ai construit des factures de situation de travaux volontairement fausses, puis je les ai passées au contrôle officiel européen EN 16931 et à deux plateformes agréées, en environnement de test. Choisissez une facture, changez les montants : le contrôle rend toujours le même verdict.

Votre situation de travaux
FACTURESituation n° 2
SIT-2026-002

Le contrôle attrape ce qui ne coûte rien et laisse passer ce qui coûte cher.

Décodeur de règles de rejet

Un message de rejet ne donne qu’un code et une phrase en anglais. Choisissez un code : ce que la règle reproche, la cause probable dans le logiciel de gestion, la correction, et la balise XML en cause dans mes factures de test. Seules figurent ici les règles que j’ai déclenchées moi-même.

    Le parcours d’une facture, et ses mailles

    Cinq étapes entre le logiciel du fournisseur et celui du client. Suivez la facture réelle dont les remises dépassaient le total, ou choisissez une étape : ce qu’on y contrôle, et ce qui passe entre les mailles.

    ou cliquez sur une étape

    1. Logiciel du fournisseur

    Ce qui s’y décide
    Le champ où l’on place la retenue de garantie, le code de TVA d’une sous-traitance, le montant d’une situation. Le logiciel ne vérifie que ses propres calculs.
    Ce qui passe entre les mailles
    Tout ce que le paramétrage a mal décidé. La correspondance entre les données du logiciel et les champs de la norme est une décision, pas un contrôle.

    Cas réel. La facture part avec des remises supérieures au montant total.

    2. Contrôle de cohérence avant envoi

    Ce qu’on y contrôle
    Ce qu’aucune norme ne regarde : le montant de la période par rapport aux situations déjà facturées, le régime de TVA par rapport au chantier, la base de TVA par rapport à la retenue, les remises par rapport au total.
    Ce qui passe entre les mailles
    Le plus souvent, tout : cette étape n’existe pas. Elle se construit dans le logiciel ou juste à sa sortie, et c’est la seule placée avant l’envoi.

    Cas réel. C’est ici que la facture aurait dû être arrêtée : des remises supérieures au total, cela se voit en une ligne de code.

    3. Plateforme de l’émetteur

    Ce qu’on y contrôle
    La forme : le schéma XML, les règles européennes EN 16931 et les règles françaises. Au banc d’essai, deux plateformes agréées ont rendu exactement le même verdict, règle par règle : elles appliquent le même jeu de règles publié.
    Ce qui passe entre les mailles
    Le fond. Une surfacturation de 36 000 € TTC, 250 € de TVA sous-déclarés et un régime de TVA faux passent avec zéro erreur.

    Cas réel. La facture aux remises supérieures au total est acceptée.

    4. Plateforme du destinataire

    Ce qui s’y passe
    Elle reçoit la facture et la met à disposition du client, puis fait remonter les statuts de traitement vers le fournisseur.
    Ce qui passe entre les mailles
    Une facture valide dans la forme continue sa route, même si elle ne veut rien dire.

    Cas réel. La facture est transmise au client.

    5. Logiciel du client

    Ce qu’on y contrôle
    L’import en gestion et en comptabilité, avec ses propres exigences : c’est souvent le premier endroit où quelqu’un regarde vraiment les montants.
    Ce qui passe entre les mailles
    Plus rien, mais c’est trop tard : l’erreur apparaît chez le client, pas chez celui qui peut la corriger.

    Cas réel. Le logiciel du client ne peut pas importer la facture, et le client ne peut même pas la refuser. Contournement : un PDF de secours, un avoir, puis une nouvelle facture. Trois documents au lieu d’un, pour une erreur qu’un contrôle avant envoi aurait vue.

    Réalisations et savoir-faire

    Un banc d’essai de factures électroniques Réalisé

    Le problème
    Savoir ce que le contrôle officiel laisse vraiment passer, plutôt que de le supposer en lisant la norme.
    Ce que j’ai construit
    Neuf factures au format CII (XML), générées sur un même scénario de situation de travaux, chacune avec un seul encodage différent. Elles sont validées au schématron officiel EN 16931, puis soumises à deux plateformes agréées. Les démonstrations 1 et 2 en viennent.
    Les choix techniques
    Une fonction de génération unique, pour que deux cas ne diffèrent que par ce qu’on veut tester ; validation XSLT avec Saxon-HE ; résultats reproductibles en deux commandes.
    • Python
    • XML CII
    • EN 16931
    • Schématron
    • XSLT · Saxon

    Extrait réel : deux façons d’encoder une retenue de garantie en remise. La première passe le contrôle en sous-déclarant la TVA, la seconde est juste et rejetée.

    # A2a : retenue en remise document, arithmetique COHERENTE (TVA recalculee)
    build("A2a_retenue_remise_coherente.xml", lines=SIT,
          taxes=[(23750, 4750, "S", 20.0, None)],
          allowances=[(1250.0, "102", "Retenue de garantie 5%", "S", 20.0)],
          line_total=25000, allowance_total=1250, charge_total=0,
          tax_basis=23750, tax_total=4750, grand=28500, due=28500)
    
    # A2b : retenue en remise document, TVA LEGALEMENT JUSTE (5000) -> incoherence arithmetique
    build("A2b_retenue_remise_TVA_juste.xml", lines=SIT,
          taxes=[(25000, 5000, "S", 20.0, None)],
          allowances=[(1250.0, "102", "Retenue de garantie 5%", "S", 20.0)],
          line_total=25000, allowance_total=1250, charge_total=0,
          tax_basis=23750, tax_total=5000, grand=28750, due=28750)

    Un générateur de devis qui refuse d’émettre un devis incomplet Réalisé

    Le problème
    Un devis de prestation engage : prix ventilé par lot, échéancier, dates, plafond de responsabilité, renvoi aux conditions générales. Un oubli se paie plus tard.
    Ce que j’ai construit
    Un outil qui part d’une fiche YAML et produit le devis en Markdown et en PDF, avec les conditions générales. Avant l’émission, un contrôle vérifie l’arithmétique, les dates, les mentions obligatoires et les traces internes oubliées. Une seule erreur, et le PDF n’est pas produit.
    Les choix techniques
    Montants en Decimal, jamais en nombres à virgule flottante ; textes dans un modèle modifiable sans toucher au code ; PDF identique sous Windows et Linux ; tests unitaires et de non-régression ; aucun appel réseau.
    • Python
    • YAML
    • Jinja2
    • ReportLab
    • unittest

    Extrait réel : le début du contrôle arithmétique, qui bloque l’émission.

    def arithmetique(r: Rapport, d: dict):
        f, t = d["f"], d["t"]
        somme = sum((l["montant"] or Decimal(0) for l in f["lots"]), Decimal(0))
        ventile = all(l["montant"] is not None for l in f["lots"])
        if ventile and f["total_annonce"] is not None and somme != f["total_annonce"]:
            r.erreur("arithmétique", f"la somme des lots ({euros(somme)}) ne donne pas le total annoncé ({euros(f['total_annonce'])})")
        parts = sum((Decimal(str(e["part"])) for e in f["echeancier"]), Decimal(0))
        if parts != 100:
            r.erreur("arithmétique", f"l'échéancier fait {parts:f} %, pas 100 %")
        if sum((e["montant"] for e in d["echeancier"]), Decimal(0)) != t["lots"]:
            r.erreur("arithmétique", "les montants de l'échéancier ne redonnent pas le total")

    L’automatisation d’un flux de données comptables Savoir-faire

    Le problème type
    Chaque jour, des données doivent passer d’un logiciel à un autre : les lire, les mettre en forme, déposer un fichier. Sans doublon ni trou, même quand un traitement s’arrête en plein milieu.
    Ce que je construis
    La lecture d’une API REST page par page, avec pagination par curseur ; un point de reprise enregistré après chaque page traitée, pour repartir sans doublon après un incident ; le dépôt quotidien des fichiers ; une supervision qui prévient quand un traitement a échoué ou n’a pas tourné.
    Les choix techniques
    Écriture tout ou rien ; le point de reprise n’avance qu’après une écriture réussie ; les doublons sont écartés par identifiant, pas par date ; un journal lisible par quelqu’un qui n’est pas développeur.
    • API REST
    • Pagination par curseur
    • Reprise sur incident
    • Supervision

    Exemple écrit pour ce site : le principe de la reprise sans doublon.

    def synchroniser(api, depot, etat):
        """Lit l'API page par page et reprend au dernier curseur enregistré."""
        curseur = etat.lire("curseur")                 # None au tout premier passage
        while True:
            page = api.lire("/ecritures", curseur=curseur, limite=500)
            nouvelles = [e for e in page["donnees"] if not depot.deja_vu(e["id"])]
            depot.ecrire(nouvelles)                    # tout ou rien
            suivant = page.get("curseur_suivant")
            if not suivant:
                break                                  # on garde le dernier curseur pour demain
            etat.enregistrer("curseur", suivant)       # après l'écriture, jamais avant
            curseur = suivant

    Ce site, demunter.fr Réalisé

    Le problème
    Présenter mon activité sans rien imposer au visiteur : ni cookie, ni traceur, ni ressource chargée depuis un autre site. Et sans que mon adresse finisse dans les listes des robots qui collectent les courriels.
    Ce que j’ai construit
    Trois pages statiques assemblées par un script Python : l’en-tête et le pied de page sont écrits une seule fois, puis chaque version est vérifiée avant d’être déposée. Le courriel n’est assemblé qu’au premier geste du visiteur. Les démonstrations tournent entièrement dans le navigateur.
    Les choix techniques
    Bibliothèque standard Python uniquement ; tests du simulateur avec les montants du banc d’essai ; la construction s’arrête si une page dépasse 200 Ko, contient un lien vers un site non autorisé, un cookie, un formulaire, un appel réseau, un mot interdit, ou une image qui garde ses métadonnées (position GPS, modèle de téléphone).
    • Python
    • HTML
    • CSS
    • JavaScript
    • Node.js (tests)

    Extrait réel du script de construction : la lecture d’une image WebP bloc par bloc, pour refuser toute trace EXIF ou XMP.

    def metadonnees(octets: bytes) -> list[str]:
        trouves = set()
        if octets[:4] == b"RIFF" and octets[8:12] == b"WEBP":
            i = 12
            while i + 8 <= len(octets):
                genre, taille = octets[i:i + 4], int.from_bytes(octets[i + 4:i + 8], "little")
                if genre == b"EXIF":
                    trouves.add("EXIF")
                elif genre == b"XMP ":
                    trouves.add("XMP")
                i += 8 + taille + (taille & 1)

    Un besoin qui ressemble à l’une de ces réalisations ?

    Écrivez-moi en quelques lignes : les logiciels que vous utilisez et ce qui bloque. Si vous avez reçu un message de rejet, joignez-le.