Una fattura Factur-X o ZUGFeRD è formata da due documenti con un unico nome file. Il documento esterno è un container PDF/A-3 che un lettore di archiviazione deve accettare per i prossimi dieci anni. Il documento interno è una fattura XML che il sistema contabile di un acquirente deve analizzare rispetto alla norma EN 16931. L'errore che manda in produzione fatture non valide è credere che ottenere il primo documento corretto fornisca il secondo gratuitamente. Non è così. Un file può essere un PDF/A-3 impeccabile e contenere ancora XML che nessuna autorità fiscale accetterà, e può contenere un perfetto XML EN 16931 all'interno di un contenitore che fallisce la convalida dell'archiviazione. I due livelli sono convalidati da due strumenti diversi che non sanno nulla l'uno dell'altro, e una vera pipeline deve soddisfarli entrambi
Due validatori, due domande diverse
veraPDF è l'implementazione di riferimento per PDF/A. Puntalo verso una fattura e risponderà a una domanda: questo è un file PDF/A-3 conforme. Controlla le cose di cui si occupa ISO 19005-3. Ogni font è incorporato. C'è un OutputIntent. I metadati XMP dichiarano la parte e il livello di conformità corretti. Per una fattura elettronica (e-invoice) controlla anche l'infrastruttura del file associato richiesta dal PDF/A-3, perché l'XML viaggia insieme come file incorporato con un /AFRelationship e una voce nell'array /AF del catalogo del documento. veraPDF non dice nulla sul fatto che il totale della fattura torni o meno, perché ciò non rientra nelle sue competenze
Mustang è il validatore open source del Mustangproject. Pone la domanda ortogonale: l'XML incorporato è una fattura valida. Esegue l'XML rispetto allo schema per il profilo dichiarato e quindi applica le regole aziendali (business rules) EN 16931 e i set di regole specifici per paese stratificati su di esse, tra cui la CIUS di XRechnung. Controlla che sia presente un identificatore IVA del venditore quando i totali lo richiedono, che gli importi di sconti e maggiorazioni (allowance and charge) si riconcilino con il totale del documento, che l'URN del profilo nell'XML corrisponda a ciò che il file dichiara di essere. A Mustang non importa se il PDF circostante incorpora i suoi font, perché questo è il lavoro di veraPDF
Nessuno dei due strumenti è un superset dell'altro. veraPDF fa passare un container strutturalmente perfetto che avvolge XML senza senso. Mustang fa passare un XML perfetto avvolto in un contenitore con un OutputIntent mancante. Ciascuno cattura esattamente la classe di difetti a cui l'altro è cieco, il che è l'intero motivo per cui un'imbracatura (harness) di validazione seria li esegue entrambi e tratta un file come spedibile solo quando entrambi concordano
La matrice di validazione
Per dimostrare che la libreria produce file che sopravvivono a entrambi i cancelli (gates), l'harness costruisce una matrice. Sei profili di fattura coprono l'intervallo che una pipeline europea incontra in pratica: Factur-X EN 16931, Factur-X BASIC, la variante Factur-X EXTENDED France B2B, XRechnung 3.0, ZUGFeRD 1.0 COMFORT e ZUGFeRD 2.0 BASIC. Ogni profilo viene generato rispetto a due livelli di sotto-conformità PDF/A, 3b e 3u, perché i requisiti di livello B e di livello U divergono sulla mappatura Unicode e un file che supera l'uno può fallire l'altro. Sei profili moltiplicati per due livelli danno dodici file, ognuno dei quali è compilato in modalità headless (senza interfaccia utente) tramite lo stesso percorso di codice dell'esempio GUI, cosicché gli artefatti in fase di test non siano ottimizzati a mano appositamente per la prova
Il generatore scrive tutti e dodici i file e uno script ne fornisce uno a ciascun validatore. Nella prima esecuzione completa veraPDF li ha approvati tutti e dodici. L'infrastruttura del container era corretta su tutta la linea: file associati registrati, conformità XMP dichiarata, intenti di output al loro posto. Mustang ne ha superati otto. Quattro fatture erano file PDF/A-3 strutturalmente validi ma contenevano un XML che il validatore di regole di business ha respinto, il che è precisamente lo sdoppiamento che l'approccio a due strumenti è in grado di far emergere. Se l'harness si fosse fidato solo di veraPDF, quei quattro sarebbero sembrati pronti
Le due correzioni che hanno colmato il divario
I quattro fallimenti in Mustang derivavano da due cause distinte, e la correzione per ciascuna è un dettaglio che vale la pena conoscere prima di generare questi profili tu stesso
La prima era il profilo Factur-X EXTENDED France B2B. Il generatore originale passava un'etichetta interna come livello di conformità e un URN interno come linea guida (guideline), e Mustang ha rifiutato il file con un errore di valore di conformità non valido seguito da un errore di tipo di profilo non supportato. Il motivo è che il campo XMP fx:ConformanceLevel non è uno slot a testo libero per la denominazione del tuo profilo. Factur-X definisce per esso esattamente cinque valori standard: MINIMUM, BASIC WL, BASIC, EN 16931 ed EXTENDED. Una fattura B2B specifica per la Francia è ancora un documento a profilo EXTENDED per quanto riguarda i metadati XMP. Il carattere francese della fattura non è espresso inventando un sesto valore di conformità. È espresso dal codice paese, FR, e dall'identificatore della linea guida all'interno dell'XML, che deve portare il prefisso urn:cen.eu:en16931:2017#conformant# il quale contrassegna una CIUS conforme alla EN 16931. Il passaggio del valore standard EXTENDED con FR come codice paese e l'URN della linea guida corretto ha reso il file conforme
Nell'API della libreria si tratta di una chiamata a AddFacturXAssociatedFileFromString con la conformità, il paese e la linea guida allineati. L'argomento del livello di conformità porta il token standard, l'argomento del codice paese porta FR e l'URN della linea guida risiede nei byte XML immessi
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 seconda causa era il profilo ZUGFeRD 1.0 COMFORT, e non aveva nulla a che fare con i metadati. ZUGFeRD 1.0 viene convalidato rispetto all'XSD :1p0, che è più severo sulla cardinalità di quanto non suggeriscano i riassunti in prosa. L'XSD richiede che la sommatoria del regolamento dell'intestazione (header settlement summation), ram:SpecifiedTradeSettlementMonetarySummation, contenga ram:ChargeTotalAmount e ram:AllowanceTotalAmount ciascuno esattamente una volta. L'XML generato ometteva entrambi, quindi Mustang segnalava che gli elementi devono ricorrere esattamente una volta. Questi non sono facoltativi quando lo schema dice che minOccurs è uno. Emettere entrambi nell'ordine di sequenza XSD, subito dopo ram:LineTotalAmount, con un valore di 0.00 quando non ci sono spese (charges) o sconti (allowances), ha soddisfatto lo schema. Uno zero è un elemento presente; un elemento assente è una violazione dello schema. Con queste due correzioni in atto, la matrice è passata a dodici su dodici in Mustang pur rimanendo a dodici su dodici in veraPDF
I campi XRechnung che ribaltano da invalido a valido
XRechnung merita una nota a sé stante perché la sua CIUS tedesca aggiunge regole aziendali che sono assenti dal set di base EN 16931, e falliscono in modi tali che sembra non ci sia nulla di sbagliato nel documento a prima vista. Due di essi riguardano gli indirizzi elettronici. BT-34 è l'indirizzo elettronico del venditore e BT-49 è l'indirizzo elettronico dell'acquirente, ovvero gli endpoint di instradamento che un portale del settore pubblico tedesco usa per recapitare e confermare la fattura. Il modello base EN 16931 li considera opzionali. XRechnung no. Ometti uno dei due e la fattura sarà ben formata (well-formed), valida per lo schema e scartata (rejected)
La terza è la regola BR-DE-6, che richiede la presenza del numero di telefono di contatto del venditore. È il tipo di campo che uno sviluppatore tralascia perché sembra presentazione piuttosto che dati, e la sua assenza produce un errore di validazione che punta al gruppo di contatti del venditore anziché a qualcosa di palesemente mancante. Fornire BT-34, BT-49 e il numero di telefono del venditore è ciò che sposta un file XRechnung da non valido a valido in Mustang, e nulla di tutto ciò cambia alcunché di quanto visto da veraPDF, perché tutti e tre si trovano nell'XML
Collegare l'output della libreria a un validatore
Il punto architetturale alla base dell'harness si generalizza a qualsiasi sistema aziendale. La libreria PDF scrive un contenitore conforme e incorpora l'XML. Non tenta, e non dovrebbe tentare, di essere l'autorità sulle regole aziendali (business rules) EN 16931. ValidateFacturXInvoice nella libreria controlla la coerenza del contenitore, che l'array /AF del catalogo, l'albero dei nomi dei file incorporati (embedded-files name tree), il DocumentFileName XMP, il profilo, la guideline e la /AFRelationship concordino tutti, ma non valida i codici fiscali né riconcilia gli importi. La giusta divisione del lavoro consiste nel far sì che il sistema aziendale estragga l'XML e lo consegni a un validatore di fatture dedicato, esattamente come l'harness lo consegna a Mustang
Rileggere il file ti dice cosa è stato effettivamente scritto. DetectFacturXInvoice riporta se una fattura è stata riconosciuta e GetFacturXInvoiceInfo legge i campi dei metadati tramite tag: il tag 1 è il nome del file incorporato, il tag 2 è il DocumentFileName XMP, il tag 5 è il livello di conformità, il tag 6 è l'identificatore della linea guida (guideline) e il tag 7 è la /AFRelationship. Verificare che il livello di conformità riletto sia il token standard e non un'etichetta interna è il modo più economico per cogliere l'errore EXTENDED prima che un file lasci la build
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 restituisce i byte XML non elaborati (raw) come una AnsiString, pronti per essere scritti in un file o inviati in streaming (streamed) in un processo di validazione. Nell'harness di test la destinazione è Mustang, invocato tramite il suo file jar da riga di comando, assieme a veraPDF eseguito nel medesimo passaggio sullo stesso file. Il collegamento è di piccola entità: un generatore da console, EInvoiceValidation.dpr, scrive i dodici file sfruttando il modello di fattura condivisa dell'esempio, e uno script, run-validation.ps1, guida entrambi i validatori sulla directory di output stampando una tabella di pass e fail (superati e falliti). La medesima forma in due passaggi, generazione con la libreria e verifica con validatori esterni, è ciò che un processo di continuous integration dovrebbe eseguire ad ogni modifica della generazione delle fatture, poiché l'unico modo per sapere che un file soddisfa entrambi i livelli è interrogare ambedue gli strumenti
Se la tua pipeline deve anche certificare il container prima della firma, il lato di preflight (verifica preliminare) di tale lavoro è trattato nella nostra panoramica sul preflight PDF/A e PDF/UA in Delphi, e il flusso più ampio di certificazione e successiva firma è descritto in il workbench di conformità e firma. Entrambi si basano sullo stesso percorso di generazione offerto come parte della Delphi PDF Library per Delphi e C++Builder, affiancati alle API per PDF/A, file associati e metadati utilizzate qui