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
Een validatiematrix bouwen
Om te bewijzen dat gegenereerde bestanden beide poorten doorstaan, bouw je 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, en elk ervan zou headless gebouwd moeten worden via hetzelfde codepad dat uw toepassing gebruikt, zodat de geteste artefacten niet handmatig voor de test zijn aangepast
Voer elk bestand aan beide validators en de scheiding wordt zichtbaar. Containerinfrastructuur die over de hele linie correct is — gekoppelde bestanden geregistreerd, XMP-conformiteit gedeclareerd, output intents aanwezig — komt bij veraPDF door elk bestand heen, terwijl een structureel geldige PDF/A-3 nog steeds XML kan bevatten die de bedrijfsregelvalidator afwijst. Dat is precies de scheiding die de aanpak met twee tools aan het licht moet brengen: een testsysteem dat alleen veraPDF vertrouwde, zou die bestanden als gereed beschouwen
Twee valkuilen die bij veraPDF slagen en bij Mustang falen
Het zijn allebei details die goed om te weten zijn voordat u deze profielen zelf genereert
De eerste is het Factur-X EXTENDED France B2B-profiel. Geef een intern label door als conformiteitsniveau en een interne URN als richtlijn, en Mustang wijst het bestand af met een invalid-conformance-value-fout gevolgd door een unsupported-profile-type-fout. De reden is dat het XMP-veld fx:ConformanceLevel geen vrije-tekstplek is voor uw eigen profielbenaming. Factur-X definieert er precies vijf standaardwaarden voor: MINIMUM, BASIC WL, BASIC, EN 16931 en EXTENDED. Een Frankrijk-specifieke B2B-factuur is wat de XMP-metadata betreft nog steeds een EXTENDED-profieldocument. Het Franse karakter van de factuur wordt niet uitgedrukt door een zesde conformiteitswaarde te verzinnen. Het wordt uitgedrukt door de landcode, FR, en door de richtlijnidentificatie in de XML, die het voorvoegsel urn:cen.eu:en16931:2017#conformant# moet dragen dat een CIUS markeert die conform EN 16931 is. De standaard EXTENDED-waarde doorgeven met FR als landcode en de juiste richtlijn-URN maakt het bestand conformant
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 is het ZUGFeRD 1.0 COMFORT-profiel, en dit heeft 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. Laat beide weg en Mustang meldt dat de elementen precies één keer moeten 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, voldoet aan het schema. Een nul is een aanwezig element; een afwezig element is een schending van het schema
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