Article technique

Validation des factures électroniques : veraPDF et Mustang dans Delphi

Une facture Factur-X ou ZUGFeRD est un ensemble de deux documents portant un seul nom de fichier. Le document extérieur est un conteneur PDF/A-3 qu'un lecteur d'archives doit accepter pendant les dix prochaines années. Le document intérieur est une facture XML qu'un système comptable de l'acheteur doit analyser par rapport à la norme EN 16931. L'erreur qui envoie des factures défectueuses en production est de croire que si le premier est correct, le second le sera automatiquement. Ce n'est pas le cas. Un fichier peut être un PDF/A-3 irréprochable tout en contenant un XML qu'aucune autorité fiscale n'acceptera, et il peut contenir un XML conforme à la norme EN 16931 à l'intérieur d'un conteneur qui échoue à la validation archivistique. Les deux couches sont validées par deux outils différents qui s'ignorent mutuellement, et un pipeline réel doit satisfaire les deux

Deux validateurs, deux questions différentes

veraPDF est l'implémentation de référence pour le PDF/A. Pointez-le sur une facture et il répond à une seule question : ce fichier est-il un PDF/A-3 conforme. Il vérifie les points dont se préoccupe la norme ISO 19005-3. Chaque police est-elle intégrée. Y a-t-il un OutputIntent (intention de sortie). Les métadonnées XMP déclarent-elles la bonne partie et le bon niveau de conformité. Pour une facture électronique, il vérifie également la plomberie des fichiers associés requise par le PDF/A-3, car le XML est transporté comme un fichier intégré avec une /AFRelationship et une entrée dans le tableau /AF du catalogue de documents. veraPDF ne se prononce pas sur l'exactitude du total de la facture, car cela ne relève pas de sa compétence

Mustang est le validateur open-source du projet Mustangproject. Il pose la question orthogonale : le XML intégré est-il une facture valide. Il confronte le XML au schéma du profil déclaré, puis applique les règles métier EN 16931 ainsi que les ensembles de règles spécifiques aux pays qui s'y superposent, dont le CIUS de XRechnung. Il vérifie qu'un identifiant de TVA du vendeur est présent lorsque les totaux l'exigent, que les montants des remises et des frais concordent avec le total du document, et que l'URN du profil dans le XML correspond à ce que le fichier prétend être. Mustang ne se soucie pas de savoir si le PDF environnant intègre ses polices, car c'est le travail de veraPDF

Aucun des deux outils n'est un sur-ensemble de l'autre. veraPDF valide un conteneur structurellement parfait autour d'un XML absurde. Mustang valide un XML parfait enveloppé dans un conteneur auquel il manque un OutputIntent. Chacun détecte exactement la catégorie de défaut à laquelle l'autre est aveugle, c'est la raison pour laquelle un harnais de validation sérieux exécute les deux et ne considère un fichier comme expédiable que lorsque les deux sont d'accord

La matrice de validation

Pour prouver que la bibliothèque produit des fichiers qui survivent aux deux contrôles, le harnais construit une matrice. Six profils de factures couvrent la gamme qu'un pipeline européen rencontre en pratique : Factur-X EN 16931, Factur-X BASIC, la variante Factur-X EXTENDED France B2B, XRechnung 3.0, ZUGFeRD 1.0 COMFORT et ZUGFeRD 2.0 BASIC. Chaque profil est généré pour deux sous-niveaux de conformité PDF/A, 3b et 3u, car les exigences de niveau B et de niveau U divergent sur le mappage Unicode et un fichier qui réussit l'un peut échouer à l'autre. Six profils multipliés par deux niveaux donnent douze fichiers, tous générés de manière automatisée par le même chemin de code que celui fourni par l'exemple de l'interface graphique, de sorte que les artefacts testés ne sont pas ajustés manuellement pour le test

Le générateur écrit les douze fichiers et un script soumet chacun d'eux aux deux validateurs. Lors de la première exécution complète, veraPDF a validé les douze. La plomberie du conteneur était correcte à tous les niveaux : fichiers associés enregistrés, conformité XMP déclarée, intentions de sortie en place. Mustang en a validé huit. Quatre factures étaient des fichiers PDF/A-3 structurellement valides transportant un XML que le validateur de règles métier a rejeté, ce qui est précisément la division que l'approche à deux outils vise à mettre en évidence. Si le harnais ne s'était fié qu'à veraPDF, ces quatre fichiers auraient semblé terminés

