Technisch artikel

E-facturen valideren: veraPDF en Mustang in Delphi

Een Factur-X of ZUGFeRD-factuur is in feite twee documenten onder één bestandsnaam. Het buitenste document is een PDF/A-3-container die een archiveringslezer de komende tien jaar moet accepteren. Het binnenste document is een XML-factuur die het boekhoudsysteem van een koper moet verwerken volgens EN 16931. De fout die ervoor zorgt dat onvolledige facturen in productie belanden, is de overtuiging dat het in orde maken van de eerste automatisch ook de tweede in orde maakt. Dat is niet zo. Een bestand kan een foutloze PDF/A-3 zijn en toch XML bevatten die door geen enkele belastingdienst wordt geaccepteerd, en het kan schoolvoorbeeld EN 16931 XML bevatten in een container die niet door de archiveringsvalidatie komt. De twee lagen worden gevalideerd door twee verschillende tools die niets van elkaar weten, en een echte pijplijn moet aan beide voldoen.

Twee validators, twee verschillende vragen

veraPDF is de referentie-implementatie voor PDF/A. Wijs het op een factuur en het beantwoordt één vraag: is dit een conform PDF/A-3 bestand? Het controleert de zaken waar ISO 19005-3 om geeft. Is elk lettertype ingesloten? Is er een OutputIntent? Verklaart de XMP-metadata het juiste onderdeel en conformiteitsniveau? Voor een e-factuur controleert het ook de infrastructuur voor het gekoppelde bestand die PDF/A-3 vereist, omdat de XML meelift als een ingesloten bestand met een /AFRelationship en een vermelding in de /AF-array van de documentencatalogus. veraPDF zegt niets over de vraag of het totaalbedrag van de factuur klopt, omdat dat niet tot zijn taken behoort.

Mustang is de open-source validator van het Mustangproject. Het stelt de orthogonale vraag: is de ingesloten XML een geldige factuur? Het vergelijkt de XML met het schema voor het gedeclareerde profiel en past vervolgens de EN 16931-bedrijfsregels toe en de landspecifieke regelsets die daarop zijn gestapeld, waaronder de CIUS van XRechnung. Het controleert of er een btw-nummer van de verkoper aanwezig is wanneer de totalen daarom vragen, of toeslagen en kortingen overeenkomen met het documenttotaal, of de profiel-URN in de XML overeenkomt met wat het bestand beweert te zijn. Mustang geeft er niet om of de omringende PDF zijn lettertypen insluit, want dat is de taak van veraPDF.

Geen van beide tools is een superset van de andere. veraPDF keurt een structureel perfecte container rond onzin-XML goed. Mustang keurt perfecte XML goed die is verpakt in een container met een ontbrekende OutputIntent. Elk vangt precies de soort defecten op waar de andere blind voor is, wat de hele reden is dat een serieus validatiesysteem beide uitvoert en een bestand alleen als verzendbaar behandelt wanneer beide het met elkaar eens zijn.

De validatiematrix

Om te bewijzen dat de bibliotheek bestanden produceert die beide tests doorstaan, bouwt het testsysteem een matrix. Zes factuurprofielen dekken het bereik dat een Europese pijplijn in de praktijk tegenkomt: Factur-X EN 16931, Factur-X BASIC, de Factur-X EXTENDED France B2B-variant, XRechnung 3.0, ZUGFeRD 1.0 COMFORT en ZUGFeRD 2.0 BASIC. Elk profiel wordt gegenereerd op basis van twee PDF/A sub-conformiteitsniveaus, 3b en 3u, omdat de niveau B- en niveau U-vereisten afwijken wat betreft Unicode-toewijzing en een bestand dat de ene doorstaat, voor de andere kan falen. Zes profielen maal twee niveaus is twaalf bestanden, elk 'headless' gebouwd via hetzelfde codepad dat het GUI-voorbeeld levert, zodat de geteste artefacten niet handmatig zijn aangepast voor de test.

De generator schrijft ze alle twaalf en een script voert ze stuk voor stuk in bij beide validators. Bij de eerste volledige test slaagden alle twaalf bij veraPDF. De infrastructuur van de container was over de hele linie correct: gekoppelde bestanden geregistreerd, XMP-conformiteit gedeclareerd, output intents aanwezig. Mustang keurde er acht goed. Vier facturen waren structureel geldige PDF/A-3 bestanden die XML bevatten die door de bedrijfsregelvalidator werd afgewezen, wat precies de scheiding is die de tweeledige benadering aan het licht moet brengen. Als het testsysteem alleen veraPDF had vertrouwd, zouden die vier bestanden als gereed zijn beschouwd.

