Technikai cikk

ZUGFeRD és Factur-X e-számlák Delphi-ben HotPDF segítségével

A HotPDF generálja, észleli és ellenőrzi a ZUGFeRD és Factur-X elektronikus számlákat Delphi és C++Builder környezetben. Az AddFacturXAssociatedFile beágyazza az EN 16931 számla XML-t egy PDF/A-3 konténerbe, a DetectFacturXInvoice felismeri a bejövő hibrid számlát, a HPDFValidateEInvoice pedig futtatja a hivatalos EN 16931 Schematron üzleti szabályokat a beágyazott XML-en. Ez a cikk végigvezet mindhárom lépésen, és őszintén bemutatja, hol áll meg az ellenőrző motor.

A funkciók mögött meghúzódó nyomás szabályozási, nem pedig technikai jellegű. Németország és Franciaország fokozatosan bevezeti a kötelező strukturált e-számlázást a belföldi B2B tranzakciókhoz, és mindkét előírás ugyanarra a hibrid modellre épül: az ERP által előállított PDF számlának tartalmaznia kell egy gépileg olvasható XML ikerpárt. Egy olyan Delphi számviteli vagy ERP alkalmazásnak, amely ma egyszerű PDF számlákat bocsát ki, szigorú határidővel kell elkezdenie Factur-X vagy ZUGFeRD számlák kibocsátását, és a két név közötti különbség kisebb, mint amit a marketing sugall – a Factur-X és a ZUGFeRD ugyanaz a francia-német szabvány, két különböző címke alatt közzétéve.

Mitől lesz egy PDF érvényes Factur-X számla?

Az érvényes Factur-X számla egy olyan PDF/A-3 fájl (ISO 19005-3), amely pontosan egy strukturált számla XML-t ágyaz be társított fájlként (associated file), hivatkozik rá a dokumentumkatalógusból, és deklarálja azt az XMP kiterjesztési metaadatokban. Három rétegnek kell egyetértenie. Elsőként a konténernek meg kell felelnie a PDF/A-3 szabványnak, annak az archiválási profilnak, amely – a PDF/A-1-től és a PDF/A-2-től eltérően – bármilyen típusú beágyazott fájlt engedélyez. Másodszor, a számla XML-t (amely a szabványos profilok esetében factur-x.xml, a német XRechnung változatnál pedig xrechnung.xml nevű) regisztrálni kell a katalógus /AF tömbjében és a /Names /EmbeddedFiles fájljában, egy AFRelationship bejegyzéssel, amely megadja a melléklet jellegét: az Alternative azt jelenti, hogy az XML a látható számla egyenértékű ábrázolása, ezt a szemantikát a legtöbb profil megköveteli. Harmadszor, a Factur-X XMP kiterjesztési sémának meg kell neveznie a beágyazott fájlt, annak megfelelőségi szintjét és a dokumentum típusát, hogy a fogadó rendszer az XML elemzése nélkül is osztályozhassa a számlát.

Maga az XML tartalom az EN 16931 szemantikai modellt követi, azt az európai szabványt, amely meghatározza a törzsszámlát: vevő, eladó, tételsorok, adórészletezés, fizetendő összeg, mindegyik egy-egy üzleti azonosítóval (BT-1 a számlaszámhoz, BT-5 a pénznemhez stb.). Az EN 16931 ellenőrzés ezért két különböző rétegben történik. Az XML séma-ellenőrzés azt vizsgálja, hogy a dokumentum megfelelően formázott CII-e a helyes elemnevekkel. A Schematron üzleti szabályok pedig azt ellenőrzik, hogy a tartalom koherens-e – például hogy a BR-CO-15 teljesül-e, és a számla végösszege valóban megegyezik-e a nettó összegek és az áfa összegével, valamint hogy a kötelező adatok jelen vannak-e a deklarált profilhoz. Egy fájl átmehet a séma-ellenőrzésen úgy is, hogy közben érvénytelen számla marad, ezért létezik az üzleti szabályok rétege.

Miért nincs szüksége a ZUGFeRD 2.5-nek új konténerre?

