Technical Article

Validering av e-fakturaer: veraPDF og Mustang i Delphi

En Factur-X- eller ZUGFeRD-faktura er to dokumenter med ett filnavn. Det ytre dokumentet er en PDF/A-3-beholder som et arkivleseprogram må godta i ti år fremover. Det indre dokumentet er en XML-faktura som kjøperens regnskapssystem må analysere mot EN 16931. Feilen som sender ødelagte fakturaer til produksjon, er å tro at det å få den første riktig gir den andre gratis. Det gjør det ikke. En fil kan være en feilfri PDF/A-3 og likevel inneholde XML som ingen skattemyndighet vil godta, og den kan inneholde lærebokriktig EN 16931 XML inni en beholder som ikke består arkiveringsvalidering. De to lagene valideres av to forskjellige verktøy som ikke vet noe om hverandre, og en reell pipeline må tilfredsstille begge

To validatorer, to forskjellige spørsmål

veraPDF er referanseimplementasjonen for PDF/A. Pek den mot en faktura og den svarer på ett spørsmål: er dette en konform PDF/A-3-fil. Den sjekker det ISO 19005-3 bryr seg om. Er hvert skriftsnitt innebygd. Er det et OutputIntent. Erklærer XMP-metadataene riktig del og konformansnivå. For en e-faktura sjekker den også tilknyttet-fil-rørleggerarbeidet som PDF/A-3 krever, fordi XML-en følger med som en innebygd fil med et /AFRelationship og en oppføring i dokumentkatalogenes /AF-matrise. veraPDF sier ingenting om fakturabeløpet er riktig, fordi det ikke er dens ansvarsområde

Mustang er den åpen kildekode-validatoren fra Mustangproject. Den stiller det ortogonale spørsmålet: er den innebygde XML-en en gyldig faktura. Den kjører XML-en mot skjemaet for den erklærte profilen og anvender deretter EN 16931-forretningsreglene og de landsspesifikke regelsetteene som er lagt oppå, XRechnung sin CIUS blant dem. Den sjekker at en selger-MVA-identifikator er tilstede når totalene krever en, at tillegg og fradragsbeløp stemmer overens med dokumenttotalen, at profil-URN-en i XML-en stemmer med det filen hevder å være. Mustang bryr seg ikke om den omgivende PDF-en har innebygde skriftsnitt, fordi det er veraPDFs jobb

Intet av verktøyene er et supersett av det andre. veraPDF godtar en strukturelt perfekt beholder rundt meningsløs XML. Mustang godtar perfekt XML pakket inn i en beholder med manglende OutputIntent. Hvert av dem fanger nøyaktig den klassen av feil det andre er blind for, og det er hele grunnen til at et seriøst valideringsoppsett kjører begge og behandler en fil som leveringsklar bare når begge er enige

Valideringsmatrisen

For å bevise at biblioteket produserer filer som overlever begge porter, bygger oppsettet en matrise. Seks fakturaprofiler dekker spekteret en europeisk pipeline møter i praksis: Factur-X EN 16931, Factur-X BASIC, Factur-X EXTENDED Frankrike B2B-variant, XRechnung 3.0, ZUGFeRD 1.0 COMFORT og ZUGFeRD 2.0 BASIC. Hver profil genereres mot to PDF/A-delkonformansnivåer, 3b og 3u, fordi krav til nivå B og nivå U divergerer på Unicode-tilordning, og en fil som består det ene kan feile det andre. Seks profiler ganger to nivåer er tolv filer, alle bygget hodeløst av den samme kodestien som GUI-eksemplet leveres med, slik at artefaktene som testes ikke er håndinnstilt for testen

Generatoren skriver alle tolv og et skript mater hver enkelt til begge validatorer. På den første fulle kjøringen besto veraPDF alle tolv. Beholderplikkeriet var korrekt over hele linjen: tilknyttede filer registrert, XMP-konformans erklært, utdataintensjoner på plass. Mustang besto åtte. Fire fakturaer var strukturelt gyldige PDF/A-3-filer som inneholdt XML som forretningsregel-validatoren avviste, noe som er nøyaktig det splittet toverktøy-tilnærmingen eksisterer for å avdekke. Hadde oppsettet stolt på veraPDF alene, ville de fire ha sett ferdige ut

De to rettelsene som lukket gapet

De fire Mustang-feilene kom fra to adskilte årsaker, og rettelsen for hver er et detalj verdt å kjenne til før du genererer disse profilene selv

Den første var Factur-X EXTENDED Frankrike B2B-profilen. Den opprinnelige generatoren sendte en intern etikett som konformansnivå og en intern URN som retningslinje, og Mustang avviste filen med en ugyldig-konformansverdi-feil etterfulgt av en ikke-støttet-profiltype-feil. Årsaken er at XMP-feltet fx:ConformanceLevel ikke er en fritekststøtte for din egen profilnavngiving. Factur-X definerer nøyaktig fem standardverdier for det: MINIMUM, BASIC WL, BASIC, EN 16931 og EXTENDED. En Frankrike-spesifikk B2B-faktura er likevel et EXTENDED-profildokument når det gjelder XMP-metadataene. Det franske preget ved fakturaen uttrykkes ikke ved å finne opp en sjette konformansverdi. Det uttrykkes ved landskoden, FR, og av retningslinje-identifikatoren inne i XML-en, som må bære prefikset urn:cen.eu:en16931:2017#conformant# som markerer en CIUS-konform med EN 16931. Å sende standard EXTENDED-verdi med FR som landskode og riktig retningslinje-URN gjorde filen konform