De twee oplossingen die het verschil overbrugden

De vier mislukkingen bij Mustang kwamen voort uit twee verschillende oorzaken, en de oplossing voor elk ervan is een detail dat het waard is om te weten voordat u deze profielen zelf genereert.

De eerste was het Factur-X EXTENDED France B2B-profiel. De originele generator gaf een intern label door als conformiteitsniveau en een interne URN als richtlijn, en Mustang weigerde het bestand met een invalid-conformance-value fout, gevolgd door een unsupported-profile-type fout. De reden is dat het XMP fx:ConformanceLevel veld geen vrij in te vullen tekstvak is voor uw eigen profielnaamgeving. Factur-X definieert hiervoor precies vijf standaardwaarden: MINIMUM, BASIC WL, BASIC, EN 16931 en EXTENDED. Een Frankrijk-specifieke B2B-factuur is nog steeds een document met een EXTENDED-profiel voor zover het de XMP-metadata betreft. Het Franse karakter van de factuur wordt niet uitgedrukt door een zesde conformiteitswaarde uit te vinden. Het wordt uitgedrukt door de landcode, FR, en door de richtlijn-identifier in de XML, die het voorvoegsel urn:cen.eu:en16931:2017#conformant# moet bevatten dat een CIUS markeert die conform is aan EN 16931. Het doorgeven van de standaard EXTENDED-waarde met FR als landcode en de juiste richtlijn-URN maakte het bestand conform.

In de bibliotheek-API is dat een aanroep naar AddFacturXAssociatedFileFromString waarbij de conformiteit, het land en de richtlijn op elkaar zijn afgestemd. Het argument voor het conformiteitsniveau bevat de standaardtoken, het argument voor de landcode bevat FR, en de richtlijn-URN bevindt zich in de XML-bytes die u doorgeeft.

var
  FileID: Integer;
begin
  PDF.SetPDFAMode(5);            // PDF/A-3b
  PDF.NewDocument;
  // ... teken de voor mensen leesbare factuurpagina ...
  // ExtendedXML bevat een EN 16931-richtlijn-URN met de notatie
  //   urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended
  FileID := PDF.AddFacturXAssociatedFileFromString(
    ExtendedXML,
    'EXTENDED',          // standaard fx:ConformanceLevel, geen intern label
    'factur-x.xml',
    'Factur-X EXTENDED invoice',
    'Alternative',       // /AFRelationship
    '1.0',
    'FR');               // France B2B wordt gemarkeerd door de landcode, niet door conformiteit
  if FileID = 0 then
    raise Exception.Create('Factur-X attachment rejected');
  PDF.SaveToFile('02_Factur-X-EXTENDED-FR_PDFA-3b.pdf');
end;

De tweede oorzaak was het ZUGFeRD 1.0 COMFORT-profiel, en dit had niets te maken met metadata. ZUGFeRD 1.0 wordt gevalideerd aan de hand van de :1p0 XSD, die strenger is over kardinaliteit dan de proza-samenvattingen suggereren. De XSD vereist dat de sommatie van de afrekening in de header, ram:SpecifiedTradeSettlementMonetarySummation, ram:ChargeTotalAmount en ram:AllowanceTotalAmount elk precies één keer bevat. De gegenereerde XML liet beide weg, dus Mustang meldde dat de elementen precies één keer moesten voorkomen. Deze zijn niet optioneel wanneer het schema zegt dat minOccurs één is. Beide uitzenden in XSD-volgorde, direct na ram:LineTotalAmount, met een waarde van 0.00 wanneer er geen kosten of kortingen zijn, voldeed aan het schema. Een nul is een aanwezig element; een afwezig element is een schending van het schema. Met deze twee oplossingen erin, ging de matrix naar twaalf van de twaalf in Mustang, terwijl deze op twaalf van de twaalf bleef staan in veraPDF.

De XRechnung-velden die ongeldig omzetten naar geldig

