Tekninen artikkeli

Factur-X- ja ZUGFeRD-hybridilaskut Delphissä

Yhteensopiva sähköinen lasku ei ole PDF, jonka kylkeen on nidottu XML-tiedosto. Se on yksi PDF/A-3-asiakirja, joka sisältää laskun kahdesti: kerran sivuna, jonka ihminen lukee, ja kerran koneellisesti luettavana Cross Industry Invoice XML -tiedostona, joka on tallennettu tiedoston sisään liitetyksi asiakirjaksi (associated file). Molemmat esitystavat kuvaavat samaa laskua. Tämä kaksoisluonne on koko ajatus niiden muotoperheiden takana, joita eurooppalaiset mandaatit nykyään vaativat, Factur-X Ranskassa ja Saksassa, ZUGFeRD saksankielisillä markkinoilla ja XRechnung Saksan julkisen sektorin laskutuksessa. Tässä artikkelissa käydään läpi, kuinka PDFlibPas kokoaa tällaisen hybridilaskun Delphissä, missä standardit jättävät tilaa virheille ja miksi yksi luettelon profiili tarvitsee täysin erillisen XML-rakentajan

Mikä hybridilasku oikeastaan on

Näkyvä sivu ja upotettu XML palvelevat eri lukijoita. Maksun hyväksyvä virkailija katsoo renderöityä sivua. Ostoreskontrajärjestelmä nielee XML:n, lukee loppusummat ja verojen erittelyn jäsenneltyinä kenttinä ja kirjaa viennin ilman, että kukaan ihminen naputtelee 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 tarkoittavat ja mitkä ovat pakollisia. EN 16931 on semanttinen malli, ei tiedostomuoto. Factur-X, ZUGFeRD 2.x ja XRechnung toteuttavat kaikki kyseisen mallin UN/CEFACT Cross Industry Invoice -asiakirjana, syntaksina, joka kuljettaa EN 16931 -kentät verkossa

Jotta asiakirja voisi olla sekä arkistoitava että itseään kuvaava, säiliö on PDF/A-3, jonka määrittelee ISO 19005-3. PDF/A-3 on vaatimustenmukaisuustaso, joka sallii mielivaltaiset upotetut tiedostot, mikä on juuri sitä, mitä lasku-XML:n on oltava. 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 siksi ole mieltymys, se on vaatimus, joka seuraa suoraan halusta upottaa ei-PDF-dataa arkistoitavaan asiakirjaan

Miksi suhde on Alternative

Tavujen upottaminen on helppo osa. ISO 32000 §7.11.4 määrittelee upotetun tiedostovirran, objektin, joka sisältää raa'an XML:n ja sen parametrit. Osa, joka tekee tiedostosta kelvollisen associated file -tiedoston (liitetyn tiedoston), on §14.13, joka lisää associated file -käsitteen ja /AFRelationship-avaimen. Kyseinen avain ilmoittaa, miten upotettu data liittyy sisältöön, johon se on kiinnitetty, ja arvo, jota Factur-X vaatii, on Alternative

Valinnalla on merkitystä, koska muut arvot väittäisivät jotain väärää asiakirjasta. Source (Lähde) tarkoittaisi, että XML on materiaali, josta näkyvä sisältö on tuotettu, master-kappale, josta sivu on johdettu. Supplement (Täydennys) tarkoittaisi, että XML lisää tietoja sen lisäksi, mitä sivu näyttää, lisäosa, jota renderöinti ei sisällä. Kumpikaan näistä ei ole se, mikä Factur-X-lasku on. XML ja sivu ovat saman laskun kaksi vastaavaa ilmaisua, jotka sisältävät saman oikeudellisen sisällön kahdessa muodossa. Alternative on arvo, joka sanoo juuri tämän: näkyvän sisällön vastaava vaihtoehtoinen esitys. Validaattori, joka lukee minkä tahansa muun suhteen Factur-X-tiedostosta, hylkää sen, ja syystäkin, koska suhde on koneellisesti luettava väite siitä, mitä varten liite on

Profiililuettelo

