Teknisk artikkel

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

PDF Library for Delphi: Lagdelt diagram over en Factur-X e-fakturafil: den ytre PDF/A-3-arkiveringscontaineren med /AFRelationship- og /AF-tilknytningen inspisert av veraPDF, og den indre factur-x.xml EN 16931-faktura-XML-en inspisert av Mustang, uten at noe av verktøyene dekker det andre laget
Det ytre PDF/A-3-laget svarer til veraPDF, mens Mustang dømmer den innebygde EN 16931-XML-en, og ingen av kontrollene innebærer den andre

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;
  // ... tegn den menneskelesbare fakturasiden ...
  // ExtendedXML inneholder en retningslinje-URN for EN 16931 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 etikett
    'factur-x.xml',
    'Factur-X EXTENDED invoice',
    'Alternative',       // /AFRelationship
    '1.0',
    'FR');               // Frankrike B2B merkes med landskode, ikke med samsvarsnivå
  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

PDF Library for Delphi: Oppsummering av valideringsmatrisen: seks fakturaprofiler fra Factur-X EN 16931 til ZUGFeRD 2.0 BASIC generert på nivåene 3b og 3u gir tolv filer; veraPDF består tolv av tolv mens Mustang bare består åtte, og to fiks for konformitetstokenet og obligatoriske summeringselementer lukker gapet
Seks profiler på to samsvarsnivåer gir tolv filer, alle tolv grønne i veraPDF og bare åtte grønne i Mustang

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-retningslinje-ID
    Writeln('Profile:   ', Profile);
    Writeln('Guideline: ', Guideline);
    // Send rå-XML-en til en dedikert 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

PDF Library for Delphi-rørledningsdiagram som kobler bibliotekets utdata til eksterne validatorer: generatoren skriver invoice.pdf headless, veraPDF porter containeren, ExtractFacturXXMLToString mater Mustang med EN 16931-forretningsreglene, og shipment-porten avslutter med null bare når begge validatorer består
Pipelinen genererer headless, gir samme artefakt til begge validatorer og sender bare ut når deres samlede vurdering er grønn