Teknisk artikel

ZUGFeRD- och Factur-X-e-fakturor i Delphi med HotPDF

HotPDF skapar, detekterar och validerar elektroniska ZUGFeRD- och Factur-X-fakturor från Delphi och C++Builder. AddFacturXAssociatedFile bäddar in faktura-XML enligt EN 16931 i en PDF/A-3-behållare, DetectFacturXInvoice känner igen en inkommande hybridfaktura och HPDFValidateEInvoice kör de officiella Schematron-affärsreglerna för EN 16931 mot den inbäddade XML-koden. Den här artikeln går igenom alla tre stegen och redogör ärligt för var valideringsmotorn sätter stopp

Drivkraften bakom dessa funktioner är lagstiftning, inte teknik. Tyskland och Frankrike fasar in obligatorisk strukturerad e-fakturering för inhemska B2B-transaktioner, och båda länders regelverk sammanstrålar i samma hybridmodell: den PDF-faktura som ditt affärssystem (ERP) redan skapar måste innehålla en maskinläsbar XML-tvilling. En Delphi-baserad redovisnings- eller ERP-applikation som idag skickar vanliga PDF-fakturor har en skarp deadline för att börja skicka Factur-X eller ZUGFeRD, och skillnaden mellan de två namnen är mindre än vad marknadsföringen antyder — Factur-X och ZUGFeRD är samma fransk-tyska standard under två olika etiketter

Vad gör en PDF till en giltig Factur-X-faktura?

En giltig Factur-X-faktura är en PDF/A-3-fil (ISO 19005-3) som bäddar in exakt en strukturerad faktura-XML som en associerad fil, länkar den från dokumentkatalogen och deklarerar den i XMP-tilläggsmetadata. Tre skikt måste stämma överens. För det första algebra-behållaren uppfylla kraven för PDF/A-3, det arkiveringsformat som — till skillnad från PDF/A-1 och PDF/A-2 — tillåter inbäddade filer av vilken typ som helst. För det andra måste faktura-XML-koden, namngiven factur-x.xml för standardprofiler eller xrechnung.xml för den tyska XRechnung-varianten, registreras i katalogens /AF-array och trädet /Names /EmbeddedFiles, med en AFRelationship-post som anger vad bilagan är: Alternative innebär att XML-koden är en likvärdig representation av den synliga fakturan, vilket är den semantik som de flesta profiler kräver. För det tredje måste Factur-X XMP-tilläggsschemat namnge den inbäddade filen, dess överensstämmelsenivå och dokumenttyp, så att ett mottagande system kan klassificera fakturan utan att ens behöva tolka XML-koden

Själva XML-innehållet följer den semantiska modellen EN 16931, den europeiska standarden som definierar kärnfakturan: köpare, säljare, radobjekt, momsredovisning, betalningsbelopp, var och en med ett affärstermnummer (BT-1 för fakturanummer, BT-5 för valuta, och så vidare). EN 16931-validering sker därför på två olika skikt. XML-schemavalidering kontrollerar att dokumentet är välformad CII med rätt elementnamn. Schematron-affärsreglerna kontrollerar att innehållet är sammanhängande — till exempel att BR-CO-15 uppfylls och att fakturans totalsumma faktiskt är lika med summan av radernas nettobelopp plus moms, samt att obligatoriska termer finns med för den deklarerade profilen. En fil kan klara schemavalidering och ändå vara en ogiltig faktura, vilket är anledningen till att affärsregelskiktet finns

Varför ZUGFeRD 2.5 inte behöver en ny behållare