E-Invoice-esimerkki, joka toimitetaan PDFlibPas:n mukana, ohjaa saman sukupolvipolun kuuden profiilin läpi, jotka on määritelty tietuetaulukkona kohteessa InvoiceModel.pas. Jokainen profiili sisältää arvot, joita kirjoittaja tarvitsee: näyttönimi, upotetun tiedoston nimi, vaatimustenmukaisuustaso, /AFRelationship, versio, valinnainen maakoodi ja GuidelineID URN, jonka XML ilmoittaa asiakirjakohtekstissään

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 tarkalleen, mitä profiilia odottaa, ja arvot ovat erityisiä. 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 myös osa sopimusta. Factur-X-profiilit upottavat factur-x.xml, XRechnung upottaa xrechnung.xml, ja ZUGFeRD-profiilit upottavat ZUGFeRD-invoice.xml tai zugferd-invoice.xml. Vastaanottaja skannaa liitteiden nimet löytääkseen laskun, joten tiedostonimi ei ole kosmeettinen

Yksi luettelon yksityiskohta on syytä lukea huolellisesti. Useimmat profiilit käyttävät Alternative-suhdetta, mutta näytteen XRechnung 3.0 -merkintä käyttää Source-suhdetta. Kaksi muotoa vastaavat erilaisiin validaattoreihin ja käytäntöihin, ja malli määrittää kunkin profiilin suhteen luettelosta sen sijaan, että se kovakoodaisi yhden arvon, minkä vuoksi profiilikohtainen kenttä on olemassa vakion sijaan

ZUGFeRD 1.0 -ansa

On houkuttelevaa olettaa, että jokainen profiili on EN 16931 Cross Industry Invoice pienillä muunnelmilla siitä, kuinka monta valinnaista kenttää täytät. Tämä pätee viidelle kuudesta. Se ei päde ZUGFeRD 1.0 COMFORTille, ja syy on rakenteellinen, ei kosmeettinen

Nykyaikaiset profiilit lähettävät UN/CEFACT Cross Industry -laskun nimiavaruusversiolla :100, jonka juurielementti on rsm:CrossIndustryInvoice. ZUGFeRD 1.0 edeltää kyseistä skeemaa. Se on vuoden 2014 CrossIndustryDocument nimiavaruusversiolla :1p0, ja sen juurielementti on rsm:CrossIndustryDocument. Nimiavaruus-URN:t eroavat toisistaan, juurielementti eroaa, ja elementtipuu eroaa kaikkialla: :1p0-skeema ryhmittelee tiedot otsikkojen ApplicableSupplyChainTradeAgreement, ApplicableSupplyChainTradeDelivery ja ApplicableSupplyChainTradeSettlement alle, kun taas :100 käyttää otsikkoja ApplicableHeaderTradeAgreement, ApplicableHeaderTradeDelivery ja ApplicableHeaderTradeSettlement. Nimeäminen on tarpeeksi samanlaista johtaakseen harhaan ja tarpeeksi erilaista rikkoakseen rakenteen

Sana COMFORT profiilin nimessä kuvaa, kuinka rikasta tieto on, automaatiotason profiili, jossa on täydet rivikohdat, verojen erittely ja maksuehdot, ei sitä, mikä skeema sitä kantaa. Joten et voi ottaa :100-asiakirjaa ja merkitä sitä uudelleen ZUGFeRD 1.0:ksi. Malli käsittelee tämän kunkin profiilitietueen lipulla ja kahdella erillisellä rakentajatoiminnolla valitsemalla oikean toiminnon ennen minkään XML:n luomista

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 toteutuksen hienous. Kun syötät :100-puun ZUGFeRD 1.0 -vastaanottimeen, tuotetaan asiakirja, joka epäonnistuu skeeman validoinnissa juurielementissä, joten koodin, joka tietää mitä se kirjoittaa, on rakennettava molemmat perheet

PDF/A-3-tason valinta

