Technical Article

Hybridní faktury Factur-X a ZUGFeRD v Delphi

Vyhovující elektronická faktura není jen PDF s přilepeným XML souborem z boku. Jde o jediný dokument ve formátu PDF/A-3, který nese fakturu hned dvakrát: jednou jako stránku čitelnou pro člověka a podruhé jako strojově čitelné XML ve formátu Cross Industry Invoice uložené uvnitř souboru coby přidružený soubor. Obě tato zobrazení popisují tutéž fakturu. Tato duální povaha je podstatou rodiny formátů, které nyní vyžadují evropské směrnice: Factur-X ve Francii a Německu, ZUGFeRD napříč německy mluvícími trhy a XRechnung pro fakturaci ve veřejném sektoru v Německu. Tento článek vás provede tím, jak PDFlibPas sestavuje takovou hybridní fakturu v Delphi, kde standardy ponechávají prostor pro chyby a proč jeden profil v katalogu vyžaduje zcela oddělený generátor XML

Co to vlastně je hybridní faktura

Viditelná stránka a vložené XML slouží různým čtenářům. Úředník schvalující platbu se dívá na vykreslenou stránku. Systém pro zpracování závazků naproti tomu načte XML, přečte součty a rozpis daní jako strukturovaná pole a zaúčtuje položku, aniž by kdokoli musel cokoli přepisovat ručně. Sémantický obsah tohoto XML se řídí evropskou normou EN 16931, jež definuje datový model faktury: jaká pole existují, co znamenají a která jsou povinná. EN 16931 je sémantický model, nikoliv formát souboru. Formáty Factur-X, ZUGFeRD 2.x i XRechnung tento model realizují jako dokument UN/CEFACT Cross Industry Invoice, což je syntaxe, která přenáší pole z EN 16931 po síti

Aby byl dokument zároveň archivovatelný i sebepopisující, je jeho kontejnerem PDF/A-3, definované normou ISO 19005-3. PDF/A-3 představuje úroveň shody, která povoluje vložení jakýchkoli souborů, což je přesně to, co XML faktury potřebuje. Norma PDF/A-2 naopak zakazuje vkládat soubory, které samy o sobě nejsou ve formátu PDF/A, a proto faktura Factur-X nemůže být PDF/A-2. Volba PDF/A-3 tudíž není jen preferencí, je to striktní požadavek, jenž přímo vyplývá z potřeby vložit data, která nejsou typu PDF, do archivního dokumentu

Proč je relace nastavena jako Alternative

Samotné vložení bajtů je ta snazší část. Norma ISO 32000 v odstavci 7.11.4 definuje stream vloženého souboru, což je objekt obsahující surové XML a jeho parametry. To, co z něj dělá platný přidružený soubor, je odstavec 14.13, který přidává koncept přidruženého souboru a klíče /AFRelationship. Tento klíč stanovuje, jaký je vztah vložených dat k obsahu, ke kterému jsou připojena, a hodnota, kterou Factur-X vyžaduje, je Alternative

Na této volbě záleží, protože jiné hodnoty by o dokumentu tvrdily něco nepravdivého. Hodnota Source by znamenala, že XML je materiál, ze kterého byl viditelný obsah vygenerován, tedy předloha, ze které stránka vychází. Hodnota Supplement by naopak znamenala, že XML přidává informace nad rámec toho, co stránka zobrazuje, tedy něco navíc, co ve vizuální podobě není obsaženo. Ani jedno z toho však neodpovídá faktuře Factur-X. Zmíněné XML a stránka jsou dvěma rovnocennými vyjádřeními jedné faktury, nesou stejný právní obsah ve dvou různých formách. Alternative je hodnota, která říká přesně to: ekvivalentní alternativní reprezentace viditelného obsahu. Validátor, který ze souboru Factur-X vyčte jakoukoliv jinou relaci, jej odmítne a zcela po právu, protože tato relace je strojově čitelným tvrzením o tom, k čemu vlastně příloha slouží

Katalog profilů

Ukázkový příklad elektronické faktury, který je dodáván s PDFlibPas, prochází stejnou cestou generování napříč šesti profily definovanými jako pole záznamů v InvoiceModel.pas. Každý profil nese hodnoty, které tvůrce dokumentu potřebuje: zobrazovaný název, název vloženého souboru, úroveň shody, /AFRelationship, verzi, volitelný kód země a URN GuidelineID, které o sobě XML v kontextu svého dokumentu hlásá

