Artykuł techniczny

E-faktury ZUGFeRD i Factur-X w Delphi za pomocą HotPDF

Komponent HotPDF generuje, wykrywa oraz waliduje e-faktury ZUGFeRD i Factur-X z poziomu Delphi i C++Builder. Metoda AddFacturXAssociatedFile osadza plik XML faktury zgodny z EN 16931 w kontenerze PDF/A-3, funkcja DetectFacturXInvoice rozpoznaje przychodzącą fakturę hybrydową, a procedura HPDFValidateEInvoice wykonuje oficjalne reguły biznesowe Schematron EN 16931 na osadzonym pliku XML. Ten artykuł omawia wszystkie trzy etapy i szczerze opisuje ograniczenia silnika walidacji

Presja stojąca za tym zestawem funkcji ma charakter regulacyjny, a nie techniczny. Niemcy i Francja stopniowo wprowadzają obowiązkowe strukturyzowane e-fakturowanie dla krajowych transakcji B2B, a oba te przepisy zbiegają się w tym samym modelu hybrydowym: faktura PDF, którą generuje już Twój system ERP, musi zawierać w sobie odczytywalny maszynowo plik bliźniaczy XML. Aplikacja księgowa lub ERP w Delphi generująca dziś zwykłe faktury PDF ma wyznaczony nieprzekraczalny termin na rozpoczęcie generowania formatu Factur-X lub ZUGFeRD. Różnica między tymi dwoma nazwami jest mniejsza niż sugeruje marketing — Factur-X i ZUGFeRD to ten sam francusko-niemiecki standard opublikowany pod dwoma nazwami

Co sprawia, że plik PDF staje się poprawną fakturą Factur-X?

Poprawna faktura Factur-X to plik PDF/A-3 (ISO 19005-3), który osadza dokładnie jeden strukturyzowany plik XML faktury jako plik powiązany, odsyła do niego z katalogu głównego (catalog) dokumentu i deklaruje go w metadanych rozszerzeń XMP. Trzy warstwy muszą być zgodne. Po pierwsze, kontener musi spełniać wymogi standardu PDF/A-3, czyli profilu archiwalnego, który — w przeciwieństwie do PDF/A-1 i PDF/A-2 — zezwala na osadzanie plików dowolnego typu. Po do drugie, plik XML faktury, o nazwie factur-x.xml dla profilu standardowego lub xrechnung.xml dla niemieckiego wariantu XRechnung, musi zostać zarejestrowany w tablicy katalogu /AF oraz w drzewie /Names /EmbeddedFiles, wraz z wpisem AFRelationship określającym charakter załącznika: wartość Alternative oznacza, że plik XML jest równoważną reprezentacją widocznej faktury, czego wymaga większość profili. Po trzecie, schemat rozszerzenia XMP Factur-X musi wskazywać nazwę osadzonego pliku, jego poziom zgodności oraz typ dokumentu, co pozwala systemowi odbiorczemu na sklasyfikowanie faktury bez konieczności parsowania samego pliku XML

Dlaczego standard ZUGFeRD 2.5 nie wymaga nowego kontenera?

ZUGFeRD 2.5 nie wprowadza żadnych zmian na poziomie kontenera PDF: identyfikatory URN, schemat XMP oraz znacznik wersji są identyczne jak w ZUGFeRD 2.3 / Factur-X 1.0. Zaskakuje to większość wdrożeniowców, dlatego warto przytoczyć źródło. Specyfikacja Factur-X 1.09 stwierdza, że numer wersji w URI schematu rozszerzenia PDF/A nie jest powiązany z numerem wersji specyfikacji danych XML i że wersjonowanie instancji faktur Factur-X pozostaje na poziomie 1.0 — zarówno w metadanych XMP fx:Version, jak i w polu BT-24 pliku factur-x.xml. Oficjalne próbki ZUGFeRD 2.5 to potwierdzają: każda z nich zawiera wbudowany plik factur-x.xml w identyfikatorze URN urn:factur-x.eu:1p0 z wartością fx:Version równą 1.0. To, co wersja 2.5 w rzeczywistości wprowadza, znajduje się wewnątrz modelu danych XML — nowe elementy, takie jak podpozycje faktury czy identyfikatory partii — co leży po stronie warstwy biznesowej Twojej aplikacji, a nie na poziomie kontenera zarządzanego przez bibliotekę PDF

Osadzanie pliku XML faktury z poziomu Delphi