PDF/A-3:ssa on kolme vaatimustenmukaisuustasoa, ja PDFlibPas valitsee ne SetPDFAMode-toiminnon avulla. Tila 5 on PDF/A-3b, taso, joka takaa luotettavan visuaalisen toiston. Tila 6 on PDF/A-3a, joka lisää a-tason tagged-structure- ja saavutettavuusvaatimukset. Tila 7 on PDF/A-3u, joka edellyttää, että koko teksti on kartoitettava Unicodeen. Tilan ottaminen käyttöön upottaa myös kirjaston sisäänrakennetun sRGB-tulostusaiottumuksen (output intent), värin luonnehdinnan, jota PDF/A vaatii, jotta renderöity väri on määritelty eikä laitteesta riippuvainen

Useimmat laskuvirrat toimivat tasolla 3b, joka riittää uskolliseen näkyvään sivuun ja upotettuun XML:ään. Jos tarvitset eksplisiittisen ICC-profiilin sisäänrakennetun profiilin sijaan, LoadOutputIntentProfile vaihtaa sen sisään, kun tila on asetettu. Näyte lataa arkiston sRGB-profiilin tällä tavalla ja palaa sisäänrakennettuun tarkoitukseen, kun tiedostoa ei voida tavoittaa, joten tulostusaikomus on aina läsnä

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 määritetty, loppu on kolme vaihetta järjestyksessä: aseta PDF/A-3-tila, piirrä ihmisen luettava sivu ja kiinnitä sitten XML associated file -tiedostona. Näkyvä sivu on tavallista sisältöä. Ainoa rajoitus, joka on syytä muistaa, on se, että PDF/A kieltää upottamattomat Standard 14 -fontit, joten sivun on upotettava todellinen kirjasin sen sijaan, että se viittaisi sisäänrakennettuun kirjasimeen

Liittäminen on yksi kutsu. AddFacturXAssociatedFileFromString ottaa raa'at UTF-8 XML-tavut sekä profiilin metatiedot, kirjoittaa upotetun tiedostovirran, rekisteröi sen Catalog /AF -taulukkoon, jota PDF/A-3 vaatii, soveltaa /AFRelationship-arvoa ja luo XMP e-invoice -metatiedot, jotka tunnistavat asiakirjan Factur-X-, ZUGFeRD- tai XRechnung-laskuksi. Se myös tarkistaa, että XML:n Guideline ID vastaa pyytämääsi vaatimustenmukaisuustasoa, joten rakentamasi XML:n ja nimeämäsi profiilin välinen ristiriita saadaan kiinni eikä sitä hiljaa lähetetä 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 hienous tietopolussa on koodaus. Upotettu XML ilmoittaa encoding="UTF-8", ja menetelmä ottaa tavut muodossa AnsiString, joten ei-ASCII myyjän tai ostajan nimen on saavutettava kutsu raakoina UTF-8-oktetteina. Pelkkä valaminen (cast) järjestelmän ANSI-koodisivun kautta korruptoisi nuo merkit ja tuottaisi hiljaa laskun, jonka XML ei enää vastaa sen omaa ilmoitusta. Näyte koodaa utf-8:ksi eksplisiittisesti ennen tavujen luovuttamista, mikä on turvallinen tapa syöttää mikä tahansa tavusuuntautunut PDF-sovellusliittymä Unicode string -merkkijonosta

Sellaisten XML-tiedostojen liittämiseen, jotka eivät ole tunnistettuja sähköisen laskun profiileja, AddPDFA3AssociatedFileFromString on yleinen vastine. Se ottaa tiedostonimen, MIME-tyypin, kuvauksen, suhteen ja tavut ja kirjoittaa tavallisen PDF/A-3 associated file -tiedoston ilman laskukohtaisia metatietoja tai ohjeistuksen tarkistuksia. Käytä sitä lisätietojen saamiseksi; käytä Factur-X-menetelmää laskuille, jotta profiilin metatiedot ja ohjeistuksen vastaavuus 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 pdflibpas-pdfa-pdfua-preflight-delphi.html-artikkelin PDF/A- ja PDF/UA -preflight-läpikäynnissä ja pdflibpas-compliance-signing-workbench.html-artikkelin vaatimustenmukaisuus- ja allekirjoituspenkissä (compliance and signing workbench). Kaikki tämä toimitetaan osana PDFlibPas Delphi PDF Library -kirjastoa, yhdessä PDF/A-, taggaus- ja asiakirjaominaisuuksien API:iden kanssa, joille verkkolaskupolku perustuu