Technical Article

Fatture ibride Factur-X e ZUGFeRD in Delphi

Una fattura elettronica conforme non è un PDF con un file XML pinzato a fianco. È un singolo documento PDF/A-3 che trasporta la fattura due volte: una come pagina che un essere umano legge e una come file XML Cross Industry Invoice leggibile da una macchina, memorizzato all'interno del file come file associato. Le due rappresentazioni descrivono la stessa fattura. Questa doppia natura è lo scopo primario delle famiglie di formati ora richieste dai mandati europei: Factur-X in Francia e Germania, ZUGFeRD nei mercati di lingua tedesca e XRechnung per la fatturazione del settore pubblico tedesco. Questo articolo illustra come PDFlibPas assembla una tale fattura ibrida in Delphi, dove gli standard lasciano spazio a errori, e perché un profilo nel catalogo necessita di un generatore XML completamente separato

Cos'è effettivamente una fattura ibrida

La pagina visibile e l'XML incorporato servono lettori diversi. Un impiegato che approva un pagamento guarda la pagina renderizzata. Un sistema di contabilità fornitori ingerisce l'XML, legge i totali e la ripartizione delle tasse come campi strutturati, e registra la voce senza che un essere umano digiti nulla. Il contenuto semantico di quell'XML è governato dalla norma EN 16931, lo standard europeo che definisce il modello dati della fattura: quali campi esistono, cosa significano e quali sono obbligatori. EN 16931 è un modello semantico, non un formato di file. Factur-X, ZUGFeRD 2.x e XRechnung realizzano tutti tale modello come un documento UN/CEFACT Cross Industry Invoice, la sintassi che trasporta i campi EN 16931 sul cavo (on the wire)

Affinché il documento sia sia archiviabile che autodescrittivo, il contenitore è PDF/A-3, definito dalla ISO 19005-3. Il PDF/A-3 è il livello di conformità che consente file incorporati arbitrari, che è esattamente ciò che l'XML di una fattura ha bisogno di essere. Il PDF/A-2 vieta l'incorporazione di file che non siano essi stessi PDF/A, quindi una fattura Factur-X non può essere PDF/A-2. La scelta del PDF/A-3 non è perciò una preferenza, ma un requisito che consegue direttamente dal voler incorporare dati non PDF in un documento di archiviazione

Perché la relazione (relationship) è Alternative

Incorporare i byte è la parte facile. L'ISO 32000 §7.11.4 definisce lo stream (flusso) del file incorporato, l'oggetto che contiene l'XML grezzo e i suoi parametri. La parte che rende il file un file associato valido è la §14.13, che aggiunge il concetto di file associato e la chiave /AFRelationship. Tale chiave stabilisce come i dati incorporati si relazionino al contenuto a cui sono allegati, e il valore richiesto da Factur-X è Alternative

La scelta è importante perché gli altri valori affermerebbero qualcosa di falso sul documento. Source significherebbe che l'XML è il materiale da cui è stato generato il contenuto visibile, un master da cui la pagina deriva. Supplement significherebbe che l'XML aggiunge informazioni oltre a quelle mostrate dalla pagina, un extra non contenuto nel rendering. Nessuna delle due definisce cos'è una fattura Factur-X. L'XML e la pagina sono due espressioni equivalenti di un'unica fattura, che portano lo stesso contenuto legale in due forme. Alternative è il valore che dice esattamente questo: una rappresentazione alternativa equivalente del contenuto visibile. Un validatore che legga qualsiasi altra relazione su un file Factur-X lo respingerà, e a ragione, perché la relazione è un'affermazione leggibile da macchina (machine-readable) su a cosa serve l'allegato

Il catalogo dei profili

L'esempio E-Invoice fornito con PDFlibPas guida lo stesso percorso di generazione attraverso sei profili, definiti come un array di record in InvoiceModel.pas. Ogni profilo porta con sé i valori di cui ha bisogno lo scrittore: un nome da visualizzare, il nome del file incorporato, un livello di conformità, la /AFRelationship, una versione, un codice paese opzionale e l'URN del GuidelineID che l'XML annuncia all'interno del contesto del documento

I sei sono Factur-X EN16931, Factur-X BASIC, Factur-X EXTENDED per la Francia, XRechnung 3.0, ZUGFeRD 1.0 COMFORT e ZUGFeRD 2.0 BASIC. Il GuidelineID è il campo che dice esattamente a un destinatario quale profilo aspettarsi, e i valori sono specifici. Factur-X EN16931 annuncia urn:cen.eu:en16931:2017. XRechnung 3.0 annuncia urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0. ZUGFeRD 2.0 BASIC annuncia urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p0:basic. Anche il nome del file incorporato fa parte del contratto. I profili Factur-X incorporano factur-x.xml, XRechnung incorpora xrechnung.xml e i profili ZUGFeRD incorporano ZUGFeRD-invoice.xml o zugferd-invoice.xml. Un destinatario scansiona i nomi degli allegati per trovare la fattura, quindi il nome del file non è cosmetico

Un dettaglio nel catalogo vale la pena di essere letto con attenzione. La maggior parte dei profili usa la relazione Alternative, ma la voce XRechnung 3.0 nell'esempio usa Source. I due formati rispondono a convenzioni e validatori diversi, e l'esempio imposta la relazione di ciascun profilo partendo dal catalogo piuttosto che codificando in modo rigido (hard-coding) un singolo valore, motivo per cui esiste il campo per-profilo invece di una costante

La trappola di ZUGFeRD 1.0

Si è tentati di presumere che ogni profilo sia la Cross Industry Invoice EN 16931 con variazioni minori su quanti campi opzionali si popolano. Questo vale per cinque su sei. Non vale per ZUGFeRD 1.0 COMFORT, e il motivo è strutturale anziché cosmetico

