Tekninen artikkeli

Factur-X- ja ZUGFeRD-hybridilaskut Delphissä

Vaatimustenmukainen sähköinen lasku ei ole PDF, jonka kylkeen on nidottu XML-tiedosto. Se on yksi ainoa PDF/A-3-asiakirja, joka kantaa laskun kahteen kertaan: kerran sivuna, jota ihminen lukee, ja kerran koneellisesti luettavana Cross Industry Invoice XML -tiedostona, joka on tallennettu asiakirjan sisään liitetiedostona (associated file). Nämä kaksi esitystapaa kuvaavat samaa laskua. Tämä kaksoisluonne on koko ydin niissä formaattiperheissä, joita Euroopan laajuiset mandaatit nykyään edellyttävät: Factur-X Ranskassa ja Saksassa, ZUGFeRD saksankielisillä markkinoilla, ja XRechnung Saksan julkisen sektorin laskutuksessa. Tämä artikkeli käy läpi, kuinka PDFlibPas kokoaa tällaisen hybridilaskun Delphissä, missä kohtaa standardit jättävät tilaa virheille, ja miksi yksi valikoiman profiileista tarvitsee täysin erillisen XML-rakentajan

Mikä hybridilasku todellisuudessa on

Näkyvä sivu ja upotettu XML palvelevat eri lukijoita. Maksua hyväksyvä virkailija katsoo renderöityä sivua. Ostoreskontrajärjestelmä (accounts-payable system) nielee XML:n, lukee loppusummat ja verojen erittelyn rakenteellisina kenttinä, ja kirjaa viennin ilman, että ihminen näppäilee mitään. Tämän XML:n semanttista sisältöä ohjaa EN 16931, eurooppalainen standardi, joka määrittelee laskun tietomallin: mitkä kentät ovat olemassa, mitä ne merkitsevät ja mitkä niistä ovat pakollisia. EN 16931 on semanttinen malli, ei tiedostomuoto. Factur-X, ZUGFeRD 2.x ja XRechnung toteuttavat kaikki tuon mallin UN/CEFACT Cross Industry Invoice -asiakirjana, syntaksina, joka kantaa EN 16931 -kentät siirrossa

Jotta asiakirja voisi olla sekä arkistoitava että itseään kuvaileva, säiliönä toimii ISO 19005-3:n määrittelemä PDF/A-3. PDF/A-3 on se vaatimustenmukaisuustaso, joka sallii mielivaltaisten upotettujen tiedostojen käytön, mikä on juuri sitä, mitä laskun XML:n tarvitsee olla. PDF/A-2 kieltää sellaisten tiedostojen upottamisen, jotka eivät itse ole PDF/A-tiedostoja, joten Factur-X-lasku ei voi olla PDF/A-2. PDF/A-3:n valinta ei siten ole makuasia, vaan se on suora seuraus vaatimuksesta upottaa muuta kuin PDF-tietoa arkistoitavaan asiakirjaan

Miksi suhde (relationship) on Alternative

Tavujen upottaminen on helppo osa. ISO 32000 §7.11.4 määrittelee upotetun tiedostovirran (embedded file stream), objektin, joka pitää sisällään raa'an XML:n ja sen parametrit. Osa, joka tekee tiedostosta kelvollisen liitetiedoston, on §14.13, joka lisää käsitteen liitetiedostosta (associated file) ja /AFRelationship-avaimesta. Tämä avain kertoo, miten upotettu tieto suhtautuu siihen sisältöön, johon se on liitetty, ja Factur-X:n vaatima arvo on Alternative

Valinnalla on merkitystä, koska muut arvot väittäisivät asiakirjasta jotain, mikä ei ole totta. Source tarkoittaisi, että XML on se materiaali, josta näkyvä sisältö on luotu – master-kappale, josta sivu on johdettu. Supplement tarkoittaisi, että XML lisää tietoa sen yli, mitä sivu näyttää – jotain ylimääräistä, jota renderöinti ei sisällä. Kumpikaan näistä ei vastaa sitä, mikä Factur-X-lasku on. XML ja sivu ovat saman laskun kaksi tasavertaista ilmentymää, jotka kantavat saman juridisen sisällön kahdessa muodossa. Alternative on arvo, joka sanoo juuri tämän: näkyvän sisällön kanssa tasavertainen, vaihtoehtoinen esitystapa. Validaattori, joka lukee minkä tahansa muun suhteen Factur-X-tiedostosta, hylkää sen, ja syystä, koska suhde on koneellisesti luettava väite siitä, mitä varten liite on olemassa

Profiilivalikoima

