Technisch artikel

Factur-X en ZUGFeRD hybride facturen in Delphi

Een conforme elektronische factuur is geen PDF met een XML-bestand eraan vastgeniet. Het is één enkel PDF/A-3-document dat de factuur twee keer bevat: één keer als een pagina die een mens leest, en één keer als een machinaal leesbare Cross Industry Invoice XML die in het bestand is opgeslagen als een gekoppeld bestand (associated file). De twee representaties beschrijven dezelfde factuur. Die dubbele aard is de essentie van de formaatfamilies die door Europese mandaten nu worden vereist, Factur-X in Frankrijk en Duitsland, ZUGFeRD in Duitstalige markten, en XRechnung voor facturering aan de Duitse publieke sector. Dit artikel bespreekt hoe PDFlibPas zo'n hybride factuur in Delphi samenstelt, waar de standaarden ruimte laten om het fout te doen, en waarom één profiel in de catalogus een compleet afzonderlijke XML-builder nodig heeft.

Wat een hybride factuur eigenlijk is

De zichtbare pagina en de ingesloten XML dienen verschillende lezers. Een medewerker die een betaling goedkeurt, kijkt naar de gerenderde pagina. Een crediteurenadministratie neemt de XML op, leest de totalen en btw-uitsplitsing als gestructureerde velden, en boekt de invoer zonder dat een mens iets intypt. De semantische inhoud van die XML wordt bepaald door EN 16931, de Europese standaard die het factuurdatamodel definieert: welke velden er bestaan, wat ze betekenen, en welke verplicht zijn. EN 16931 is een semantisch model, geen bestandsformaat. Factur-X, ZUGFeRD 2.x en XRechnung realiseren dat model allemaal als een UN/CEFACT Cross Industry Invoice-document, de syntaxis die de EN 16931-velden over het netwerk verzendt.

Om ervoor te zorgen dat het document zowel archiveerbaar als zelfbeschrijvend is, is de container PDF/A-3, gedefinieerd door ISO 19005-3. PDF/A-3 is het conformiteitsniveau dat willekeurige ingesloten bestanden toestaat, wat precies is wat een XML-factuur moet zijn. PDF/A-2 verbiedt het insluiten van bestanden die zelf geen PDF/A zijn, dus een Factur-X factuur kan geen PDF/A-2 zijn. De keuze voor PDF/A-3 is daarom geen voorkeur, het is een vereiste die direct voortvloeit uit de wens om niet-PDF-data in een archiefdocument in te sluiten.

Waarom de relatie 'Alternative' is

Het insluiten van de bytes is het makkelijke gedeelte. ISO 32000 §7.11.4 definieert de stream van het ingesloten bestand, het object dat de ruwe XML en de parameters ervan bevat. Het deel dat van het bestand een geldig gekoppeld bestand maakt, is §14.13, wat het concept van een 'associated file' en de sleutel /AFRelationship toevoegt. Deze sleutel geeft aan hoe de ingesloten data zich verhoudt tot de inhoud waaraan het is gekoppeld, en de waarde die Factur-X vereist is Alternative.

De keuze doet ertoe, omdat de andere waarden iets onwaars over het document zouden beweren. Source zou betekenen dat de XML het materiaal is waaruit de zichtbare inhoud is gegenereerd, een origineel waarvan de pagina is afgeleid. Supplement zou betekenen dat de XML informatie toevoegt die verder gaat dan wat de pagina laat zien, een extraatje dat niet in de rendering is opgenomen. Geen van beide is wat een Factur-X-factuur is. De XML en de pagina zijn twee gelijkwaardige uitdrukkingen van één factuur, die dezelfde juridische inhoud in twee vormen bevatten. Alternative is de waarde die precies dat zegt: een gelijkwaardige alternatieve weergave van de zichtbare inhoud. Een validator die een andere relatie op een Factur-X-bestand leest, zal deze afwijzen, en terecht, omdat de relatie een machinaal leesbare claim is over waar de bijlage voor is.

De profielencatalogus

Het E-Invoice-voorbeeld dat met PDFlibPas wordt meegeleverd, stuurt hetzelfde generatiepad aan voor zes profielen, gedefinieerd als een array van records in InvoiceModel.pas. Elk profiel bevat de waarden die de schrijver nodig heeft: een weergavenaam, de naam van het ingesloten bestand, een conformiteitsniveau, de /AFRelationship, een versie, een optionele landcode en de GuidelineID URN die de XML aankondigt binnen de documentcontext.

