Technical Article

E-számlák érvényesítése: veraPDF és Mustang Delphiben

A Factur-X vagy ZUGFeRD számla két dokumentum, amely egy fájlnevet visel. A külső dokumentum egy PDF/A-3 konténer, amelyet egy archiválási olvasónak (archival reader) a következő tíz évben el kell fogadnia. A belső dokumentum egy XML számla, amelyet a vevő számviteli rendszerének (accounting system) fel kell dolgoznia az EN 16931 szabvány szerint. A hiba, amely hibás számlákat juttat éles üzembe (production), az a hit, hogy az első helyes megoldásával a másodikat ingyen kapjuk. Nem így van. Egy fájl lehet hibátlan PDF/A-3, miközben olyan XML-t hordoz, amelyet egyetlen adóhatóság sem fogad el, és hordozhat tankönyvbe illő EN 16931 XML-t egy olyan konténerben, amely elbukik az archiválási validáción (archival validation). A két réteget két különböző eszköz érvényesíti (validate), amelyek semmit sem tudnak egymásról, és egy valós csővezetéknek (pipeline) mindkettőt ki kell elégítenie

Két validátor, két különböző kérdés

A veraPDF a PDF/A referencia-implementációja. Irányítsa rá egy számlára, és az egyetlen kérdésre válaszol: ez egy konform PDF/A-3 fájl-e. Ellenőrzi azokat a dolgokat, amelyekkel az ISO 19005-3 törődik. Minden betűtípus (font) be van-e ágyazva. Van-e OutputIntent. Az XMP metaadatok a megfelelő részt és megfelelőségi szintet (conformance level) deklarálják-e. Egy e-számla esetében ellenőrzi a társított fájl (associated-file) hálózatát is, amelyet a PDF/A-3 megkövetel, mivel az XML beágyazott fájlként (embedded file) utazik együtt, /AFRelationship-pel és egy bejegyzéssel a dokumentumkatalógus /AF tömbjében. A veraPDF semmit sem mond arról, hogy a számla végösszege (invoice total) stimmel-e, mert ez nem tartozik a hatáskörébe (remit)

A Mustang a Mustangproject nyílt forráskódú validátora. Az ortogonális (merőleges) kérdést teszi fel: az beágyazott XML érvényes számla-e. Lefuttatja az XML-t a deklarált profil sémájával (schema) szemben, majd alkalmazza az EN 16931 üzleti szabályokat (business rules) és a rájuk rétegzett országspecifikus szabályrendszereket, többek között az XRechnung CIUS-át. Ellenőrzi, hogy jelen van-e az eladó áfaazonosítója (VAT identifier), amikor a végösszegek azt megkövetelik, hogy az engedmény- és díjösszegek (allowance and charge amounts) egyeztethetők-e a dokumentum végösszegével, és hogy az XML-ben szereplő profil URN megegyezik-e azzal, aminek a fájl állítja magát. A Mustang nem törődik azzal, hogy az őt körülvevő PDF beágyazza-e a betűtípusait, mert ez a veraPDF dolga

Egyik eszköz sem a másiknak a bővített halmaza (superset). A veraPDF átenged egy szerkezetileg tökéletes konténert, amely értelmetlen XML-t vesz körül. A Mustang átenged tökéletes XML-t egy olyan konténerbe csomagolva, amelyből hiányzik az OutputIntent. Mindkettő pontosan azt a hibatípust kapja el, amelyre a másik vak, és ez a teljes oka annak, amiért egy komoly validációs hám (validation harness) mindkettőt lefuttatja, és csak akkor tekinti szállításra alkalmasnak (shippable) a fájlt, ha mindkettő egyetért

A validációs mátrix

Annak bizonyítására, hogy a könyvtár olyan fájlokat állít elő, amelyek mindkét kaput túlélik, a hám egy mátrixot épít fel. Hat számlaprofil lefedi azt a tartományt, amellyel egy európai csővezeték (pipeline) a gyakorlatban találkozik: Factur-X EN 16931, Factur-X BASIC, a Factur-X EXTENDED France B2B változat, XRechnung 3.0, ZUGFeRD 1.0 COMFORT és ZUGFeRD 2.0 BASIC. Minden profil két PDF/A al-megfelelőségi szinttel (sub-conformance levels), 3b-vel és 3u-val van generálva, mert a B és U szint követelményei eltérnek az Unicode-leképezésben (Unicode mapping), és egy fájl, amely az egyiken átmegy, a másikon elbukhat. Hat profil szorozva két szinttel az tizenkét fájl, mindegyikük fejléc nélkül (headless) épül fel ugyanazon a kódútvonalon (code path), amelyet a grafikus felületű (GUI) minta is szállít, így a tesztelt leletek (artifacts) nincsenek kézzel finomhangolva a teszthez