A ZUGFeRD 2.5 nem változtat semmit a PDF konténer szintjén: az irányadó URN-ek, az XMP séma és a verziótoken megegyeznek a ZUGFeRD 2.3 / Factur-X 1.0 szabvánnyal. Ez a legtöbb megvalósítót meglepi, ezért érdemes idézni a forrást. A Factur-X 1.09 specifikáció kimondja, hogy a PDF/A kiterjesztési séma URI-jában szereplő verziószám nincs kapcsolatban az XML adatspecifikáció verziószámával, és a Factur-X számlapéldányok verziószáma 1.0 marad – az XMP-ben lévő fx:Version-ben és a factur-x.xml BT-24-es mezőjében egyaránt. A hivatalos ZUGFeRD 2.5 mintagyűjtemény is ezt igazolja: minden minta a factur-x.xml-t ágyazza be egy urn:factur-x.eu:1p0 irányadó URN alatt, 1.0 értékű fx:Version-nel. Amit a 2.5 valójában hozzáad, az az XML adatmodellen belül él – új elemek, mint például a részszámla-sorok és a kötegazonosítók –, ami az Ön alkalmazása által előállított üzleti réteg, nem pedig a PDF könyvtár által kezelt konténerréteg.

A HotPDF közvetlenül kódolja ezt a megállapítást. Az AddFacturXAssociatedFile elfogad egy Version argumentumot, és normalizálja azt a HPDFFacturXNormalizeVersion segítségével az XMP írása előtt, így a 2p5 aliast átadó hívó továbbra is a specifikációnak megfelelő tokent bocsátja ki, ahelyett, hogy egy olyan új 2p5 XMP-verziót találna ki, amelyet egyetlen ellenőrző sem fogadna el. Az észlelő segédfüggvények olvasáskor egyenértékűként kezelik a 2p5-öt, így a folyamatok szimmetrikusak maradnak. Ha egy könyvtár vagy eszköz azt állítja, hogy speciális ZUGFeRD 2.5 konténert ír, maga a specifikáció mondja ki, hogy ilyen nem létezik.

A számla XML beágyazása Delphi-ből

A THotPDF.AddFacturXAssociatedFile egyetlen hívással Factur-X számlává alakítja át a szokásos PDF/A-3 generálást: beágyazza az XML bájtokat társított fájlként, regisztrálja a factur-x.xml-t a katalógus /AF tömbjében és a /Names /EmbeddedFiles fájljában, valamint kibocsátja a megfelelő Factur-X XMP kiterjesztési metaadatokat. A szemmel olvasható számlalapot a szokásos HotPDF canvas API segítségével rajzolhatja meg; a konténerrel kapcsolatos feladat mindössze egyetlen sor a BeginDoc és EndDoc között.

var
  Pdf: THotPDF;
  InvoiceXML, UBLXML: TBytes;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'invoice-2026-0042.pdf';
    Pdf.PDFACompliance := '3B';            // PDF/A-3b konténer
    Pdf.BeginDoc;
    // embed the EN 16931 CII invoice XML as factur-x.xml
    InvoiceXML := BuildInvoiceXML;         // produced by your business layer
    Pdf.AddFacturXAssociatedFile(InvoiceXML, 'EN 16931');
    // optional: attach a UBL view as a supplementary representation
    UBLXML := BuildUBLXML;
    Pdf.AddUBLSupplementaryFile(UBLXML);   // factur-xubl.xml, Alternative
    // ... draw the visible invoice page here ...
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Az alapértelmezett értékek követik a specifikációt: fájlnév factur-x.xml, kapcsolat Alternative, verzió 1.0. Az AddUBLSupplementaryFile megvalósítja a Factur-X 1.09 specifikáció 6.4-es szakaszát, amely lehetővé teszi, hogy ugyanazon számla UBL-ábrázolása második mellékletként fusson factur-xubl.xml néven, application/xml MIME típussal és Alternative kapcsolattal. A sorrend itt számít, és a HotPDF kikényszeríti a specifikáció által megkövetelt korlátozást: az UBL fájl egy kiegészítő nézet, soha nem az elsődleges számla, így azt azután kell hozzáadni, miután az AddFacturXAssociatedFile regisztrálta a CII mesterfájlt. Vegye figyelembe, hogy a megrajzolt oldalnek és a beágyazott XML-nek ugyanazt a számlát kell leírnia – a hibrid számlázás éppen azt a hibaállapotot hivatott megakadályozni, amikor a PDF egy bizonyos összeget mutat, miközben a kódolt XML egy másikat tartalmaz. Ezen munkafolyamat PDF/A-3-as oldala – beleértve az OutputIntent-et és a betűtípus-beágyazást is – részletesebben a PDF/A, PDF/X és PDF/UA ellenőrzésről szóló társcikkben Delphi-ben olvasható.