De zes zijn Factur-X EN16931, Factur-X BASIC, Factur-X EXTENDED voor Frankrijk, XRechnung 3.0, ZUGFeRD 1.0 COMFORT en ZUGFeRD 2.0 BASIC. De GuidelineID is het veld dat een ontvanger precies vertelt welk profiel te verwachten, en de waarden zijn specifiek. Factur-X EN16931 kondigt urn:cen.eu:en16931:2017 aan. XRechnung 3.0 kondigt urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0 aan. ZUGFeRD 2.0 BASIC kondigt urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p0:basic aan. De bestandsnaam van het ingesloten bestand maakt ook deel uit van het contract. Factur-X profielen sluiten factur-x.xml in, XRechnung sluit xrechnung.xml in, en de ZUGFeRD profielen sluiten ZUGFeRD-invoice.xml of zugferd-invoice.xml in. Een ontvanger scant de namen van de bijlagen om de factuur te vinden, dus de bestandsnaam is niet cosmetisch.

Eén detail in de catalogus is de moeite waard om zorgvuldig te lezen. De meeste profielen gebruiken de Alternative relatie, maar het XRechnung 3.0 item in het voorbeeld gebruikt Source. De twee formaten beantwoorden aan verschillende validators en conventies, en het voorbeeld stelt de relatie van elk profiel in vanuit de catalogus in plaats van één enkele waarde hard te coderen, wat de reden is waarom het veld per profiel bestaat in plaats van een constante.

De ZUGFeRD 1.0-valstrik

Het is verleidelijk om aan te nemen dat elk profiel de EN 16931 Cross Industry Invoice is, met kleine variaties in de hoeveelheid optionele velden die u invult. Dat geldt voor vijf van de zes. Het geldt niet voor ZUGFeRD 1.0 COMFORT, en de reden is eerder structureel dan cosmetisch.

De moderne profielen geven een UN/CEFACT Cross Industry Invoice uit met namespace-versie :100, waarvan het root-element rsm:CrossIndustryInvoice is. ZUGFeRD 1.0 dateert van voor dat schema. Het is de 2014 CrossIndustryDocument met namespace-versie :1p0, en het root-element is rsm:CrossIndustryDocument. De namespace-URN's verschillen, het root-element verschilt, en de elementenboom verschilt doorlopend: het :1p0 schema groepeert data onder ApplicableSupplyChainTradeAgreement, ApplicableSupplyChainTradeDelivery en ApplicableSupplyChainTradeSettlement, waar :100 gebruikmaakt van ApplicableHeaderTradeAgreement, ApplicableHeaderTradeDelivery en ApplicableHeaderTradeSettlement. De naamgeving is vergelijkbaar genoeg om te misleiden en verschillend genoeg om het proces te verstoren.

Het woord COMFORT in de profielnaam beschrijft hoe rijk de data is, een automatiseringswaardig profiel met volledige regelitems, btw-uitsplitsing en betalingsvoorwaarden, en niet welk schema het bevat. U kunt dus geen :100 document pakken en het herlabelen voor ZUGFeRD 1.0. Het voorbeeld gaat hiermee om met een vlag op elke profielrecord en twee afzonderlijke builder-functies, waarbij de juiste wordt geselecteerd voordat er XML wordt gegenereerd.

function BuildInvoiceXMLText(const AProfile: TeInvoiceProfile;
  const Data: TInvoiceData): string;
begin
  // XMLFamily = 1 betekent het oude ZUGFeRD 1.0 :1p0 schema; elk
  // ander profiel is de moderne UN/CEFACT :100 Cross Industry Invoice.
  if AProfile.XMLFamily = 1 then
    Result := BuildZUGFeRD1Text(AProfile, Data)
  else
    Result := BuildCII100Text(AProfile, Data);
end;

De splitsing is geen implementatiedetail. Het aanleveren van een :100 tree aan een ZUGFeRD 1.0 ontvanger produceert een document dat niet door de schemavalidatie bij het root-element komt, dus de twee families moeten worden gebouwd door code die weet welke versie geschreven wordt.

Het PDF/A-3-niveau selecteren

PDF/A-3 heeft drie conformiteitsniveaus en PDFlibPas selecteert deze via SetPDFAMode. Modus 5 is PDF/A-3b, het niveau dat een betrouwbare visuele weergave garandeert. Modus 6 is PDF/A-3a, die de eisen voor getagde structuur en toegankelijkheid van niveau a toevoegt. Modus 7 is PDF/A-3u, die vereist dat alle tekst aan Unicode wordt toegewezen. Het inschakelen van de modus sluit ook de ingebouwde sRGB output intent van de bibliotheek in, de kleurkarakterisering die PDF/A vereist zodat gerenderde kleuren gedefinieerd zijn en niet apparaatafhankelijk.

De meeste factuurstromen draaien op 3b, wat voldoende is voor een getrouwe zichtbare pagina plus de ingesloten XML. Als u een expliciet ICC-profiel nodig heeft in plaats van het ingebouwde profiel, wisselt LoadOutputIntentProfile dit in nadat de modus is ingesteld. Het voorbeeld laadt het sRGB-profiel van de repository op deze manier en valt terug op de ingebouwde intentie wanneer het bestand niet bereikbaar is, zodat de output intent altijd aanwezig is.