A generátor mind a tizenkettőt megírja, és egy szkript (script) betáplálja (feeds) mindegyiket mindkét validátorba. Az első teljes futtatáskor a veraPDF mind a tizenkettőn átment. A konténer csővezetéke (plumbing) minden téren helyes volt: társított fájlok (associated files) regisztrálva, XMP megfelelőség (conformance) deklarálva, kimeneti szándékok (output intents) a helyükön. A Mustang nyolcat engedett át. Négy számla szerkezetileg érvényes PDF/A-3 fájl volt, de olyan XML-t hordoztak, amelyet az üzleti szabályokat érvényesítő validátor (business-rule validator) elutasított, és pontosan ez az a megosztás, amelynek felszínre hozására a két eszközt alkalmazó megközelítés (two-tool approach) létezik. Ha a hám (harness) csak a veraPDF-ben bízott volna, az a négy is késznek tűnt volna

A két javítás, ami bezárta a rést

A négy Mustang hiba két különböző okból származott, és mindkettő javítása egy olyan részlet, amelyet érdemes tudni, mielőtt saját maga generálná ezeket a profilokat

Az első a Factur-X EXTENDED France B2B profil volt. Az eredeti generátor egy belső címkét adott át megfelelőségi szintként (conformance level) és egy belső URN-t irányelvként (guideline), a Mustang pedig elutasította a fájlt egy érvénytelen-megfelelőségi-érték (invalid-conformance-value) hibával, amelyet egy nem-támogatott-profiltípus (unsupported-profile-type) hiba követett. Ennek az az oka, hogy az XMP fx:ConformanceLevel mező nem egy szabad szöveges hely (free-text slot) a saját profil elnevezésének (profile naming). A Factur-X pontosan öt szabványos értéket határoz meg hozzá: MINIMUM, BASIC WL, BASIC, EN 16931 és EXTENDED. Egy francia specifikus B2B számla az XMP metaadatok szempontjából még mindig egy EXTENDED-profilú dokumentum. A számla francia jellege nem egy hatodik megfelelőségi érték kitalálásával fejeződik ki. Ezt az országkód (country code), FR, és az XML-en belüli irányelv-azonosító (guideline identifier) fejezi ki, amelynek hordoznia kell az urn:cen.eu:en16931:2017#conformant# előtagot (prefix), amely az EN 16931-nek megfelelő CIUS-t jelöli. A szabványos EXTENDED érték átadása az FR országkóddal és a helyes irányelv URN-nel konformmá (conformant) tette a fájlt

A könyvtári API-ban ez egy hívás az AddFacturXAssociatedFileFromString-hez, összehangolva a megfelelőséget (conformance), az országot (country) és az irányelvet (guideline). A megfelelőségi szint (conformance level) argumentum hordozza a szabványos tokent (standard token), az országkód argumentum hordozza az FR-t, az irányelv URN pedig az Ön által átadott XML bájtokban található

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;

A második ok a ZUGFeRD 1.0 COMFORT profil volt, és semmi köze nem volt a metaadatokhoz. A ZUGFeRD 1.0 az :1p0 XSD-vel szemben van érvényesítve, amely szigorúbb a kardinalitást (cardinality) illetően, mint azt a prózai (prose) összefoglalók sugallják. Az XSD megköveteli, hogy a fejléc (header) elszámolás-összegzése (settlement summation), a ram:SpecifiedTradeSettlementMonetarySummation pontosan egyszer tartalmazza a ram:ChargeTotalAmount és ram:AllowanceTotalAmount elemeket. A generált XML mindkettőt kihagyta, ezért a Mustang jelentette, hogy az elemeknek pontosan egyszer kell előfordulniuk. Ezek nem opcionálisak (optional), amikor a séma azt mondja, hogy a minOccurs (minimális előfordulás) egy. Mindkettő kibocsátása XSD szekvenciasorrendben (sequence order), közvetlenül a ram:LineTotalAmount után, 0.00 értékkel (amikor nincsenek díjak vagy engedmények), kielégítette a sémát (schema). A nulla egy jelenlévő (present) elem; egy hiányzó (absent) elem sémasértés (schema violation). Ezzel a két javítással a mátrix tizenkettőből tizenkettő lett a Mustangon, miközben tizenkettőből tizenkettő maradt a veraPDF-en

Az XRechnung mezők, amelyek érvénytelenből érvényessé váltanak

Az XRechnung külön megjegyzést érdemel, mert a német CIUS olyan üzleti szabályokat ad hozzá, amelyek hiányoznak az alap EN 16931 halmazból (set), és úgy hiúsulnak meg, hogy egy pillantásra semmi sem látszik rossznak a dokumentumon. Kettőjük az elektronikus címekre vonatkozik. A BT-34 az eladó (seller) elektronikus címe, a BT-49 pedig a vevő (buyer) elektronikus címe; a hálózati végpontok (routing endpoints), amelyeket egy német közszféra-portál használ a számla kézbesítésére (deliver) és visszaigazolására (acknowledge). Az alap EN 16931 modell ezeket opcionálisként (optional) kezeli. Az XRechnung nem. Bármelyiket kihagyja, a számla jól formázott (well-formed), séma szerint érvényes (schema-valid) lesz, és el lesz utasítva (rejected)