XRechnung verdient een eigen notitie omdat de Duitse CIUS bedrijfsregels toevoegt die ontbreken in de basis EN 16931-set, en deze falen op manieren waardoor het lijkt alsof er op het eerste gezicht niets mis is met het document. Twee hiervan betreffen elektronische adressen. BT-34 is het elektronische adres van de verkoper en BT-49 is het elektronische adres van de koper; dit zijn de routeringseindpunten die een Duits overheidsportaal gebruikt om de factuur af te leveren en te bevestigen. Het basis EN 16931-model beschouwt deze als optioneel. XRechnung niet. Laat één van beide weg en de factuur is goed gevormd, schema-geldig en afgewezen.

De derde is regel BR-DE-6, die vereist dat het telefoonnummer van de contactpersoon van de verkoper aanwezig is. Het is het soort veld dat een ontwikkelaar weglaat omdat het meer als presentatie dan als data aanvoelt, en de afwezigheid ervan leidt tot een validatiefout die wijst naar de contactgroep van de verkoper in plaats van naar iets dat duidelijk ontbreekt. Het toevoegen van BT-34, BT-49 en het telefoonnummer van de verkoper is wat een XRechnung-bestand van ongeldig naar geldig brengt onder Mustang, en niets hiervan verandert iets aan wat veraPDF ziet, omdat ze alle drie in de XML staan.

Bibliotheekuitvoer koppelen aan een validator

Het architectonische punt achter het testsysteem generaliseert naar elk bedrijfssysteem. De PDF-bibliotheek schrijft een conforme container en sluit de XML in. Het probeert niet en zou niet moeten proberen de autoriteit te zijn voor de EN 16931-bedrijfsregels. ValidateFacturXInvoice in de bibliotheek controleert de consistentie van de container, of de catalogus /AF array, de naamstructuur van de ingesloten bestanden, de XMP DocumentFileName, het profiel, de richtlijn en de /AFRelationship allemaal overeenkomen, maar het valideert geen belastingcodes of stemt geen bedragen af. De juiste taakverdeling is dat het bedrijfssysteem de XML extraheert en deze doorgeeft aan een speciale factuurvalidator, precies zoals het testsysteem dit doorgeeft aan Mustang.

Door het bestand terug te lezen, ziet u wat er daadwerkelijk is geschreven. DetectFacturXInvoice rapporteert of een factuur werd herkend, en GetFacturXInvoiceInfo leest de metadata-velden per tag: tag 1 is de ingesloten bestandsnaam, tag 2 de XMP DocumentFileName, tag 5 het conformiteitsniveau, tag 6 de richtlijn-identifier en tag 7 de /AFRelationship. Bevestigen dat het conformiteitsniveau dat u terugleest de standaardtoken is en geen intern label, is de goedkoopste manier om de EXTENDED-fout op te sporen voordat een bestand uw build verlaat.

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-richtlijn ID
    Writeln('Profile:   ', Profile);
    Writeln('Guideline: ', Guideline);
    // Geef de ruwe XML door aan een speciale EN 16931 / Mustang-validator.
    Result := PDF.ExtractFacturXXMLToString;
  end;
end;

ExtractFacturXXMLToString retourneert de ruwe XML-bytes als een AnsiString, klaar om naar een bestand te schrijven of te streamen naar een validatorproces. In het testsysteem is dat doel Mustang, aangeroepen via de command-line jar, waarbij veraPDF in dezelfde passage over hetzelfde bestand wordt uitgevoerd. De bedrading is klein: een console-generator, EInvoiceValidation.dpr, schrijft de twaalf bestanden met behulp van het gedeelde factuurmodel uit het voorbeeld, en een script, run-validation.ps1, stuurt beide validators over de uitvoermap en drukt een tabel af met geslaagd en mislukt. Dezelfde tweestapsvorm, genereren met de bibliotheek en verifiëren met externe validators, is wat een continuous-integration-taak zou moeten uitvoeren bij elke wijziging in de factuurgeneratie, omdat de enige manier om te weten of een bestand aan beide lagen voldoet, is door het aan beide tools te vragen.

Als uw pijplijn ook de container moet certificeren vóór ondertekening, wordt de preflight-kant van dit werk behandeld in onze walkthrough van PDF/A- en PDF/UA-preflight in Delphi, en de bredere certify-then-sign-flow wordt beschreven in de nalevings- en ondertekeningswerkbank. Beide bouwen voort op hetzelfde generatiepad dat wordt meegeleverd als onderdeel van de Delphi PDF Library voor Delphi en C++Builder, naast de PDF/A, gekoppelde bestanden en metadata-API's die hier worden gebruikt.