PDFlibPas-kirjaston mukana toimitettava E-Invoice-esimerkki (E-Invoice sample) ajaa samaa generointipolkua kuuden profiilin yli, jotka on määritelty tietueiden taulukkona tiedostossa InvoiceModel.pas. Jokainen profiili kantaa mukanaan kirjoittajan tarvitsemat arvot: näyttönimen, upotetun tiedoston nimen, vaatimustenmukaisuustason, /AFRelationship-arvon, version, valinnaisen maakoodin ja GuidelineID URN:n, jonka XML ilmoittaa asiakirjakontekstinsa sisällä

Nämä kuusi ovat Factur-X EN16931, Factur-X BASIC, Factur-X EXTENDED Ranskalle, XRechnung 3.0, ZUGFeRD 1.0 COMFORT ja ZUGFeRD 2.0 BASIC. GuidelineID on kenttä, joka kertoo vastaanottajalle täsmälleen, mitä profiilia odottaa, ja arvot ovat hyvin tarkkoja. Factur-X EN16931 ilmoittaa urn:cen.eu:en16931:2017. XRechnung 3.0 ilmoittaa urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0. ZUGFeRD 2.0 BASIC ilmoittaa urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p0:basic. Upotetun tiedoston nimi on sekin osa sopimusta. Factur-X-profiilit upottavat nimen factur-x.xml, XRechnung upottaa nimen xrechnung.xml, ja ZUGFeRD-profiilit upottavat nimen ZUGFeRD-invoice.xml tai zugferd-invoice.xml. Vastaanottaja skannaa liitteiden nimet löytääkseen laskun, joten tiedostonimi ei ole pelkkää kosmetiikkaa

Yksi yksityiskohta valikoimassa on syytä lukea huolellisesti. Useimmat profiilit käyttävät Alternative-suhdetta, mutta XRechnung 3.0 -merkintä esimerkissä käyttää suhdetta Source. Nämä kaksi formaattia vastaavat eri validaattoreille ja käytäntöihin, ja esimerkki asettaa kunkin profiilin suhteen valikoimasta (catalog) sen sijaan, että se kovakoodaisi yhden arvon, mikä on syy siihen, miksi profiilikohtainen kenttä on ylipäänsä olemassa vakion sijaan

ZUGFeRD 1.0 -ansa

On houkuttelevaa olettaa, että jokainen profiili on EN 16931 Cross Industry Invoice pienin variaatioin sen suhteen, kuinka monta valinnaista kenttää täytät. Tämä pätee viiteen kuudesta. Se ei kuitenkaan päde ZUGFeRD 1.0 COMFORTiin, ja syy on pikemminkin rakenteellinen kuin kosmeettinen

Modernit profiilit emittoivat UN/CEFACT Cross Industry Invoice -rakenteen nimiavaruusversiolla (namespace version) :100, jonka juurielementti on rsm:CrossIndustryInvoice. ZUGFeRD 1.0 on tätä skeemaa edeltävältä ajalta. Se on vuoden 2014 CrossIndustryDocument nimiavaruusversiolla :1p0, ja sen juurielementti on rsm:CrossIndustryDocument. Nimiavaruus-URN:t eroavat toisistaan, juurielementti on eri, ja koko elementtipuu on erilainen: :1p0-skeema ryhmittelee tiedot elementtien ApplicableSupplyChainTradeAgreement, ApplicableSupplyChainTradeDelivery ja ApplicableSupplyChainTradeSettlement alle, kun taas :100 käyttää elementtejä ApplicableHeaderTradeAgreement, ApplicableHeaderTradeDelivery ja ApplicableHeaderTradeSettlement. Nimeäminen on riittävän samankaltaista johtaakseen harhaan ja riittävän erilaista rikkoutuakseen

Sana COMFORT profiilin nimessä kuvaa sitä, kuinka rikasta tieto on – automaatiotason profiili täysillä rivitiedoilla, veroerittelyllä ja maksuehdoilla – ei sitä, mikä skeema sitä kantaa. Et siis voi ottaa :100-asiakirjaa ja uudelleennimetä sitä ZUGFeRD 1.0:ksi. Esimerkkikoodi käsittelee tämän asettamalla lipun (flag) kuhunkin profiilitietueeseen ja käyttämällä kahta erillistä rakentajafunktiota, valiten oikean ennen kuin yhtäkään XML-tiedostoa generoidaan

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;

Jako ei ole vain toteutuksen hienous. Jos syötät :100-puun ZUGFeRD 1.0 -vastaanottajalle, se tuottaa asiakirjan, joka epäonnistuu skeemavalidoinnissa heti juurielementissä, joten nämä kaksi perhettä on rakennettava koodilla, joka tietää, kumpaa se on kirjoittamassa

PDF/A-3-tason valinta

