Teknisk artikel

Validering af e-fakturaer: veraPDF og Mustang i Delphi

En Factur-X- eller ZUGFeRD-faktura er to dokumenter under ét filnavn. Det ydre dokument er en PDF/A-3-container, som en arkivlæser skal kunne acceptere i de næste ti år. Det indre dokument er en XML-faktura, som køberens regnskabssystem skal kunne parse mod EN 16931. Den fejl, der sender defekte fakturaer i produktion, er at tro, at når det første er i orden, følger det andet gratis med. Det gør det ikke. En fil kan være en fejlfri PDF/A-3 og stadig bære XML, som ingen skattemyndighed vil acceptere, og den kan bære lærebogskorrekt EN 16931-XML inde i en container, der dumper arkivvalideringen. De to lag valideres af to forskellige værktøjer, der intet ved om hinanden, og en rigtig pipeline skal tilfredsstille begge

To validatorer, to forskellige spørgsmål

veraPDF er referenceimplementeringen for PDF/A. Peg den på en faktura, og den besvarer ét spørgsmål: er dette en konform PDF/A-3-fil. Den kontrollerer det, ISO 19005-3 går op i. Er alle skrifttyper indlejret. Findes der et OutputIntent. Deklarerer XMP-metadataene den rigtige del og det rigtige konformitetsniveau. For en e-faktura kontrollerer den også den associated-file-mekanik, som PDF/A-3 kræver, fordi XML'en følger med som en indlejret fil med et /AFRelationship og en post i dokumentkatalogets /AF-array. veraPDF siger intet om, hvorvidt fakturatotalen stemmer, for det ligger uden for dens ansvarsområde

Mustang er open source-validatoren fra Mustangproject. Den stiller det ortogonale spørgsmål: er den indlejrede XML en gyldig faktura. Den kører XML'en mod skemaet for den deklarerede profil og anvender derefter EN 16931-forretningsreglerne og de landespecifikke regelsæt, der er lagt ovenpå, heriblandt XRechnungs CIUS. Den kontrollerer, at der findes et moms-id for sælger, når totalerne kræver et, at rabat- og gebyrbeløb stemmer overens med dokumenttotalen, og at profil-URN'en i XML'en svarer til det, filen hævder at være. Mustang er ligeglad med, om den omgivende PDF indlejrer sine skrifttyper, for det er veraPDFs opgave

Ingen af værktøjerne er et supersæt af det andet. veraPDF godkender en strukturelt perfekt container omkring meningsløs XML. Mustang godkender perfekt XML pakket ind i en container, der mangler et OutputIntent. Hvert værktøj fanger præcis den klasse af defekter, det andet er blindt for, og det er hele grunden til, at en seriøs valideringsharness kører begge og først betragter en fil som leveringsklar, når begge er enige

PDF Library for Delphi: Lagdelt diagram af en Factur-X e-fakturafil: den ydre PDF/A-3-arkivcontainer med sin /AFRelationship- og /AF-mekanik inspiceret af veraPDF, og den indre factur-x.xml EN 16931-faktura-XML inspiceret af Mustang, hvor ingen af værktøjerne dækker det andet lag
Det ydre PDF/A-3-lag står til ansvar over for veraPDF, mens Mustang bedømmer den indlejrede EN 16931-XML, og ingen af kontrollerne indebærer den anden

Valideringsmatricen

For at bevise, at biblioteket producerer filer, der overlever begge porte, bygger harnessen en matrix. Seks fakturaprofiler dækker det spænd, en europæisk pipeline møder i praksis: Factur-X EN 16931, Factur-X BASIC, Factur-X EXTENDED-varianten for fransk B2B, XRechnung 3.0, ZUGFeRD 1.0 COMFORT og ZUGFeRD 2.0 BASIC. Hver profil genereres mod to PDF/A-underkonformitetsniveauer, 3b og 3u, fordi kravene til niveau B og niveau U afviger med hensyn til Unicode-mapping, og en fil, der består det ene, kan dumpe det andet. Seks profiler gange to niveauer er tolv filer, som alle bygges headless ad den samme kodesti, som GUI-eksemplet leveres med, så de artefakter, der testes, ikke er håndjusteret til testen

Generatoren skriver alle tolv, og et script fodrer hver af dem til begge validatorer. Ved den første fulde kørsel bestod alle tolv i veraPDF. Containermekanikken var korrekt hele vejen rundt: associated files registreret, XMP-konformitet deklareret, output intents på plads. Mustang godkendte otte. Fire fakturaer var strukturelt gyldige PDF/A-3-filer med XML, som forretningsregel-validatoren afviste, hvilket er præcis den opdeling, to-værktøjs-tilgangen findes for at afsløre. Havde harnessen stolet på veraPDF alene, ville de fire have set færdige ud