ZUGFeRD 2.5 ändrar ingenting på PDF-behållarnivån: riktlinjernas URN, XMP-schemat och versionstoken är identiska med ZUGFeRD 2.3 / Factur-X 1.0. Detta överraskar de flesta implementörer, så det är värt att citera källan. Specifikationen för Factur-X 1.09 slår fast att versionsnumret i URI för PDF/A-tilläggsschemat inte är relaterat till versionsnumret för XML-dataspecifikationen, och att versionshanteringen för Factur-X-fakturainstanser förblir 1.0 — både i XMP fx:Version och i BT-24 av factur-x.xml. Det officiella ZUGFeRD 2.5-exempelmaterialet bekräftar detta: varje prov bäddar in factur-x.xml under URN-riktlinjen urn:factur-x.eu:1p0 med fx:Version som 1.0. Det som 2.5 faktiskt tillför finns inuti XML-datamodellen — nya element som underfakturarader och batch-identifierare — vilket är det affärsskikt som din applikation genererar, inte behållarskiktet som PDF-biblioteket hanterar

HotPDF kodar denna slutsats direkt. AddFacturXAssociatedFile accepterar ett Version-argument och normaliserar det via HPDFFacturXNormalizeVersion innan XMP skrivs. En anropare som skickar aliaset 2p5 får därmed fortfarande den token som uppfyller specifikationen, istället för att uppfinna en 2p5 XMP-version som ingen validerare skulle acceptera. Detekteringshjälparna behandlar 2p5 som likvärdigt vid läsning, vilket gör att operationerna förblir symmetriska. Om ett bibliotek eller verktyg hävdar att det skriver en speciell ZUGFeRD 2.5-behållare, specifikationen själv säger att något sådant inte existerar

Bädda in faktura-XML från Delphi

THotPDF.AddFacturXAssociatedFile förvandlar en vanlig PDF/A-3-skapsel till en Factur-X-faktura med ett enda anrop: den bäddar in XML-byten som en associerad fil, registrerar factur-x.xml i katalogens /AF-array och trädet /Names /EmbeddedFiles samt genererar matchande tilläggsmetadata för Factur-X XMP. Du ritar den människo-läsbara fakturasidan med HotPDF:s vanliga canvas-API; arbetet med behållaren är en enda rad mellan BeginDoc och 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;
    // 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;

Standardvärdena följer specifikationen: filnamn factur-x.xml, relation Alternative, version 1.0. AddUBLSupplementaryFile implementerar avsnitt 6.4 i Factur-X 1.09-specifikationen, vilket tillåter att en UBL-representation av samma faktura skickas med som en andra bilaga med namnet factur-xubl.xml, MIME-typ application/xml och relationen Alternative. Ordningsföljden är viktig här, och HotPDF upprätthåller den begränsning som specifikationen antyder: UBL-filen är en kompletterande vy, aldrig den primära fakturan, så den måste läggas till efter att AddFacturXAssociatedFile har registrerat CII-mastern. Observera att den ritade sidan och den inbäddade XML-koden måste visa samma fakturainformation — en PDF som visar en totalsumma men kodar en annan är precis det felscenarie som hybridfakturering är utformad för att förhindra. PDF/A-3-sidan i detta arbetsflöde, inklusive OutputIntent och typsnittsinbäddning, behandlas mer i detalj i artikeln om validering av PDF/A, PDF/X och PDF/UA i Delphi

Hur upptäcker du en Factur-X-faktura i en inkommande PDF?

THotPDF.DetectFacturXInvoice svarar mot mottagarsidan av mandatet: läs in valfri PDF och få reda på i ett enda anrop om det är en hybridfaktura, vilken profil den deklarerar och vilka bilagor den bär med sig. Funktionen returnerar en THPDFFacturXInvoiceInfo-post med det inbäddade XML-filnamnet, XMP-deklarerat filnamn och typ för dokumentet, versionstoken, överensstämmelsenivå, riktlinje-URN samt AFRelationship-värdet. Ytterligare två fält rapporterar kompletterande vyer: UBLFileName fylls i när en factur-xubl.xml-bilaga finns, och EDIFACTFileName när en EDIFACT-vy följer med — ingetdera påverkar detekteringsbeslutet, eftersom en kompletterande representation inte kan existera utan den primära CII-fakturan

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;