PDF := TPDFlib.Create;
try
  // Modus 5 = PDF/A-3b, 6 = PDF/A-3a, 7 = PDF/A-3u.
  if PDF.SetPDFAMode(5) <> 1 then
    raise Exception.Create('PDF/A-3 mode could not be enabled');

  // Optioneel: wissel de ingebouwde sRGB-intentie in voor een expliciet ICC-profiel.
  if PDF.LoadOutputIntentProfile(ICCFile, 'DeviceRGB') <> 1 then
    { val terug op de ingebouwde sRGB-intentie die SetPDFAMode heeft ingesloten };
finally
  // ... ga verder met het bouwen van het document
end;

De hybride factuur bouwen

Met de container geconfigureerd, is de rest een proces van drie opeenvolgende stappen: het PDF/A-3-niveau instellen, de voor mensen leesbare pagina tekenen en vervolgens de XML als gekoppeld bestand toevoegen. De zichtbare pagina is gewone inhoud. De enige beperking om te onthouden is dat PDF/A de niet-ingesloten Standaard 14-lettertypen verbiedt, dus de pagina moet een echt lettertype insluiten in plaats van te verwijzen naar een ingebouwd lettertype.

De bijlage is een enkele aanroep. AddFacturXAssociatedFileFromString pakt de ruwe UTF-8 XML-bytes plus de profiel-metadata, schrijft de ingesloten file stream, registreert deze in de Catalogus /AF-array die PDF/A-3 vereist, past de /AFRelationship toe, en genereert de XMP e-factuur metadata die het document identificeert als Factur-X, ZUGFeRD of XRechnung. Het controleert ook of de guideline-ID van de XML overeenkomt met het conformiteitsniveau waar u om vroeg, zodat een wanverhouding tussen de XML die u bouwde en het profiel dat u noemde wordt opgevangen in plaats van ongemerkt te worden verzonden.

// 1. PDF/A-3 modus en output intent zijn al ingesteld.
// 2. Teken de zichtbare pagina (sluit een echt TrueType-lettertype in).
DrawInvoicePage(PDF, AProfile, Data);

// 3. Bouw de profiel-correcte XML en voeg deze toe als een
//    gekoppeld bestand met /AFRelationship = Alternative.
InvoiceXML := BuildInvoiceXML(AProfile, Data);   // AnsiString van UTF-8 bytes
FileID := PDF.AddFacturXAssociatedFileFromString(
  InvoiceXML,
  AProfile.ConformanceLevel,   // bijv. 'EN16931'
  AProfile.FileName,           // 'factur-x.xml'
  AProfile.Description,
  AProfile.Relationship,       // 'Alternative'
  AProfile.Version,            // '1.0'
  AProfile.CountryCode);       // '' of 'DE' of 'FR'
if FileID <= 0 then
  raise Exception.Create('Invoice XML could not be attached');

PDF.SaveToFile(TargetFile);

Eén subtiliteit in het datapad is de codering. De ingesloten XML verklaart encoding="UTF-8", en de methode neemt de bytes op als een AnsiString, zodat een niet-ASCII verkopers- of kopersnaam de aanroep moet bereiken als ruwe UTF-8 octetten. Een gewone cast via de systeem ANSI-codepagina zou die tekens corrumperen en stilletjes een factuur produceren waarvan de XML niet meer overeenkomt met de eigen declaratie. Het voorbeeld codeert expliciet naar UTF-8 voordat de bytes worden doorgegeven; dat is de veilige manier om elke byte-georiënteerde PDF API vanuit een Unicode-string te voeden.

Voor het bijvoegen van XML die geen erkend e-factuurprofiel is, is AddPDFA3AssociatedFileFromString de generieke tegenhanger. Het neemt een bestandsnaam, MIME-type, beschrijving, relatie en bytes, en schrijft een gewoon PDF/A-3 gekoppeld bestand zonder factuurspecifieke metadata of guideline-controles. Gebruik dit voor aanvullende data; gebruik de Factur-X methode voor facturen, zodat de profiel-metadata en de guideline match voor u worden geschreven.

Zodra het document is geproduceerd, is de volgende vraag of het de PDF/A- en toegankelijkheidsvalidatie doorstaat, en of het kan worden ondertekend zonder dat de naleving in het geding komt. Deze zaken worden behandeld in de walkthrough voor PDF/A- en PDF/UA-preflight in Delphi en de compliance- en ondertekeningswerkbank. Dit alles is beschikbaar als onderdeel van de PDFlibPas Delphi PDF Library, naast de PDF/A, tagging- en documenteigenschappen-API's waarop de e-factuur route voortbouwt.