Hogyan észlelhető a Factur-X számla a bejövő PDF-ben?

A THotPDF.DetectFacturXInvoice a jogszabályi kötelezettség fogadó oldalára ad választ: töltsön be bármilyen PDF-et, és egyetlen hívással megtudhatja, hogy hibrid számláról van-e szó, milyen profilt deklarál, és milyen mellékleteket hordoz. A függvény egy THPDFFacturXInvoiceInfo rekorddal tér vissza, amely tartalmazza a beágyazott XML fájlnevet, az XMP-ben deklarált dokumentum-fájlnevet és -típust, a verziótokent, a megfelelőségi szintet, az irányadó URN-t és az AFRelationship értéket. Két további mező jelzi a kiegészítő nézeteket: a UBLFileName akkor van kitöltve, ha a factur-xubl.xml melléklet jelen van, az EDIFACTFileName pedig akkor, ha EDIFACT nézet is társul hozzá – egyik sem befolyásolja az észlelési eredményt, mivel kiegészítő ábrázolás nem létezhet az elsődleges CII számla nélkül.

var
  Reader: THotPDF;
  Info: THPDFFacturXInvoiceInfo;
begin
  Reader := THotPDF.Create(nil);
  try
    if Reader.LoadFromFile('incoming-invoice.pdf') <= 0 then
      raise Exception.Create('Nincsenek betöltött oldalak.');
    if Reader.DetectFacturXInvoice(Info) then
    begin
      Writeln('Invoice XML:    ', string(Info.FileName));
      Writeln('Conformance:    ', string(Info.ConformanceLevel));
      Writeln('Guideline URN:  ', string(Info.GuidelineID));
      Writeln('Relationship:   ', string(Info.AFRelationship));
      if Info.UBLFileName <> '' then
        Writeln('UBL view:       ', string(Info.UBLFileName));
    end
    else
      Writeln('Nem Factur-X / ZUGFeRD hibrid számla.');
  finally
    Reader.Free;
  end;
end;

Az észlelés ugyanazokat a jeleket olvassa be, amelyeket a szabványnak megfelelő ellenőrző is – a XMP kiterjesztési sémát és a társított fájlok regiszterét –, ahelyett, hogy pusztán a fájlnevekből következtetne. Ugyanez a betöltött dokumentumobjektum az XMP és az Info-dictionary metaadatokat is elérhetővé teszi vizsgálatra vagy módosításra, ezt a munkafolyamatot a betöltött PDF-dokumentumok metaadatainak szerkesztéséről szóló cikk írja le.

Üzleti szabályok vagy séma: mit jelent az EN 16931 ellenőrzés?

A HPDFValidateEInvoice, a HPDFEInvoiceValidator egységben található önálló függvény egymás után futtatja mindkét ellenőrzési réteget: először a konténer-szerkezet ellenőrzését a ValidateFacturXInvoice segítségével, majd a beágyazott XML-en az EN 16931 Schematron üzleti szabályokat. A HotPDF a Factur-X 1.09 öt hivatalos szabályfájlját szállítja – MINIMUM, BASIC WL, BASIC, EN 16931 és EXTENDED – a Lib/resources/Schematron/ mappában, és a HPDFSchematronFileForProfile kiválasztja a detektált megfelelőségi szinthez tartozó fájlt, így az EN 16931 számla automatikusan a Factur-X_1.09_EN16931.sch alapján lesz ellenőrizve. A függvény csak akkor tér vissza True értékkel, ha a konténer érvényes és egyetlen üzleti szabály sem sérült meg.

uses HPDFEInvoiceValidator;

var
  Reader: THotPDF;
  Report: THPDFEInvoiceValidationReport;
  I: Integer;