Detektering läser samma signaler som en kompatibel validerare läser — XMP-tilläggsschemat och registret över associerade filer — snarare än att gissa enbart utifrån filnamn. Samma inlästa dokumentobjekt exponerar också XMP- och Info-ordlistans metadata för inspektion eller korrigering, ett arbetsflöde som beskrivs i artikeln om redigering av metadata i inlästa PDF-dokument

Affärsregler eller schema: vad innebär EN 16931-validering?

HPDFValidateEInvoice, en fristående funktion i enheten HPDFEInvoiceValidator, kör båda valideringsskikten i sekvens: först kontrollen av behållarstruktur via ValidateFacturXInvoice, sedan EN 16931 Schematron-affärsreglerna mot den extraherade faktura-XML-koden. HotPDF levereras med de fem officiella Factur-X 1.09-regelfilerna — MINIMUM, BASIC WL, BASIC, EN 16931 och EXTENDED — under Lib/resources/Schematron/, och HPDFSchematronFileForProfile väljer den fil som matchar den detekterade överensstämmelsenivån. Det innebär att en EN 16931-faktura automatiskt kontrolleras mot Factur-X_1.09_EN16931.sch. Funktionen returnerar True endast när behållaren är giltig och ingen affärsregel utlöstes

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;

Varje post i BusinessRules bär på regelidentifieraren som tolkats från påståendetexten — BR-45, BR-CO-17 och resten av EN 16931-namnschemat — plus regelns sammanhangs-XPath, testuttrycket och en allvarlighetsgrad (severity) som speglar Schematrons flaggattribut. Det gör rapporten användbar: istället för ett enkelt godkänd/underkänd får du reda på exakt vilken affärsterm som är inkonsekvent, vilket är vad din supportavdelning behöver när en kund fråga varför en faktura avvisades. Utvecklare som redan genererar strukturerade överensstämmelserapporter för arkiverings-PDF:er kan integrera dessa resultat i samma pipeline som beskrivs i artikeln om automatisering av PDF-preflight-rapporter

Var valideraren sätter stopp, ärligt talat

Schematron-motorn bygger på MSXML, som utvärderar XPath 1.0. EN 16931-regelfilen deklarerar queryBinding="xslt2" och innehåller 424 påståenden (assertions), varav de flesta endast använder XPath 1.0-funktioner som string-length och substring-after — dessa körs inbyggt. En minoritet förlitar sig på XPath 2.0-konstruktioner som xs:decimal-typomvandlingar eller exists(). HotPDF förhandsskannar varje testuttryck efter kända XPath 2.0-tokens och markerar dessa regler som Skipped med allvarlighetsgraden info, snarare än att försöka göra en utvärdering som tyst skulle misslyckas. Sammanfattningen rapporterar antalet utvärderade respektive hoppade regler separat, så att din logg alltid anger exakt hur stor del av regeluppsättningen som har körts. I Delphi 7, där MSXML-bindningar saknas, enheten kompilerar en stubb som rapporterar motorn som ej tillgänglig utan att störa behållarbeslutet; den fullständiga motorn kräver Delphi XE2 eller senare

Den andra begränsningen är ännu viktigare. Ett grönt resultat från HPDFValidateEInvoice innebär att behållarens struktur stämmer överens och att ingen utvärderad affärsregel utlöstes — det är inte ett godkännande av skattemässig efterlevnad. Nationella tilläggsregler, mottagarspecifika krav och de juridiska skyldigheterna kring fakturainnehåll ligger ovanför kärnan i EN 16931 och faller helt utanför alla programbibliotek. Behandla valideraren som den grindvakt som håller strukturellt felaktiga fakturor borta från din utskorg och flaggar inkonsekventa i din inkorg, kör en fullständig XPath 2.0-validerare i din release-pipeline när certifiering kräver det, och låt dina skatterådgivare ansvara för efterlevnadsfrågan. API:erna för inbäddning, detektering och validering som visas här är en del av standardversionen av HotPDF Component för Delphi och C++Builder, med ett komplett e-fakturaexempel som täcker alla sju ZUGFeRD- och Factur-X-profiler