A harmadik a BR-DE-6 szabály, amely megköveteli, hogy jelen legyen az eladó (seller) kapcsolattartási telefonszáma (contact telephone number). Ez az a fajta mező, amelyet egy fejlesztő (developer) elhagy, mert inkább prezentációnak, mintsem adatnak érzi, és hiánya olyan validálási hibát (validation failure) eredményez, amely az eladó kapcsolattartói csoportjára (seller contact group) mutat, ahelyett, hogy bármi nyilvánvalóan hiányzóra mutatna. A BT-34, a BT-49 és az eladó telefonszámának (seller phone number) megadása az, ami egy XRechnung fájlt érvénytelenből (invalid) érvényessé (valid) mozdít a Mustang alatt, és ez egyáltalán nem változtat azon, amit a veraPDF lát, mert mindhárom az XML-ben található

Könyvtári kimenet bekötése egy validátorhoz

A hám (harness) mögötti építészeti (architectural) pont bármely üzleti rendszerre általánosítható. A PDF könyvtár ír egy konform konténert, és beágyazza az XML-t. Nem kísérli meg, és nem is szabad megkísérelnie, hogy az EN 16931 üzleti szabályok hatósága (business-rule authority) legyen. A könyvtárban található ValidateFacturXInvoice ellenőrzi a konténer következetességét (container consistency), hogy a katalógus /AF tömbje (array), a beágyazott fájlok név fája (embedded-files name tree), az XMP DocumentFileName, a profil, az irányelv (guideline) és az /AFRelationship mind megegyeznek-e, de nem érvényesíti (validate) az adókódokat (tax codes) vagy az egyeztetési összegeket (reconcile amounts). A megfelelő munkamegosztás (division of labor) az, ha az üzleti rendszer (business system) kinyeri (extract) az XML-t, és átadja azt egy dedikált számlavalidátornak (invoice validator), pontosan úgy, ahogy a hám átadja a Mustangnak

A fájl visszaolvasása elmondja Önnek, mit írtak ténylegesen. A DetectFacturXInvoice jelzi, hogy felismert-e egy számlát, a GetFacturXInvoiceInfo pedig címke (tag) szerint olvassa be a metaadat-mezőket: az 1. címke a beágyazott fájl neve, a 2. címke az XMP DocumentFileName, az 5. címke a megfelelőségi szint (conformance level), a 6. címke az irányelv azonosítója (guideline identifier), a 7. címke pedig az /AFRelationship. Annak megerősítése, hogy a visszaolvasott megfelelőségi szint a szabványos token (standard token), és nem egy belső címke (internal label), a legolcsóbb módja annak, hogy elkapja az EXTENDED hibát, mielőtt a fájl elhagyná a buildjét

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;

Az ExtractFacturXXMLToString visszaadja a nyers XML bájtokat (raw XML bytes) AnsiString-ként, készen arra, hogy fájlba írja vagy beleáramoltassa (stream) egy validátor folyamatba (validator process). A teszt hámban (test harness) a célpont a Mustang, a parancssori jar-ján keresztül meghívva, a veraPDF pedig ugyanabban a menetben fut le ugyanazon a fájlon. A bekötés (wiring) kicsi: egy konzolos generátor (console generator), az EInvoiceValidation.dpr, felírja a tizenkét fájlt a mintából (sample) származó megosztott számlamodell (shared invoice model) felhasználásával, és egy szkript (script), a run-validation.ps1 mindkét validátort meghajtja a kimeneti könyvtáron, és egy sikeres/sikertelen táblázatot (pass and fail table) nyomtat. Ugyanez a kétlépéses (two-step) forma – generálás a könyvtárral és ellenőrzés külső validátorokkal – az, amit egy folyamatos integrációs (continuous-integration, CI) munkának futtatnia kell a számlagenerálás minden változtatásánál, mert az egyetlen módja annak megismerésére, hogy egy fájl mindkét réteget kielégíti-e, az, ha megkérdezzük mindkét eszközt

Ha a csővezetékének (pipeline) aláírás (signing) előtt a konténert (container) is hitelesítenie (certify) kell, akkor ennek a munkának a preflight részét a PDF/A és a PDF/UA preflight Delphiben történő bemutatásában, míg a tágabb "hitelesítés, majd aláírás" (certify-then-sign) folyamatát a megfelelőségi és aláírási munkapadban ismertetjük. Mindkettő ugyanarra a generálási útvonalra épül, amely a Delphihez és a C++Builderhez készült Delphi PDF Library részeként érkezik, az itt használt PDF/A, társított fájl (associated-file) és metaadat API-k mellett