begin
  Reader := THotPDF.Create(nil);
  try
    Reader.LoadFromFile('incoming-invoice.pdf');
    if HPDFValidateEInvoice(Reader, SchematronDir, Report) then
      Writeln('Container and business rules: OK')
    else
      Writeln('Validation found issues.');
    if Report.BusinessRulesEvaluated then
    begin
      Writeln(Report.BusinessSummary.Evaluated, ' rules evaluated, ',
        Report.BusinessSummary.Skipped, ' skipped (XPath 2.0)');
      for I := 0 to High(Report.BusinessRules) do
        if (not Report.BusinessRules[I].Skipped) and
           (Report.BusinessRules[I].Severity = stsError) then
          Writeln(string(Report.BusinessRules[I].RuleID), ': ',
            string(Report.BusinessRules[I].Message));
    end;
  finally
    Reader.Free;
  end;
end;

A BusinessRules minden egyes bejegyzése tartalmazza az állítás szövegéből kinyert szabályazonosítót – BR-45, BR-CO-17 és az EN 16931 elnevezési séma többi elemét –, valamint a szabály kontextus XPath-át, a tesztkifejezést és a Schematron flag attribútumát tükröző súlyosságot. Ezáltal a jelentés gyakorlatiassá válik: a puszta siker/sikertelenség helyett megkapja a pontosan inkonzisztens üzleti kifejezést, amire a terméktámogatási csapatnak szüksége van, ha az ügyfél megkérdezi, miért utasították el a számlát. Azok a csapatok, amelyek már megfelelőségi jelentéseket készítenek az archivált PDF-ekhez, beépíthetik ezeket az eredményeket ugyanabba a folyamatba, amelyet a PDF preflight jelentések automatizálásáról szóló cikkben tárgyalunk.

Ahol a hitelesítő leáll, őszintén

A Schematron motor az MSXML-re épül, amely az XPath 1.0-t értékeli ki. Az EN 16931 szabályfájl deklarálja a queryBinding="xslt2"-t, és 424 állítást tartalmaz, amelyek többsége csak az XPath 1.0 függvényeit használja (mint a string-length és a substring-after) – ezek natívan futnak. Egy kisebbség az XPath 2.0 konstrukcióira támaszkodik, mint például a xs:decimal típuskonverzió vagy a exists(); a HotPDF előzetesen átvizsgál minden tesztkifejezést az ismert XPath 2.0 tokenek után, és ezeket a szabályokat Skipped (kihagyott) jelzéssel látja el info súlyossággal, ahelyett, hogy olyan kiértékelést kísérelne meg, amely csendben hibázna. Az összegző rekord külön jelenti a kiértékelt és a kihagyott szabályok számát, így a napló mindig pontosan rögzíti, hogy a szabályrendszer mekkora része lett végrehajtva. Delphi 7 alatt, ahol az MSXML kötések nem érhetők el, az egység egy csonk (stub) segítségével fordul le, amely a motort nem elérhetőként jelzi anélkül, hogy megzavarná a konténerre vonatkozó döntést; a teljes motor használatához Delphi XE2 vagy újabb szükséges.

A második határ még fontosabb. A HPDFValidateEInvoice zöld eredménye azt jelenti, hogy a konténer szerkezete megfelel és az értékelt üzleti szabályok egyike sem sérült – ez nem adóügyi megfelelőségi jóváhagyás. Nemzeti kiterjesztési szabályok, a fogadó-specifikus követelmények és a számlatartalommal kapcsolatos jogi kötelezettségek az EN 16931 mag felett helyezkednek el, és kívül esnek bármely könyvtáron. Tekintse a hitelesítőt olyan kapunak, amely távol tartja a szerkezetileg sérült számlákat a kimenő mappájától, és megjelöli az inkonzisztens számlákat a bejövő mappájában; futtasson teljes XPath 2.0-s hitelesítőt a kiadási folyamatában, ha a minősítés megköveteli, és hagyja az adóügyi tanácsadóira a jogi megfelelés kérdését. Az itt bemutatott beágyazási, észlelési és ellenőrzési API-k a Delphi és C++Builder rendszerekhez készült standard HotPDF Component részét képezik, egy teljes E-Invoice mintával, amely lefedi mind a hét ZUGFeRD és Factur-X profilt.