I profili moderni emettono un UN/CEFACT Cross Industry Invoice con versione del namespace :100, il cui elemento radice (root element) è rsm:CrossIndustryInvoice. ZUGFeRD 1.0 è precedente a tale schema. È il CrossIndustryDocument del 2014 con versione del namespace :1p0, e il suo elemento radice è rsm:CrossIndustryDocument. Gli URN del namespace differiscono, l'elemento radice differisce e l'albero degli elementi differisce in ogni sua parte: lo schema :1p0 raggruppa i dati in ApplicableSupplyChainTradeAgreement, ApplicableSupplyChainTradeDelivery e ApplicableSupplyChainTradeSettlement, laddove :100 usa ApplicableHeaderTradeAgreement, ApplicableHeaderTradeDelivery e ApplicableHeaderTradeSettlement. La denominazione è abbastanza simile da fuorviare e abbastanza diversa da non funzionare

La parola COMFORT nel nome del profilo descrive quanto siano ricchi i dati, un profilo di grado di automazione con voci di riga (line items) complete, scomposizione delle tasse (tax breakdown) e termini di pagamento, non quale schema lo trasporti. Quindi non si può prendere un documento :100 e rietichettarlo per ZUGFeRD 1.0. L'esempio gestisce questo aspetto con un flag su ciascun record di profilo e due funzioni di costruzione separate, selezionando quella giusta prima che venga generato alcun XML

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 divisione non è una finezza d'implementazione. Inserire un albero :100 in un destinatario ZUGFeRD 1.0 produce un documento che fallisce la validazione dello schema all'elemento radice, perciò le due famiglie devono essere costruite da codice che sappia quale di esse sta scrivendo

Selezione del livello PDF/A-3

Il PDF/A-3 possiede tre livelli di conformità, e PDFlibPas li seleziona tramite SetPDFAMode. La modalità (mode) 5 è PDF/A-3b, il livello che garantisce una riproduzione visiva affidabile. La modalità 6 è PDF/A-3a, che aggiunge i requisiti di struttura taggata e accessibilità del livello a. La modalità 7 è PDF/A-3u, la quale richiede che tutto il testo sia mappato in Unicode. L'abilitazione della modalità incorpora inoltre l'output intent sRGB integrato nella libreria, ovvero la caratterizzazione del colore richiesta dal PDF/A affinché il colore renderizzato sia definito piuttosto che dipendente dal dispositivo (device-dependent)

La maggior parte dei flussi di fatturazione viene eseguita al 3b, sufficiente per una pagina visibile fedele più l'XML incorporato. Se necessiti di un profilo ICC esplicito anziché quello integrato, LoadOutputIntentProfile lo inserisce dopo aver impostato la modalità. L'esempio carica il profilo sRGB del repository in tale modo e ricorre all'intento integrato quando il file non è raggiungibile, per cui l'output intent è sempre presente

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;

Creazione della fattura ibrida

Una volta configurato il container, il resto si svolge in tre passaggi in ordine: impostare la modalità PDF/A-3, disegnare la pagina leggibile dall'uomo, quindi allegare l'XML come file associato. La pagina visibile è un contenuto ordinario. L'unico vincolo da ricordare è che il PDF/A vieta i font Standard 14 non incorporati, quindi la pagina deve incorporare un font face reale anziché far riferimento a uno integrato

L'allegato è un'unica chiamata. AddFacturXAssociatedFileFromString prende i byte XML UTF-8 grezzi più i metadati del profilo, scrive lo stream del file incorporato, lo registra nell'array /AF del catalogo richiesto dal PDF/A-3, applica la /AFRelationship e genera i metadati dell'e-invoice XMP che identificano il documento come Factur-X, ZUGFeRD o XRechnung. Controlla anche che l'ID della linea guida (guideline ID) dell'XML corrisponda al livello di conformità richiesto, in modo che una discrepanza tra l'XML costruito e il profilo indicato venga colta anziché essere spedita silenziosamente

// 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);

Una sottigliezza nel percorso dei dati è la codifica. L'XML incorporato dichiara encoding="UTF-8" e il metodo accetta i suoi byte come AnsiString, perciò un nome di venditore o acquirente non ASCII deve giungere alla chiamata come ottetti UTF-8 grezzi. Un semplice cast tramite la code page ANSI di sistema corromperebbe tali caratteri e produrrebbe silenziosamente una fattura il cui XML non corrisponde più alla propria dichiarazione. L'esempio codifica in UTF-8 esplicitamente prima di consegnare i byte, il che è il modo sicuro per alimentare (feed) qualsiasi API PDF orientata ai byte da una string Unicode

Per allegare un XML che non è un profilo di e-invoice riconosciuto, AddPDFA3AssociatedFileFromString è la controparte generica. Prende un nome file, un tipo MIME, una descrizione, una relationship e dei byte, e scrive un file associato PDF/A-3 standard senza alcun metadato specifico della fattura o controlli delle linee guida. Usalo per i dati supplementari; usa il metodo Factur-X per le fatture, in modo che i metadati del profilo e la corrispondenza delle linee guida vengano scritti per te

Una volta prodotto il documento, le domande successive sono se supererà la convalida PDF/A e quella di accessibilità e se potrà essere firmato senza infrangere la conformità. Questioni trattate in la nostra panoramica sul preflight PDF/A e PDF/UA in Delphi e in il workbench di conformità e firma. Tutto questo viene fornito come parte della PDFlibPas Delphi PDF Library, assieme alle API di PDF/A, tagging e proprietà del documento su cui si basa il percorso di fatturazione elettronica