Metoda THotPDF.AddFacturXAssociatedFile zamienia zwykłe generowanie pliku PDF/A-3 w fakturę Factur-X za pomocą jednego wywołania: osadza bajty XML jako plik powiązany, rejestruje factur-x.xml w tablicy katalogu /AF oraz w drzewie /Names /EmbeddedFiles i generuje odpowiednie metadane rozszerzenia Factur-X XMP. Rysujesz czytelną dla człowieka stronę faktury za pomocą standardowego API płótna (canvas) HotPDF; obsługa kontenera to pojedyncza linia kodu między BeginDoc a EndDoc

var
  Pdf: THotPDF;
  InvoiceXML, UBLXML: TBytes;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'invoice-2026-0042.pdf';
    Pdf.PDFACompliance := '3B';            // PDF/A-3b container
    Pdf.BeginDoc;
    // osadź plik XML faktury CII zgodny z EN 16931 jako factur-x.xml
    InvoiceXML := BuildInvoiceXML;         // wygenerowane przez Twoją warstwę biznesową
    Pdf.AddFacturXAssociatedFile(InvoiceXML, 'EN 16931');
    // opcjonalnie: dołącz widok UBL jako reprezentację pomocniczą
    UBLXML := BuildUBLXML;
    Pdf.AddUBLSupplementaryFile(UBLXML);   // factur-xubl.xml, Alternative
    // ... narysuj tutaj widoczną stronę faktury ...
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

Wartości domyślne są zgodne ze specyfikacją: nazwa pliku factur-x.xml, relacja Alternative, wersja 1.0. Metoda AddUBLSupplementaryFile implementuje sekcję 6.4 specyfikacji Factur-X 1.09, która pozwala na dołączenie reprezentacji UBL tej samej faktury jako drugiego załącznika o nazwie factur-xubl.xml z typem MIME application/xml i relacją Alternative. Kolejność ma tu znaczenie, a HotPDF wymusza ograniczenie wynikające ze specyfikacji: plik UBL stanowi reprezentację pomocniczą, a nie fakturę główną, stąd musi zostać dodany po tym, jak AddFacturXAssociatedFile zarejestruje plik główny CII. Pamiętaj, że wyrenderowana strona oraz osadzony kod XML muszą opisywać tę samą fakturę — plik PDF wyświetlający jedną kwotę i kodujący inną to dokładnie ten scenariusz awarii, przed którym chronić ma fakturowanie hybrydowe. Kwestie PDF/A-3 w tym potoku pracy, w tym profile OutputIntent oraz osadzanie czcionek, opisano szczegółowo w towarzyszącym artykule o walidacji PDF/A, PDF/X oraz PDF/UA w Delphi

Jak wykryć fakturę Factur-X w przychodzącym pliku PDF?

Metoda THotPDF.DetectFacturXInvoice obsługuje stronę odbiorczą: załaduj dowolny plik PDF i sprawdź za pomocą jednego wywołania, czy jest to faktura hybrydowa, jaki profil deklaruje oraz jakie załączniki zawiera. Funkcja zwraca rekord THPDFFacturXInvoiceInfo z nazwą osadzonego pliku XML, nazwą i typem dokumentu zadeklarowanym w XMP, znacznikiem wersji, poziomem zgodności, identyfikatorem URN oraz wartością AFRelationship. Dwa dodatkowe pola informują o reprezentacjach pomocniczych: UBLFileName jest uzupełniane w przypadku obecności załącznika factur-xubl.xml, a EDIFACTFileName w przypadku obecności widoku EDIFACT — żaden z nich nie wpływa na sam wynik wykrywania, jako że reprezentacja pomocnicza nie może istnieć bez głównej faktury CII

var
  Reader: THotPDF;
  Info: THPDFFacturXInvoiceInfo;
begin
  Reader := THotPDF.Create(nil);
  try
    if Reader.LoadFromFile('incoming-invoice.pdf') <= 0 then
      raise Exception.Create('No pages loaded.');
    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('Not a Factur-X / ZUGFeRD hybrid invoice.');
  finally
    Reader.Free;
  end;
end;

Wykrywanie odczytuje te same sygnały, które bada zgodny z normą walidator — schemat rozszerzeń XMP oraz rejestr plików powiązanych — zamiast zgadywać wyłącznie po nazwach plików. Ten sam załadowany obiekt dokumentu udostępnia również metadane słownika XMP i Info do wglądu lub korekty, co opisano w artykule o edycji metadanych w załadowanych dokumentach PDF

Reguły biznesowe czy schemat: co oznacza walidacja EN 16931?