Těchto šest představuje Factur-X EN16931, Factur-X BASIC, Factur-X EXTENDED pro Francii, XRechnung 3.0, ZUGFeRD 1.0 COMFORT a ZUGFeRD 2.0 BASIC. GuidelineID je pole, které příjemci přesně sděluje, jaký profil má očekávat, a jeho hodnoty jsou specifické. Profil Factur-X EN16931 hlásá urn:cen.eu:en16931:2017. XRechnung 3.0 oznamuje urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0. A ZUGFeRD 2.0 BASIC deklaruje urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p0:basic. Název vloženého souboru je rovněž součástí této dohody. Profily Factur-X vkládají factur-x.xml, XRechnung vkládá xrechnung.xml a profily ZUGFeRD využívají ZUGFeRD-invoice.xml nebo zugferd-invoice.xml. Příjemce skenuje názvy příloh, aby našel fakturu, proto jméno souboru rozhodně není pouze kosmetická záležitost

Jeden detail v katalogu si zaslouží pečlivé přečtení. Většina profilů používá relaci Alternative, avšak záznam XRechnung 3.0 v naší ukázce využívá hodnotu Source. Tyto dva formáty odpovídají odlišným validátorům a zvyklostem a ukázka proto nastavuje relaci každého profilu právě z katalogu namísto toho, aby jednu hodnotu pevně zakódovala. To je také důvod, proč zde existuje pole pro každý profil zvlášť namísto jedné konstanty

Past jménem ZUGFeRD 1.0

Nabízí se lákavá představa předpokládat, že každý profil je EN 16931 Cross Industry Invoice s drobnými odchylkami v tom, kolik volitelných polí nakonec vyplníte. To platí pro pět ze šesti z nich. Neplatí to však pro ZUGFeRD 1.0 COMFORT a důvod je spíše strukturální nežli kosmetický

Moderní profily generují UN/CEFACT Cross Industry Invoice s verzí jmenného prostoru :100, jehož kořenovým elementem je rsm:CrossIndustryInvoice. ZUGFeRD 1.0 tomuto schématu předchází. Jedná se o formát CrossIndustryDocument z roku 2014 s verzí jmenného prostoru :1p0, přičemž jeho kořenovým elementem je rsm:CrossIndustryDocument. URN jmenných prostorů se liší, liší se kořenový prvek a rozdílný je i celý strom prvků: schéma :1p0 seskupuje data pod položky ApplicableSupplyChainTradeAgreement, ApplicableSupplyChainTradeDelivery a ApplicableSupplyChainTradeSettlement, zatímco verze :100 používá ApplicableHeaderTradeAgreement, ApplicableHeaderTradeDelivery a ApplicableHeaderTradeSettlement. Pojmenování je dostatečně podobné na to, aby vás zmátlo, a přitom natolik odlišné, aby se vše rozbilo

Slovo COMFORT v názvu profilu popisuje, jak moc jsou data bohatá, tedy profil na úrovni automatizace s plnými položkami, rozpisem daně a platebními podmínkami, neurčuje však, které schéma je nese. Nemůžete tedy zkrátka vzít dokument :100 a pouze ho přeznačit na ZUGFeRD 1.0. Ukázka to řeší pomocí příznaku u každého záznamu profilu a dvojicí oddělených funkcí generátoru, kdy vybírá tu správnou ještě předtím, než se vůbec vygeneruje jakékoliv XML

function BuildInvoiceXMLText(const AProfile: TeInvoiceProfile;
  const Data: TInvoiceData): string;
begin
  // XMLFamily = 1 means the legacy ZUGFeRD 1.0 :1p0 schema; every
  // other profile is the modern UN/CEFACT :100 Cross Industry Invoice.
  if AProfile.XMLFamily = 1 then
    Result := BuildZUGFeRD1Text(AProfile, Data)
  else
    Result := BuildCII100Text(AProfile, Data);
end;

Toto rozdělení není žádná zbytečná implementační jemnost. Pokud strom :100 pošlete do přijímače očekávajícího ZUGFeRD 1.0, vytvoříte dokument, který u kořenového elementu selže při validaci schématu, takže obě rodiny musí být sestaveny kódem, který bezpečně ví, kterou z nich zrovna zapisuje

Výběr úrovně PDF/A-3

PDF/A-3 má tři úrovně shody a PDFlibPas je vybírá přes volání SetPDFAMode. Režim 5 znamená PDF/A-3b, tedy úroveň, jež zaručuje spolehlivou vizuální reprodukci. Režim 6 je PDF/A-3a, který k tomu ještě přidává tagovanou strukturu a požadavky na přístupnost z úrovně a. Režim 7 označuje PDF/A-3u, jenž vyžaduje, aby veškerý text byl zmapován na Unicode. Povolením tohoto režimu se také vloží vestavěný sRGB output intent knihovny, tedy barevná charakteristika, kterou PDF/A vyžaduje k tomu, aby vykreslená barva byla definována a nezávisela pouze na použitém zařízení