Les deux correctifs qui ont comblé l'écart

Les quatre échecs de Mustang provenaient de deux causes distinctes, et le correctif pour chacune d'elles est un détail qu'il vaut la peine de connaître avant de générer ces profils vous-même

Le premier concernait le profil Factur-X EXTENDED France B2B. Le générateur d'origine passait une étiquette interne comme niveau de conformité et un URN interne comme directive, et Mustang a rejeté le fichier avec une erreur de valeur de conformité invalide suivie d'une erreur de type de profil non pris en charge. La raison en est que le champ XMP fx:ConformanceLevel n'est pas un emplacement de texte libre pour le nom de votre propre profil. Factur-X définit exactement cinq valeurs standard pour lui : MINIMUM, BASIC WL, BASIC, EN 16931 et EXTENDED. Une facture B2B spécifique à la France reste un document de profil EXTENDED en ce qui concerne les métadonnées XMP. Le caractère français de la facture ne s'exprime pas en inventant une sixième valeur de conformité. Il s'exprime par le code pays, FR, et par l'identifiant de la directive à l'intérieur du XML, qui doit porter le préfixe urn:cen.eu:en16931:2017#conformant# qui marque un CIUS conforme à la norme EN 16931. Passer la valeur standard EXTENDED avec FR comme code pays et le bon URN de directive a rendu le fichier conforme

Dans l'API de la bibliothèque, il s'agit d'un appel à AddFacturXAssociatedFileFromString avec la conformité, le pays et la directive alignés. L'argument du niveau de conformité porte le jeton standard, l'argument du code pays porte FR, et l'URN de la directive réside dans les octets XML que vous transmettez

var
  FileID: Integer;
begin
  PDF.SetPDFAMode(5);            // PDF/A-3b
  PDF.NewDocument;
  // ... draw the human-readable invoice page ...
  // ExtendedXML carries an EN 16931 guideline URN of the form
  //   urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended
  FileID := PDF.AddFacturXAssociatedFileFromString(
    ExtendedXML,
    'EXTENDED',          // standard fx:ConformanceLevel, not an internal label
    'factur-x.xml',
    'Factur-X EXTENDED invoice',
    'Alternative',       // /AFRelationship
    '1.0',
    'FR');               // France B2B marked by country code, not by conformance
  if FileID = 0 then
    raise Exception.Create('Factur-X attachment rejected');
  PDF.SaveToFile('02_Factur-X-EXTENDED-FR_PDFA-3b.pdf');
end;

La deuxième cause était le profil ZUGFeRD 1.0 COMFORT, et n'avait rien à voir avec les métadonnées. ZUGFeRD 1.0 est validé par rapport au XSD :1p0, qui est plus strict sur la cardinalité que ne le suggèrent les résumés en prose. Le XSD exige que la sommation monétaire du règlement d'en-tête, ram:SpecifiedTradeSettlementMonetarySummation, contienne ram:ChargeTotalAmount et ram:AllowanceTotalAmount exactement une fois chacun. Le XML généré omettait les deux, et Mustang a donc signalé que les éléments devaient se produire exactement une fois. Ceux-ci ne sont pas facultatifs lorsque le schéma indique que minOccurs est égal à un. Émettre les deux dans l'ordre de séquence XSD, immédiatement après ram:LineTotalAmount, avec une valeur de 0.00 lorsqu'il n'y a pas de frais ou de remises, a satisfait au schéma. Un zéro est un élément présent ; un élément absent est une violation de schéma. Avec ces deux correctifs en place, la matrice est passée à douze sur douze sur Mustang tout en restant à douze sur douze sur veraPDF

Les champs XRechnung qui font basculer d'invalide à valide

XRechnung mérite sa propre note car son CIUS allemand ajoute des règles métier absentes de l'ensemble de base EN 16931, et elles échouent d'une manière qui laisse penser qu'il n'y a rien de mal avec le document au premier abord. Deux d'entre elles concernent les adresses électroniques. BT-34 est l'adresse électronique du vendeur et BT-49 est l'adresse électronique de l'acheteur, les terminaux de routage qu'un portail du secteur public allemand utilise pour livrer et accuser réception de la facture. Le modèle de base EN 16931 les considère comme facultatifs. Ce n'est pas le cas de XRechnung. Omettez l'un ou l'autre et la facture, bien formée et valide selon le schéma, sera rejetée