Metoda HPDFValidateEInvoice, samodzielna funkcja w module HPDFEInvoiceValidator, uruchamia po kolei obie warstwy walidacji: najpierw weryfikację struktury kontenera za pomocą ValidateFacturXInvoice, a następnie reguły biznesowe Schematron EN 16931 na wyodrębnionym kodzie XML faktury. HotPDF dostarcza pięć oficjalnych plików reguł Factur-X 1.09 — MINIMUM, BASIC WL, BASIC, EN 16931 oraz EXTENDED — w katalogu Lib/resources/Schematron/, a funkcja HPDFSchematronFileForProfile dobiera plik odpowiadający wykrytemu poziomowi zgodności, stąd faktura EN 16931 jest automatycznie weryfikowana pod kątem Factur-X_1.09_EN16931.sch. Funkcja ta zwraca wartość True tylko wtedy, gdy kontener jest poprawny i żadna reguła biznesowa nie zgłosiła błędu

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('Kontener i reguły biznesowe: OK')
    else
      Writeln('Walidacja wykryła problemy.');
    if Report.BusinessRulesEvaluated then
    begin
      Writeln(Report.BusinessSummary.Evaluated, ' oceniono reguły, ',
        Report.BusinessSummary.Skipped, ' pominięto (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;

Każdy wpis w BusinessRules zawiera identyfikator reguły odczytany z tekstu asercji — BR-45, BR-CO-17 i pozostałe z nazewnictwa EN 16931 — a także kontekst XPath reguły, wyrażenie testowe oraz poziom ważności (severity) odzwierciedlający atrybut flagi Schematron. Sprawia to, że raport jest przydatny: zamiast suchej oceny zaliczony/niezaliczony otrzymujesz dokładne określenie niespójnego warunku biznesowego, co ułatwi zespołowi wsparcia technicznemu odpowiedź na pytanie klienta, dlaczego faktura została odrzucona. Zespoły generujące już strukturyzowane raporty zgodności dla plików archiwalnych PDF mogą wdrożyć te wyniki do tego samego potoku opisanego w artykule o automatyzacji raportów preflight PDF

Szczere podsumowanie ograniczeń walidatora

Silnik Schematron bazuje na bibliotece MSXML, która obsługuje standard XPath 1.0. Plik reguł EN 16931 deklaruje queryBinding="xslt2" i zawiera 424 asercje, z których większość korzysta wyłącznie z funkcji XPath 1.0, takich jak string-length oraz substring-after — te wykonują się natywnie. Nieliczne reguły zależą od konstrukcji XPath 2.0, np. rzutowania xs:decimal czy funkcji exists(); HotPDF wstępnie analizuje każde wyrażenie testowe pod kątem znanych znaczników XPath 2.0 i oznacza te reguły jako Skipped (pominięte) z poziomem ważności info, zamiast podejmować próbę ich obliczenia, która mogłaby się nie powieść. Rekord podsumowania raportuje oddzielnie liczby reguł obliczonych i pominiętych, więc dziennik zdarzeń zawsze podaje stopień sprawdzenia reguł. W środowisku Delphi 7, gdzie powiązania MSXML są niedostępne, moduł kompiluje stub informujący o braku dostępności silnika bez wpływu na ocenę kontenera; pełna wersja silnika wymaga Delphi XE2 lub nowszego

Drugie ograniczenie jest jeszcze ważniejsze. Pomyślny wynik z HPDFValidateEInvoice oznacza, że struktura kontenera jest poprawna i żadna ze sprawdzonych reguł biznesowych nie zgłosiła błędu — nie stanowi to jednak poświadczenia zgodności podatkowej. Krajowe przepisy szczegółowe, specyficzne wymagania odbiorców oraz prawne obowiązki dotyczące zawartości faktur wykraczają poza jądro EN 16931 i leżą poza zakresem jakiejkolwiek biblioteki. Traktuj walidator jako filtr zatrzymujący wadliwe strukturalnie faktury przed ich wysłaniem i oznaczający niespójne faktury w skrzynce odbiorczej, uruchamiaj pełny walidator XPath 2.0 w potoku wydań, jeśli wymaga tego certyfikacja, a kwestię zgodności podatkowej pozostaw doradcom podatkowym. Interfejsy API do osadzania, wykrywania oraz walidacji przedstawione w tym miejscu wchodzą w skład standardowej wersji HotPDF Component dla Delphi i C++Builder, wraz z pełną próbką e-faktury obejmującą wszystkie siedem profilów ZUGFeRD oraz Factur-X