I biblioteks-API-et er det et kall til AddFacturXAssociatedFileFromString med konformansen, landet og retningslinjen justert. Konformansnivå-argumentet bærer standardtokenet, landskode-argumentet bærer FR, og retningslinje-URN-en bor i XML-bytene du sender inn

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;

Den andre årsaken var ZUGFeRD 1.0 COMFORT-profilen, og den hadde ingenting med metadata å gjøre. ZUGFeRD 1.0 valideres mot :1p0 XSD-en, som er strengere på kardinalitet enn prosa-sammendragene antyder. XSD-en krever at hodesettlingsummering, ram:SpecifiedTradeSettlementMonetarySummation, inneholder ram:ChargeTotalAmount og ram:AllowanceTotalAmount hver nøyaktig én gang. Den genererte XML-en utelot begge, slik at Mustang rapporterte at elementene må forekomme nøyaktig én gang. Disse er ikke valgfrie når skjemaet sier at minOccurs er én. Å sende begge i XSD-sekvensrekkefølge, umiddelbart etter ram:LineTotalAmount, med en verdi på 0.00 når det ikke er tillegg eller fradrag, tilfredsstilte skjemaet. En null er et tilstedeværende element; et fraværende element er et skjemabrudd. Med disse to rettelsene gikk matrisen til tolv av tolv på Mustang mens den forble tolv av tolv på veraPDF

XRechnung-feltene som snur ugyldig til gyldig

XRechnung fortjener sin egen merknad fordi den tyske CIUS-en legger til forretningsregler som er fraværende fra basis-EN 16931-settet, og de feiler på måter som ser ut som ingenting er galt med dokumentet ved første øyekast. To av dem gjelder elektroniske adresser. BT-34 er selgerens elektroniske adresse og BT-49 er kjøperens elektroniske adresse, rutingendepunktene en tysk offentlig sektor-portal bruker til å levere og bekrefte fakturaen. Basis-EN 16931-modellen behandler dem som valgfrie. XRechnung gjør ikke det. Utelat en av dem og fakturaen er velformet, skjema-gyldig og avvist

Den tredje er regel BR-DE-6, som krever at selgerens kontakttelefonnummer er tilstede. Det er den typen felt en utvikler dropper fordi det føles som presentasjon fremfor data, og fraværet produserer et valideringsavvik som peker på selgerkontaktgruppen fremfor noe åpenbart manglende. Å oppgi BT-34, BT-49 og selgerens telefonnummer er det som flytter en XRechnung-fil fra ugyldig til gyldig under Mustang, og ingen av det endrer noe veraPDF ser, fordi alle tre bor i XML-en

Koble bibliotekutdata til en validator

Det arkitektoniske poenget bak oppsettet generaliserer til ethvert forretningssystem. PDF-biblioteket skriver en konform beholder og bygger inn XML-en. Det gjør ikke, og bør ikke, forsøke å være EN 16931-forretningsregel-autoriteten. ValidateFacturXInvoice i biblioteket sjekker beholderkonsistens - at katalogen /AF-matrisen, navnetreet for innebygde filer, XMP DocumentFileName, profilen, retningslinjen og /AFRelationship alle stemmer overens - men det validerer ikke skattekoder eller avstemmer beløp. Den rette arbeidsdelingen er at forretningssystemet trekker ut XML-en og gir den til en dedikert fakturavalidator, nøyaktig slik oppsettet gir den til Mustang

Å lese filen tilbake forteller deg hva som faktisk ble skrevet. DetectFacturXInvoice rapporterer om en faktura ble gjenkjent, og GetFacturXInvoiceInfo leser metadatafeltene etter tag: tag 1 er det innebygde filnavnet, tag 2 er XMP DocumentFileName, tag 5 er konformansnivået, tag 6 er retningslinje-identifikatoren og tag 7 er /AFRelationship. Å bekrefte at konformansnivået du leser tilbake er standardtokenet og ikke en intern etikett er den billigste måten å fange EXTENDED-feilen på før en fil forlater bygget ditt

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 returnerer de rå XML-bytene som en AnsiString, klar til å skrive til en fil eller strømme inn i en validatorprosess. I testoppsettet er det målet Mustang, påkalt gjennom sin kommandolinje-jar, med veraPDF kjørt i samme pasning over den samme filen. Kableoppsettet er lite: en konsollgenerator, EInvoiceValidation.dpr, skriver de tolv filene ved hjelp av den delte fakturamodellen fra eksemplet, og et skript, run-validation.ps1, kjører begge validatorer over utdatakatalogen og skriver ut en bestå og feil-tabell. Den samme tostegsformen - generer med biblioteket og verifiser med eksterne validatorer - er det en kontinuerlig integrasjonsjobb bør kjøre ved hver endring av fakturagenerering, fordi den eneste måten å vite at en fil tilfredsstiller begge lagene på er å spørre begge verktøy

Hvis pipeline-en din også må sertifisere beholderen før signering, dekkes preflight-siden av dette arbeidet i vår gjennomgang av PDF/A og PDF/UA preflight i Delphi, og den bredere sertifiser-deretter-signer-flyten er beskrevet i samsvars- og signeringssarbeidsbenken. Begge bygger på den samme genereringsstien som leveres som en del av Delphi PDF Library for Delphi og C++Builder, ved siden av PDF/A-, tilknyttet-fil- og metadata-API-ene som brukes her