La troisième est la règle BR-DE-6, qui exige la présence du numéro de téléphone de contact du vendeur. C'est le genre de champ qu'un développeur omet parce qu'il ressemble plus à une présentation qu'à des données, et son absence produit un échec de validation qui pointe vers le groupe de contact du vendeur plutôt que vers quoi que ce soit d'évident. Fournir BT-34, BT-49 et le numéro de téléphone du vendeur est ce qui fait passer un fichier XRechnung d'invalide à valide sous Mustang, et rien de tout cela ne change ce que voit veraPDF, car tous trois se trouvent dans le XML

Connecter la sortie de la bibliothèque à un validateur

Le point architectural derrière le harnais se généralise à tout système métier. La bibliothèque PDF écrit un conteneur conforme et y intègre le XML. Elle n'essaie pas, et ne devrait pas essayer, d'être l'autorité en matière de règles métier EN 16931. ValidateFacturXInvoice dans la bibliothèque vérifie la cohérence du conteneur, que le tableau /AF du catalogue, l'arbre de noms des fichiers intégrés, le DocumentFileName XMP, le profil, la directive et la /AFRelationship concordent tous, mais elle ne valide pas les codes fiscaux ni ne réconcilie les montants. La bonne division du travail consiste pour le système métier à extraire le XML et à le confier à un validateur de factures dédié, exactement comme le harnais le confie à Mustang

Relire le fichier vous indique ce qui a été réellement écrit. DetectFacturXInvoice signale si une facture a été reconnue, et GetFacturXInvoiceInfo lit les champs de métadonnées par balise : la balise 1 est le nom de fichier intégré, la balise 2 le DocumentFileName XMP, la balise 5 le niveau de conformité, la balise 6 l'identifiant de la directive, et la balise 7 la /AFRelationship. Confirmer que le niveau de conformité que vous relisez est le jeton standard et non une étiquette interne est le moyen le moins coûteux de détecter l'erreur EXTENDED avant qu'un fichier ne quitte votre compilation

function ExtractAndInspect(const PdfPath: string): AnsiString;
var
  Profile, Guideline: WideString;
begin
  Result := '';
  PDF.LoadFromFile(PdfPath);
  if PDF.DetectFacturXInvoice = 1 then
  begin
    Profile   := PDF.GetFacturXInvoiceInfo(5);  // fx:ConformanceLevel
    Guideline := PDF.GetFacturXInvoiceInfo(6);  // XML guideline ID
    Writeln('Profile:   ', Profile);
    Writeln('Guideline: ', Guideline);
    // Hand the raw XML to a dedicated EN 16931 / Mustang validator.
    Result := PDF.ExtractFacturXXMLToString;
  end;
end;

ExtractFacturXXMLToString renvoie les octets XML bruts sous forme d'AnsiString, prêts à être écrits dans un fichier ou diffusés dans un processus de validateur. Dans le harnais de test, cette cible est Mustang, invoqué via son jar en ligne de commande, avec veraPDF exécuté dans la même passe sur le même fichier. Le câblage est minime : un générateur de console, EInvoiceValidation.dpr, écrit les douze fichiers en utilisant le modèle de facture partagé depuis l'exemple, et un script, run-validation.ps1, pilote les deux validateurs sur le répertoire de sortie et imprime un tableau de réussites et d'échecs. La même forme en deux étapes, générer avec la bibliothèque et vérifier avec des validateurs externes, est ce qu'une tâche d'intégration continue devrait exécuter à chaque modification de la génération de factures, car la seule façon de savoir qu'un fichier satisfait les deux couches est de demander aux deux outils

Si votre pipeline doit également certifier le conteneur avant signature, l'aspect pré-vol (preflight) de ce travail est couvert dans notre visite guidée du contrôle en amont PDF/A et PDF/UA dans Delphi, et le flux plus large de certification puis de signature est décrit dans le plan de travail de conformité et de signature. Les deux s'appuient sur le même chemin de génération qui est inclus dans la Bibliothèque PDF Delphi pour Delphi et C++Builder, avec les API PDF/A, de fichiers associés et de métadonnées utilisées ici