PDF Library for Delphi: Oversigt over valideringsmatrix: seks fakturaprofiler fra Factur-X EN 16931 til ZUGFeRD 2.0 BASIC genereret på niveau 3b og 3u giver tolv filer, veraPDF godkender tolv af tolv, mens Mustang kun godkender otte, og to rettelser af konformitetstokenet og de obligatoriske summationselementer lukker hullet
Seks profiler på to konformitetsniveauer giver tolv filer, alle tolv grønne i veraPDF og kun otte grønne i Mustang

De to rettelser, der lukkede hullet

De fire Mustang-fejl skyldtes to forskellige årsager, og rettelsen af hver er en detalje, der er værd at kende, før du selv genererer disse profiler

Den første var Factur-X EXTENDED-profilen for fransk B2B. Den oprindelige generator sendte en intern etiket som konformitetsniveau og en intern URN som guideline, og Mustang afviste filen med en fejl om ugyldig konformitetsværdi efterfulgt af en fejl om ikke-understøttet profiltype. Grunden er, at XMP-feltet fx:ConformanceLevel ikke er et fritekstfelt til din egen profilnavngivning. Factur-X definerer præcis fem standardværdier for det: MINIMUM, BASIC WL, BASIC, EN 16931 og EXTENDED. En franskspecifik B2B-faktura er stadig et dokument i EXTENDED-profilen, for så vidt angår XMP-metadataene. Fakturaens franske karakter udtrykkes ikke ved at opfinde en sjette konformitetsværdi. Den udtrykkes gennem landekoden, FR, og gennem guideline-identifikatoren inde i XML'en, som skal bære præfikset urn:cen.eu:en16931:2017#conformant#, der markerer en CIUS, som er konform med EN 16931. At sende standardværdien EXTENDED med FR som landekode og den korrekte guideline-URN gjorde filen konform

I bibliotekets API er det et kald til AddFacturXAssociatedFileFromString med konformitet, land og guideline bragt på linje. Konformitetsniveau-argumentet bærer standardtokenet, landekode-argumentet bærer FR, og guideline-URN'en ligger i de XML-bytes, du sender ind

var
  FileID: Integer;
begin
  PDF.SetPDFAMode(5);            // PDF/A-3b
  PDF.NewDocument;
  // ... tegn den menneskelæsbare fakturaside ...
  // ExtendedXML bærer en EN 16931-guideline-URN på formen
  //   urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended
  FileID := PDF.AddFacturXAssociatedFileFromString(
    ExtendedXML,
    'EXTENDED',          // standard fx:ConformanceLevel, ikke en intern etiket
    'factur-x.xml',
    'Factur-X EXTENDED invoice',
    'Alternative',       // /AFRelationship
    '1.0',
    'FR');               // fransk B2B markeres med landekode, ikke med konformitet
  if FileID = 0 then
    raise Exception.Create('Factur-X attachment rejected');
  PDF.SaveToFile('02_Factur-X-EXTENDED-FR_PDFA-3b.pdf');
end;

Den anden årsag var ZUGFeRD 1.0 COMFORT-profilen, og den havde intet med metadata at gøre. ZUGFeRD 1.0 valideres mod :1p0-XSD'en, som er strengere med hensyn til kardinalitet, end prosaresuméerne antyder. XSD'en kræver, at header-afregningssummationen, ram:SpecifiedTradeSettlementMonetarySummation, indeholder ram:ChargeTotalAmount og ram:AllowanceTotalAmount præcis én gang hver. Den genererede XML udelod begge, så Mustang rapporterede, at elementerne skal forekomme præcis én gang. De er ikke valgfrie, når skemaet siger, at minOccurs er ét. At udsende begge i XSD-sekvensrækkefølge, umiddelbart efter ram:LineTotalAmount, med værdien 0.00, når der ikke er gebyrer eller rabatter, tilfredsstillede skemaet. Et nul er et tilstedeværende element; et fraværende element er en skemaovertrædelse. Med de to rettelser på plads gik matricen til tolv af tolv i Mustang og forblev tolv af tolv i veraPDF

XRechnung-felterne, der vender ugyldig til gyldig