PDF/A-3:ssa on kolme vaatimustenmukaisuustasoa, ja PDFlibPas valitsee ne SetPDFAMode-kutsun kautta. Tila 5 on PDF/A-3b, taso, joka takaa luotettavan visuaalisen toiston. Tila 6 on PDF/A-3a, joka lisää a-tason tagausrakenteen ja saavutettavuusvaatimukset. Tila 7 on PDF/A-3u, joka edellyttää, että kaikki teksti on mäpätty Unicodeen. Tilan ottaminen käyttöön upottaa myös kirjaston sisäänrakennetun sRGB-output intentin, värien karakterisoinnin, jota PDF/A vaatii, jotta renderöity väri on määritelty eikä riippuvainen laitteesta

Useimmat laskutusvirrat pyörivät 3b-tasolla, mikä riittää uskolliseen näkyvään sivuun ja upotettuun XML:ään. Jos tarvitset eksplisiittisen ICC-profiilin sisäänrakennetun sijaan, LoadOutputIntentProfile vaihtaa sen sisään sen jälkeen, kun tila on asetettu. Esimerkki lataa repositorion sRGB-profiilin tällä tavoin ja putoaa takaisin sisäänrakennettuun intentiin, kun tiedostoa ei saada kiinni, joten output intent on aina olemassa

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;

Hybridilaskun rakentaminen

Kun säiliö on konfiguroitu, loppu koostuu kolmesta vaiheesta järjestyksessä: aseta PDF/A-3-tila, piirrä ihmisen luettavissa oleva sivu ja liitä sitten XML liitetiedostona. Näkyvä sivu on tavallista sisältöä. Yksi rajoitus, joka on syytä muistaa, on se, että PDF/A kieltää upottamattomat Standard 14 -fontit, joten sivun on upotettava todellinen fonttikirjasin (font face) sisäänrakennettuun viittaamisen sijaan

Liitteen teko on yksittäinen kutsu. AddFacturXAssociatedFileFromString ottaa raa'at UTF-8 XML -tavut ja profiilin metatiedot, kirjoittaa upotetun tiedostovirran, rekisteröi sen Catalogin /AF-taulukkoon (kuten PDF/A-3 vaatii), soveltaa /AFRelationship-suhdetta ja luo XMP-verkkolaskumetatietoa, joka tunnistaa asiakirjan Factur-X:ksi, ZUGFeRDiksi tai XRechnungiksi. Se myös tarkistaa, että XML:n ohjeistus-ID (guideline ID) vastaa pyytämääsi vaatimustenmukaisuustasoa, joten rakentamasi XML:n ja nimeämäsi profiilin välinen epäsuhta jää kiinni sen sijaan, että se lähetettäisiin hiljaisesti eteenpäin

// 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);

Yksi hienovaraisuus datapolussa on koodaus (encoding). Upotettu XML julistaa encoding="UTF-8", ja metodi ottaa sen tavut AnsiString-muodossa, joten ei-ASCII-myyjän tai ostajan nimen on saavutettava kutsu raakoina UTF-8-oktetteina. Pelkkä tyyppimuunnos (cast) järjestelmän ANSI-koodisivun läpi turmelisi nuo merkit ja tuottaisi kaikessa hiljaisuudessa laskun, jonka XML ei enää vastaa sen omaa julistusta. Esimerkki koodaa UTF-8:aan eksplisiittisesti ennen tavujen ojentamista, mikä on turvallinen tapa ruokkia mitä tahansa tavupohjaista PDF-ohjelmointirajapintaa Unicode-tyyppisestä string-merkkijonosta

Sellaisen XML:n liittämiseen, joka ei ole tunnistettu verkkolaskuporfiili, AddPDFA3AssociatedFileFromString on yleiskäyttöinen vastine. Se ottaa tiedoston nimen, MIME-tyypin, kuvauksen, suhteen ja tavut, ja kirjoittaa puhtaan PDF/A-3-liitetiedoston ilman minkäänlaisia laskukohtaisia metatietoja tai ohjeistustarkistuksia. Käytä sitä täydentävälle datalle; käytä Factur-X-metodia laskuille, jotta profiilimetatiedot ja ohjeistuksen osumat kirjoitetaan puolestasi

Kun asiakirja on tuotettu, seuraavat kysymykset ovat, läpäiseekö se PDF/A- ja saavutettavuusvalidoinnin, ja voidaanko se allekirjoittaa rikkomatta vaatimustenmukaisuutta. Näitä käsitellään artikkeleissa läpikäyntimme PDF/A:n ja PDF/UA:n preflight-tarkistuksista Delphissä ja vaatimustenmukaisuus- ja allekirjoitustyöpenkki. Kaikki tämä toimitetaan osana PDFlibPas Delphi PDF -kirjastoa niiden PDF/A-, tagaus- ja asiakirjan ominaisuuksien ohjelmointirajapintojen rinnalla, joille verkkolaskupolku rakentuu