Une facture électronique conforme n'est pas un PDF avec un fichier XML agrafé sur le côté. C'est un document PDF/A-3 unique qui transporte la facture deux fois : une fois en tant que page lisible par un humain, et une fois en tant que facture intersectorielle (Cross Industry Invoice) XML lisible par une machine, stockée à l'intérieur du fichier en tant que fichier associé. Les deux représentations décrivent la même facture. Cette double nature est le but même des familles de formats que les mandats européens exigent désormais, Factur-X en France et en Allemagne, ZUGFeRD sur les marchés germanophones, et XRechnung pour la facturation du secteur public allemand. Cet article explique comment PDFlibPas assemble une telle facture hybride dans Delphi, là où les normes laissent la place à l'erreur, et pourquoi un profil du catalogue a besoin d'un constructeur XML complètement séparé
Ce qu'est réellement une facture hybride
La page visible et le XML intégré servent des lecteurs différents. Un employé approuvant un paiement regarde la page rendue. Un système de comptabilité fournisseur ingère le XML, lit les totaux et la ventilation des taxes sous forme de champs structurés, et enregistre l'entrée sans qu'un humain n'ait à saisir quoi que ce soit. Le contenu sémantique de ce XML est régi par la norme EN 16931, la norme européenne qui définit le modèle de données de la facture : quels champs existent, ce qu'ils signifient et lesquels sont obligatoires. La norme EN 16931 est un modèle sémantique, et non un format de fichier. Factur-X, ZUGFeRD 2.x et XRechnung réalisent tous ce modèle sous la forme d'un document UN/CEFACT Cross Industry Invoice, la syntaxe qui transporte les champs EN 16931 sur le réseau
Pour que le document soit à la fois archivable et auto-descriptif, le conteneur est le PDF/A-3, défini par la norme ISO 19005-3. Le PDF/A-3 est le niveau de conformité qui permet des fichiers intégrés arbitraires, ce qui est exactement ce qu'un fichier XML de facture doit être. Le PDF/A-2 interdit d'intégrer des fichiers qui ne sont pas eux-mêmes PDF/A, de sorte qu'une facture Factur-X ne peut pas être PDF/A-2. Le choix du PDF/A-3 n'est donc pas une préférence, c'est une exigence qui découle directement de la volonté d'intégrer des données non-PDF dans un document d'archive
Pourquoi la relation est Alternative
L'intégration des octets est la partie facile. La norme ISO 32000 §7.11.4 définit le flux de fichiers intégré, l'objet qui contient le XML brut et ses paramètres. La partie qui fait du fichier un fichier associé valide est le §14.13, qui ajoute le concept de fichier associé et la clé /AFRelationship. Cette clé indique comment les données intégrées sont liées au contenu auquel elles sont attachées, et la valeur imposée par Factur-X est Alternative
Le choix est important car les autres valeurs affirmeraient quelque chose de faux au sujet du document. Source signifierait que le XML est le matériau à partir duquel le contenu visible a été généré, un original dont dérive la page. Supplement signifierait que le XML ajoute des informations au-delà de ce que montre la page, un supplément non contenu dans le rendu. Ce n'est ni l'un ni l'autre d'une facture Factur-X. Le XML et la page sont deux expressions équivalentes d'une seule facture, transportant le même contenu légal sous deux formes. Alternative est la valeur qui dit exactement cela : une représentation alternative équivalente du contenu visible. Un validateur qui lit toute autre relation sur un fichier Factur-X le rejettera, à juste titre, car la relation est une affirmation lisible par machine sur la raison d'être de la pièce jointe
Le catalogue de profils
L'exemple E-Invoice fourni avec PDFlibPas pilote le même chemin de génération à travers six profils, définis sous la forme d'un tableau d'enregistrements dans InvoiceModel.pas. Chaque profil transporte les valeurs dont l'auteur a besoin : un nom d'affichage, le nom de fichier intégré, un niveau de conformité, la /AFRelationship, une version, un code pays facultatif et l'URN GuidelineID que le XML annonce dans son contexte de document
Les six sont Factur-X EN16931, Factur-X BASIC, Factur-X EXTENDED pour la France, XRechnung 3.0, ZUGFeRD 1.0 COMFORT et ZUGFeRD 2.0 BASIC. Le GuidelineID est le champ qui indique précisément à un destinataire à quel profil s'attendre, et les valeurs sont spécifiques. Factur-X EN16931 annonce urn:cen.eu:en16931:2017. XRechnung 3.0 annonce urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0. ZUGFeRD 2.0 BASIC annonce urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p0:basic. Le nom du fichier intégré fait également partie du contrat. Les profils Factur-X intègrent factur-x.xml, XRechnung intègre xrechnung.xml, et les profils ZUGFeRD intègrent ZUGFeRD-invoice.xml ou zugferd-invoice.xml. Un destinataire scanne les noms de pièces jointes pour trouver la facture, le nom de fichier n'est donc pas esthétique
Un détail dans le catalogue mérite d'être lu attentivement. La plupart des profils utilisent la relation Alternative, mais l'entrée XRechnung 3.0 dans l'exemple utilise Source. Les deux formats répondent à des validateurs et des conventions différents, et l'exemple définit la relation de chaque profil à partir du catalogue plutôt que de coder en dur une seule valeur, c'est pourquoi le champ par profil existe plutôt qu'une constante
Le piège ZUGFeRD 1.0
Il est tentant de supposer que chaque profil est une facture intersectorielle (Cross Industry Invoice) EN 16931 avec des variations mineures quant au nombre de champs facultatifs que vous remplissez. Cela vaut pour cinq des six. Cela ne vaut pas pour ZUGFeRD 1.0 COMFORT, et la raison est structurelle plutôt que cosmétique
Les profils modernes émettent une facture intersectorielle UN/CEFACT avec la version d'espace de noms :100, dont l'élément racine est rsm:CrossIndustryInvoice. ZUGFeRD 1.0 est antérieur à ce schéma. C'est le CrossIndustryDocument de 2014 avec la version d'espace de noms :1p0, et son élément racine est rsm:CrossIndustryDocument. Les URN d'espace de noms diffèrent, l'élément racine diffère, et l'arbre d'éléments diffère d'un bout à l'autre : le schéma :1p0 regroupe les données sous ApplicableSupplyChainTradeAgreement, ApplicableSupplyChainTradeDelivery et ApplicableSupplyChainTradeSettlement, où :100 utilise ApplicableHeaderTradeAgreement, ApplicableHeaderTradeDelivery et ApplicableHeaderTradeSettlement. Le nommage est suffisamment similaire pour induire en erreur et suffisamment différent pour planter
Le mot COMFORT dans le nom du profil décrit la richesse des données, un profil d'automatisation avec des postes complets, une ventilation des taxes et des conditions de paiement, et non le schéma qui le transporte. Vous ne pouvez donc pas prendre un document :100 et le renommer pour ZUGFeRD 1.0. L'exemple gère cela avec un drapeau sur chaque enregistrement de profil et deux fonctions de construction distinctes, en sélectionnant la bonne avant qu'aucun XML ne soit généré
function BuildInvoiceXMLText(const AProfile: TeInvoiceProfile;
const Data: TInvoiceData): string;
begin
// XMLFamily = 1 means the legacy ZUGFeRD 1.0 :1p0 schema; every
// other profile is the modern UN/CEFACT :100 Cross Industry Invoice.
if AProfile.XMLFamily = 1 then
Result := BuildZUGFeRD1Text(AProfile, Data)
else
Result := BuildCII100Text(AProfile, Data);
end;
La scission n'est pas une subtilité de mise en œuvre. Fournir un arbre :100 à un destinataire ZUGFeRD 1.0 produit un document qui échoue à la validation de schéma à l'élément racine, les deux familles doivent donc être construites par un code qui sait laquelle il écrit
Sélectionner le niveau PDF/A-3
Le PDF/A-3 possède trois niveaux de conformité, et PDFlibPas les sélectionne via SetPDFAMode. Le mode 5 est le PDF/A-3b, le niveau qui garantit une reproduction visuelle fiable. Le mode 6 est le PDF/A-3a, qui ajoute les exigences de structure balisée et d'accessibilité du niveau a. Le mode 7 est le PDF/A-3u, qui exige que tout texte soit mappé vers Unicode. L'activation du mode intègre également l'intention de sortie (OutputIntent) sRGB intégrée de la bibliothèque, la caractérisation des couleurs que le PDF/A exige pour que la couleur rendue soit définie plutôt que dépendante de l'appareil
La plupart des flux de facturation s'exécutent en 3b, ce qui est suffisant pour une page visible fidèle plus le XML intégré. Si vous avez besoin d'un profil ICC explicite plutôt que de celui intégré, LoadOutputIntentProfile le remplace une fois le mode défini. L'exemple charge ainsi le profil sRGB du référentiel et se rabat sur l'intention intégrée lorsque le fichier n'est pas accessible, de sorte que l'intention de sortie est toujours présente
PDF := TPDFlib.Create;
try
// Mode 5 = PDF/A-3b, 6 = PDF/A-3a, 7 = PDF/A-3u.
if PDF.SetPDFAMode(5) <> 1 then
raise Exception.Create('PDF/A-3 mode could not be enabled');
// Optional: swap the built-in sRGB intent for an explicit ICC profile.
if PDF.LoadOutputIntentProfile(ICCFile, 'DeviceRGB') <> 1 then
{ fall back to the built-in sRGB intent that SetPDFAMode embedded };
finally
// ... continue building the document
end;
Créer la facture hybride
Une fois le conteneur configuré, le reste se fait en trois étapes dans l'ordre : définir le mode PDF/A-3, dessiner la page lisible par l'homme, puis attacher le XML en tant que fichier associé. La page visible est un contenu ordinaire. La seule contrainte à retenir est que le PDF/A interdit les 14 polices standard non intégrées, de sorte que la page doit intégrer une vraie police de caractères plutôt que de faire référence à une police intégrée
La pièce jointe est un appel unique. AddFacturXAssociatedFileFromString prend les octets XML bruts UTF-8 plus les métadonnées de profil, écrit le flux de fichier intégré, l'enregistre dans le tableau /AF du catalogue que le PDF/A-3 exige, applique la /AFRelationship, et génère les métadonnées XMP de la facture électronique qui identifient le document comme Factur-X, ZUGFeRD ou XRechnung. Il vérifie également que l'ID de la directive XML correspond au niveau de conformité que vous avez demandé, de sorte qu'une non-concordance entre le XML que vous avez construit et le profil que vous avez nommé est interceptée plutôt qu'expédiée silencieusement
// 1. PDF/A-3 mode and output intent are already set.
// 2. Draw the visible page (embeds a real TrueType font).
DrawInvoicePage(PDF, AProfile, Data);
// 3. Build the profile-correct XML and attach it as an
// associated file with /AFRelationship = Alternative.
InvoiceXML := BuildInvoiceXML(AProfile, Data); // AnsiString of UTF-8 bytes
FileID := PDF.AddFacturXAssociatedFileFromString(
InvoiceXML,
AProfile.ConformanceLevel, // e.g. 'EN16931'
AProfile.FileName, // 'factur-x.xml'
AProfile.Description,
AProfile.Relationship, // 'Alternative'
AProfile.Version, // '1.0'
AProfile.CountryCode); // '' or 'DE' or 'FR'
if FileID <= 0 then
raise Exception.Create('Invoice XML could not be attached');
PDF.SaveToFile(TargetFile);
Une subtilité dans le chemin des données est l'encodage. Le XML intégré déclare encoding="UTF-8", et la méthode prend ses octets sous forme d'AnsiString, donc le nom d'un vendeur ou d'un acheteur non ASCII doit atteindre l'appel sous forme d'octets UTF-8 bruts. Un simple typage via la page de codes ANSI du système corromprait ces caractères et produirait discrètement une facture dont le XML ne correspond plus à sa propre déclaration. L'exemple encode en UTF-8 explicitement avant de transmettre les octets, ce qui est le moyen sûr d'alimenter toute API PDF orientée octet à partir d'une string Unicode
Pour attacher un XML qui n'est pas un profil de facture électronique reconnu, AddPDFA3AssociatedFileFromString est l'homologue générique. Il prend un nom de fichier, un type MIME, une description, une relation et des octets, et écrit un fichier associé PDF/A-3 ordinaire sans aucune métadonnée ni contrôle de directive spécifiques à la facture. Utilisez-le pour des données supplémentaires ; utilisez la méthode Factur-X pour les factures, afin que les métadonnées de profil et la concordance de directive soient écrites pour vous
Une fois le document produit, les questions suivantes sont de savoir s'il réussit la validation PDF/A et d'accessibilité, et s'il peut être signé sans rompre la conformité. Ces points sont abordés dans la visite guidée du contrôle en amont PDF/A et PDF/UA dans Delphi et le plan de travail de conformité et de signature. Tout cela s'appuie sur le même chemin de génération qui est inclus dans la Bibliothèque PDF Delphi PDFlibPas, aux côtés des API PDF/A, de marquage et de propriétés de documents sur lesquelles s'appuie le chemin de la facture électronique