XRechnung fortjener sin egen bemærkning, fordi dens tyske CIUS tilføjer forretningsregler, der ikke findes i det grundlæggende EN 16931-sæt, og de fejler på måder, hvor dokumentet ved første øjekast ser fejlfrit ud. To af dem vedrører elektroniske adresser. BT-34 er sælgers elektroniske adresse, og BT-49 er købers elektroniske adresse, de routing-endepunkter en tysk offentlig portal bruger til at levere og kvittere for fakturaen. Den grundlæggende EN 16931-model behandler dem som valgfrie. Det gør XRechnung ikke. Udelad en af dem, og fakturaen er velformet, skemagyldig og afvist

Den tredje er regel BR-DE-6, som kræver, at sælgers kontakttelefonnummer er til stede. Det er den slags felt, en udvikler dropper, fordi det føles som præsentation snarere end data, og dets fravær giver en valideringsfejl, der peger på sælgers kontaktgruppe frem for på noget åbenlyst manglende. At levere BT-34, BT-49 og sælgers telefonnummer er det, der flytter en XRechnung-fil fra ugyldig til gyldig i Mustang, og intet af det ændrer noget, veraPDF ser, fordi alle tre ligger i XML'en

Kobling af bibliotekets output til en validator

Den arkitektoniske pointe bag harnessen kan generaliseres til ethvert forretningssystem. PDF-biblioteket skriver en konform container og indlejrer XML'en. Det forsøger ikke, og bør ikke forsøge, at være autoriteten for EN 16931-forretningsregler. ValidateFacturXInvoice i biblioteket kontrollerer containerkonsistens, at katalogets /AF-array, navnetræet for indlejrede filer, XMP-feltet DocumentFileName, profilen, guidelinen og /AFRelationship alle stemmer overens, men det validerer ikke skattekoder eller afstemmer beløb. Den rigtige arbejdsdeling er, at forretningssystemet udtrækker XML'en og overdrager den til en dedikeret fakturavalidator, præcis som harnessen overdrager den til Mustang

At læse filen tilbage fortæller dig, hvad der faktisk blev skrevet. DetectFacturXInvoice rapporterer, om en faktura blev genkendt, og GetFacturXInvoiceInfo læser metadatafelterne efter tag: tag 1 er den indlejrede fils navn, tag 2 XMP-feltet DocumentFileName, tag 5 konformitetsniveauet, tag 6 guideline-identifikatoren og tag 7 /AFRelationship. At bekræfte, at det konformitetsniveau, du læser tilbage, er standardtokenet og ikke en intern etiket, er den billigste måde at fange EXTENDED-fejlen på, før en fil forlader dit build

PDF Library for Delphi pipeline-diagram, der kobler bibliotekets output til eksterne validatorer: generatoren skriver invoice.pdf headless, veraPDF vogter containeren, ExtractFacturXXMLToString fodrer Mustang med EN 16931-forretningsreglerne, og leveringsporten returnerer nul, kun når begge validatorer består
Pipelinen genererer headless, overdrager det samme artefakt til begge validatorer og leverer kun, når deres samlede dom er grøn
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);
    // Overdrag den rå XML til en dedikeret EN 16931- / Mustang-validator.
    Result := PDF.ExtractFacturXXMLToString;
  end;
end;

ExtractFacturXXMLToString returnerer de rå XML-bytes som en AnsiString, klar til at blive skrevet til en fil eller streamet ind i en validatorproces. I testharnessen er målet Mustang, kaldt via dens kommandolinje-jar, med veraPDF kørt i samme omgang over den samme fil. Koblingen er lille: en konsolgenerator, EInvoiceValidation.dpr, skriver de tolv filer ved hjælp af den delte fakturamodel fra eksemplet, og et script, run-validation.ps1, driver begge validatorer over outputmappen og udskriver en tabel over beståede og fejlede. Den samme totrinsform, generér med biblioteket og verificér med eksterne validatorer, er det, et continuous integration-job bør køre ved hver ændring af fakturagenereringen, for den eneste måde at vide, at en fil opfylder begge lag, er at spørge begge værktøjer

Hvis din pipeline også skal certificere containeren før signering, er preflight-siden af dette arbejde dækket i vores gennemgang af PDF/A- og PDF/UA-preflight i Delphi, og det bredere certificér-og-signér-flow er beskrevet i compliance- og signeringsarbejdsbænken. Begge bygger på den samme genereringssti, som leveres som en del af Delphi PDF Library til Delphi og C++Builder, sammen med de PDF/A-, associated-file- og metadata-API'er, der er brugt her