Většina toků pro zpracování faktur běží na úrovni 3b, což je dostačující pro věrně zobrazenou viditelnou stránku doplněnou o vložené XML. Pokud potřebujete výslovný ICC profil namísto toho vestavěného, metoda LoadOutputIntentProfile jej nahradí poté, co je režim nastaven. Ukázka takto načte sRGB profil z repozitáře a v případě, že soubor není dostupný, ustoupí zpět k vestavěnému intentu, takže output intent je ve výsledku vždy přítomen

PDF := TPDFlib.Create;
try
  // Mode 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');

  // Optional: swap the built-in sRGB intent for an explicit ICC profile.
  if PDF.LoadOutputIntentProfile(ICCFile, 'DeviceRGB') <> 1 then
    { fall back to the built-in sRGB intent that SetPDFAMode embedded };
finally
  // ... continue building the document
end;

Sestavení hybridní faktury

Máme-li kontejner nakonfigurovaný, to zbývající se skládá ze tří kroků v pořadí: nastavit režim PDF/A-3, vykreslit stránku čitelnou pro člověka a nakonec připojit XML jako přidružený soubor. Viditelná stránka představuje obyčejný obsah. Jediným omezením, na které je dobré pamatovat, je fakt, že norma PDF/A zakazuje nevložená písma Standard 14, takže stránka musí vložit reálný font, a nikoliv pouze odkazovat na písmo vestavěné

Vložení přílohy je na jedno volání. Metoda AddFacturXAssociatedFileFromString vezme hrubé bajty UTF-8 XML spolu s metadaty profilu, zapíše stream vloženého souboru, zaregistruje jej do pole katalogu /AF, které PDF/A-3 vyžaduje, aplikuje /AFRelationship a vygeneruje v XMP metadata elektronické faktury, jež dokument identifikují jako Factur-X, ZUGFeRD nebo XRechnung. Navíc zkontroluje, zda se guideline ID v XML shoduje s úrovní shody, o kterou jste žádali, čímž se případný nesoulad mezi XML, které jste sestavili, a pojmenovaným profilem odhalí dříve, než by tiše putoval k zákazníkovi

// 1. PDF/A-3 mode and output intent are already set.
// 2. Draw the visible page (embeds a real TrueType font).
DrawInvoicePage(PDF, AProfile, Data);

// 3. Build the profile-correct XML and attach it as an
//    associated file with /AFRelationship = Alternative.
InvoiceXML := BuildInvoiceXML(AProfile, Data);   // AnsiString of UTF-8 bytes
FileID := PDF.AddFacturXAssociatedFileFromString(
  InvoiceXML,
  AProfile.ConformanceLevel,   // e.g. 'EN16931'
  AProfile.FileName,           // 'factur-x.xml'
  AProfile.Description,
  AProfile.Relationship,       // 'Alternative'
  AProfile.Version,            // '1.0'
  AProfile.CountryCode);       // '' or 'DE' or 'FR'
if FileID <= 0 then
  raise Exception.Create('Invoice XML could not be attached');

PDF.SaveToFile(TargetFile);

Jednou ze záludností datové cesty je kódování. Vložené XML deklaruje encoding="UTF-8" a daná metoda přijímá jeho bajty jako typ AnsiString, takže non-ASCII jméno prodejce nebo kupujícího se k volání musí dostat jako surové oktety v UTF-8. Obyčejné přetypování přes systémovou kódovou stránku ANSI by tyto znaky poškodilo a nenápadně vytvořilo fakturu, jejíž XML již neodpovídá vlastní deklaraci. Naše ukázka tyto znaky pro jistotu před odesláním bajtů výslovně kóduje do UTF-8, což představuje ten nejbezpečnější způsob, jak předávat data typu Unicode string jakémukoliv byte orientovanému PDF API

Pro připojení XML, které nepatří k rozpoznaným profilům elektronické faktury, slouží obecný protějšek AddPDFA3AssociatedFileFromString. Přijímá název souboru, MIME typ, popis, relaci a bajty a nakonec zapisuje obyčejný přidružený soubor ve formátu PDF/A-3 bez jakýchkoli metadat specifických pro fakturu či kontrol shody. Použijte jej pro doplňková data; na samotné faktury používejte metodu pro Factur-X, takže metadata profilu i potřebné informace o shodě se zapíšou za vás

Jakmile je dokument hotový, dalšími otázkami je to, zda projde validací na formát PDF/A a kontrolou přístupnosti, a rovněž to, zda jej lze podepsat, aniž by došlo k narušení shody s normami. To se probírá v průvodci pro PDF/A a PDF/UA preflight kontroly a v článku o pracovním prostředí pro soulad s předpisy a elektronické podepisování. Vše z toho je dodáváno jako součást produktu PDFlibPas Delphi PDF Library po boku rozhraní API pro PDF/A, tvorbu tagů a vlastnosti dokumentů, na kterých celá koncepce zpracování elektronické faktury stojí