Factur-X- tai ZUGFeRD-lasku on kaksi asiakirjaa, joilla on yksi tiedostonimi. Ulompi asiakirja on PDF/A-3-säiliö, joka arkistolukijan on hyväksyttävä seuraavat kymmenen vuotta. Sisempi asiakirja on XML-lasku, joka ostajan kirjanpitojärjestelmän on jäsenneltävä standardin EN 16931 mukaisesti. Virhe, joka päästää rikkinäisiä laskuja tuotantoon, on uskoa, että ensimmäisen osan saaminen oikein antaa toisen ilmaiseksi. Se ei pidä paikkaansa. Tiedosto voi olla virheetön PDF/A-3 ja kantaa silti mukanaan XML:ää, jota mikään veroviranomainen ei hyväksy, ja se voi kantaa sisällään oppikirjamaista EN 16931 XML:ää säiliössä, joka epäonnistuu arkistovalidoinnissa. Nämä kaksi kerrosta validoidaan kahdella eri työkalulla, jotka eivät tiedä toisistaan mitään, ja todellisen tuotantoputken on tyydytettävä molemmat
Kaksi validaattoria, kaksi eri kysymystä
veraPDF on referenssitoteutus PDF/A:lle. Osoita sillä laskuun, ja se vastaa yhteen kysymykseen: onko tämä standardin mukainen PDF/A-3-tiedosto. Se tarkistaa asiat, joista ISO 19005-3 välittää. Onko jokainen fontti upotettu. Onko OutputIntent olemassa. Ilmoittaako XMP-metatieto oikean osan ja vaatimustenmukaisuustason. Verkkolaskun tapauksessa se tarkistaa myös PDF/A-3:n vaatiman liitetiedostojen kytkennän, koska XML kulkee mukana upotettuna tiedostona, jolla on /AFRelationship ja merkintä asiakirjan luettelon (document catalog) /AF-taulukossa. veraPDF ei ota kantaa siihen, täsmääkö laskun loppusumma, koska se ei kuulu sen tehtäviin
Mustang on avoimen lähdekoodin validaattori Mustangprojectista. Se kysyy toisen, ortogonaalisen kysymyksen: onko upotettu XML kelvollinen lasku. Se ajaa XML:n ilmoitetun profiilin skeemaa vasten ja soveltaa sitten EN 16931 -liiketoimintasääntöjä ja niiden päälle kerrostettuja maakohtaisia sääntöjoukkoja, mukaan lukien XRechnungin CIUS. Se tarkistaa, että myyjän ALV-tunniste (VAT identifier) on olemassa silloin kun loppusummat sitä edellyttävät, että alennus- ja kulusummat (allowance and charge amounts) täsmäävät asiakirjan loppusumman kanssa, ja että XML:n profiili-URN vastaa sitä, mitä tiedosto väittää olevansa. Mustang ei välitä siitä, upottaako ympäröivä PDF fonttinsa, koska se on veraPDF:n tehtävä
Kumpikaan työkalu ei ole toisen ylijoukko (superset). veraPDF päästää läpi rakenteellisesti täydellisen säiliön, joka ympäröi hölynpöly-XML:ää. Mustang päästää läpi täydellisen XML:n säiliössä, josta puuttuu OutputIntent. Kumpikin saa kiinni juuri sen luokan vikoja, joille toinen on sokea, mikä on koko syy siihen, miksi vakavasti otettava validointikehys ajaa molemmat ja pitää tiedostoa toimituskelpoisena vasta, kun molemmat ovat yhtä mieltä
Validointimatriisi
Todistaakseen, että kirjasto tuottaa tiedostoja, jotka selviävät molemmista porteista, testauskehys (harness) rakentaa matriisin. Kuusi laskuprofiilia kattaa sen kirjon, jonka eurooppalainen tuotantoputki kohtaa käytännössä: Factur-X EN 16931, Factur-X BASIC, Factur-X EXTENDED France B2B -variantti, XRechnung 3.0, ZUGFeRD 1.0 COMFORT ja ZUGFeRD 2.0 BASIC. Jokainen profiili luodaan kahta PDF/A-alavaatimustenmukaisuustasoa, 3b ja 3u, vasten, koska B-tason ja U-tason vaatimukset eroavat toisistaan Unicode-mäppäyksen osalta, ja tiedosto, joka läpäisee toisen, voi epäonnistua toisessa. Kuusi profiilia kertaa kaksi tasoa tekee kaksitoista tiedostoa, jokainen niistä rakennettuna ilman käyttöliittymää (headless) samalla koodipolulla, joka toimitetaan graafisen käyttöliittymän esimerkin mukana, joten testattavat artefaktit eivät ole käsin viritettyjä testiä varten
Generaattori kirjoittaa kaikki kaksitoista, ja skripti syöttää ne molemmille validaattoreille. Ensimmäisellä täydellä ajolla veraPDF päästi läpi kaikki kaksitoista. Säiliön putkisto oli kaikkialla kunnossa: liitetiedostot oli rekisteröity, XMP-vaatimustenmukaisuus oli ilmoitettu, ja output intentit olivat paikoillaan. Mustang päästi läpi kahdeksan. Neljä laskua olivat rakenteellisesti kelvollisia PDF/A-3-tiedostoja, jotka sisälsivät XML:ää, jonka liiketoimintasääntöjen validaattori hylkäsi. Tämä on juuri se jakautuma, jonka esiin nostamista varten kahden työkalun lähestymistapa on olemassa. Jos testauskehys olisi luottanut pelkästään veraPDF:ään, nuo neljä olisivat näyttäneet valmiilta
Kaksi korjausta, jotka kurouttivat umpeen kuilun
Neljä Mustang-epäonnistumista johtuivat kahdesta erillisestä syystä, ja korjaus kumpaankin on yksityiskohta, joka on syytä tietää ennen kuin luot näitä profiileja itse
Ensimmäinen koski Factur-X EXTENDED France B2B -profiilia. Alkuperäinen generaattori välitti sisäisen nimikkeen vaatimustenmukaisuustasona ja sisäisen URN:n ohjeistuksena (guideline), ja Mustang hylkäsi tiedoston antaen invalid-conformance-value -virheen, jota seurasi unsupported-profile-type -virhe. Syynä tähän on se, että XMP:n fx:ConformanceLevel -kenttä ei ole vapaan tekstin paikka omalle profiilinimikkeistöllesi. Factur-X määrittelee sille tasan viisi standardiarvoa: MINIMUM, BASIC WL, BASIC, EN 16931 ja EXTENDED. Ranskakohtainen B2B-lasku on yhä EXTENDED-profiilin asiakirja XMP-metatietojen näkökulmasta. Laskun ranskalaista luonnetta ei ilmaista keksimällä kuudes vaatimustenmukaisuusarvo. Se ilmaistaan maakoodilla FR sekä XML:n sisällä olevalla ohjeistuksen tunnisteella (guideline identifier), jonka on sisällettävä urn:cen.eu:en16931:2017#conformant# -etuliite (prefix), joka merkitsee EN 16931 -yhteensopivan CIUS:n. Standardin EXTENDED-arvon välittäminen maakoodilla FR ja oikealla ohjeistus-URN:llä teki tiedostosta standardin mukaisen
Kirjaston ohjelmointirajapinnassa (API) tämä vastaa kutsua AddFacturXAssociatedFileFromString, jossa vaatimustenmukaisuus, maa ja ohjeistus ovat linjassa keskenään. Vaatimustenmukaisuustaso-argumentti (conformance level argument) sisältää standardin tunnisteen (token), maakoodi-argumentti sisältää arvon FR, ja ohjeistus-URN asuu välittämissäsi XML-tavuissa
var
FileID: Integer;
begin
PDF.SetPDFAMode(5); // PDF/A-3b
PDF.NewDocument;
// ... draw the human-readable invoice page ...
// ExtendedXML carries an EN 16931 guideline URN of the form
// urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended
FileID := PDF.AddFacturXAssociatedFileFromString(
ExtendedXML,
'EXTENDED', // standard fx:ConformanceLevel, not an internal label
'factur-x.xml',
'Factur-X EXTENDED invoice',
'Alternative', // /AFRelationship
'1.0',
'FR'); // France B2B marked by country code, not by conformance
if FileID = 0 then
raise Exception.Create('Factur-X attachment rejected');
PDF.SaveToFile('02_Factur-X-EXTENDED-FR_PDFA-3b.pdf');
end;
Toinen syy liittyi ZUGFeRD 1.0 COMFORT -profiiliin, eikä sillä ollut mitään tekemistä metatietojen kanssa. ZUGFeRD 1.0 validoidaan :1p0 XSD:tä vasten, joka on kardinaliteetin suhteen tiukempi kuin sanalliset yhteenvedot antavat ymmärtää. XSD edellyttää, että otsikkotason summan, ram:SpecifiedTradeSettlementMonetarySummation, on sisällettävä ram:ChargeTotalAmount ja ram:AllowanceTotalAmount kumpikin tasan kerran. Luotu XML jätti molemmat pois, joten Mustang ilmoitti, että elementtien on esiinnyttävä tasan kerran. Nämä eivät ole valinnaisia silloin kun skeema sanoo minOccurs on yksi. Näiden molempien emittointi XSD:n sekvenssijärjestyksessä heti elementin ram:LineTotalAmount jälkeen arvolla 0.00 (silloin kun kuluja tai alennuksia ei ole), tyydytti skeeman vaatimukset. Nolla on olemassa oleva elementti; puuttuva elementti on skeeman rikkomus. Näiden kahden korjauksen ollessa paikoillaan, matriisi ylsi Mustangissa lukuun kaksitoista kahdestatoista ja pysyi luvussa kaksitoista kahdestatoista veraPDF:ssä
XRechnung-kentät, jotka kääntävät virheellisen kelvolliseksi
XRechnung ansaitsee oman mainintansa, koska sen saksalainen CIUS lisää liiketoimintasääntöjä, jotka puuttuvat perus-EN 16931 -joukosta, ja ne epäonnistuvat tavoilla, jotka saavat asiakirjan näyttämään ensi silmäyksellä virheettömältä. Kaksi niistä koskee sähköisiä osoitteita (electronic addresses). BT-34 on myyjän sähköinen osoite ja BT-49 on ostajan sähköinen osoite, reitityksen päätepisteet, joita Saksan julkisen sektorin portaali käyttää laskun toimittamiseen ja vastaanottokuittaukseen. Perus-EN 16931 -malli käsittelee niitä valinnaisina. XRechnung ei. Jos jätät kumman tahansa pois, lasku on oikein muodostettu, skeeman mukainen, ja silti hylätty
Kolmas on sääntö BR-DE-6, joka edellyttää, että myyjän yhteyshenkilön puhelinnumero on olemassa. Se on tyypillinen kenttä, jonka kehittäjä jättää pois, koska se tuntuu pikemminkin esitystavalta kuin datalta, ja sen puuttuminen tuottaa validointivirheen, joka viittaa myyjän yhteystietoryhmään (seller contact group) ennemmin kuin mihinkään ilmeisesti puuttuvaan. BT-34:n, BT-49:n ja myyjän puhelinnumeron toimittaminen on se, mikä siirtää XRechnung-tiedoston epäkelvosta kelvolliseksi Mustangissa, eikä mikään siitä muuta mitään, minkä veraPDF näkee, koska kaikki kolme elävät XML:ssä
Kirjaston tulosteen kytkeminen validaattoriin
Testauskehyksen (harness) takana oleva arkkitehtuurinen pointti on yleistettävissä mihin tahansa liiketoimintajärjestelmään. PDF-kirjasto kirjoittaa standardin mukaisen säiliön ja upottaa XML:n. Sen ei pidä, eikä sen tulisikaan yrittää, olla EN 16931 -liiketoimintasääntöjen auktoriteetti. Kirjaston funktio ValidateFacturXInvoice tarkistaa säiliön johdonmukaisuuden: sen, että luettelon (catalog) /AF-taulukko, upotettujen tiedostojen nimipuu (name tree), XMP DocumentFileName, profiili, ohjeistus ja /AFRelationship ovat kaikki yhtä mieltä keskenään. Se ei kuitenkaan validoi verokoodeja tai täsmäytä summia. Oikea työnjako on se, että liiketoimintajärjestelmä poimii XML:n ja luovuttaa sen erilliselle laskuvalidaattoreille aivan samalla tavalla kuin testauskehys luovuttaa sen Mustangille
Tiedoston lukeminen takaisin kertoo sinulle, mitä tosiasiallisesti kirjoitettiin. DetectFacturXInvoice raportoi, tunnistettiinko lasku, ja GetFacturXInvoiceInfo lukee metatietokentät tunnisteen (tag) perusteella: tunniste 1 on upotetun tiedoston nimi, tunniste 2 on XMP:n DocumentFileName, tunniste 5 on vaatimustenmukaisuustaso (conformance level), tunniste 6 on ohjeistuksen tunniste (guideline identifier) ja tunniste 7 on /AFRelationship. Sen vahvistaminen, että takaisin lukemasi vaatimustenmukaisuustaso on standardin mukainen tunniste (standard token) eikä sisäinen nimike, on halvin tapa saada EXTENDED-virhe kiinni ennen kuin tiedosto poistuu buildistasi
function ExtractAndInspect(const PdfPath: string): AnsiString;
var
Profile, Guideline: WideString;
begin
Result := '';
PDF.LoadFromFile(PdfPath);
if PDF.DetectFacturXInvoice = 1 then
begin
Profile := PDF.GetFacturXInvoiceInfo(5); // fx:ConformanceLevel
Guideline := PDF.GetFacturXInvoiceInfo(6); // XML guideline ID
Writeln('Profile: ', Profile);
Writeln('Guideline: ', Guideline);
// Hand the raw XML to a dedicated EN 16931 / Mustang validator.
Result := PDF.ExtractFacturXXMLToString;
end;
end;
ExtractFacturXXMLToString palauttaa raa'at XML-tavut muodossa AnsiString, valmiina kirjoitettavaksi tiedostoon tai striimattavaksi validaattoriprosessiin. Testauskehyksessä tuo kohde on Mustang, jota kutsutaan sen komentorivin jar-tiedoston kautta, samalla kun veraPDF ajetaan samalla kierroksella saman tiedoston yli. Kytkentä on pieni: konsoligeneraattori, EInvoiceValidation.dpr, kirjoittaa nämä kaksitoista tiedostoa käyttäen jaettua laskumallia (shared invoice model) esimerkistä, ja skripti, run-validation.ps1, ajaa molemmat validaattorit tulostehakemiston läpi ja tulostaa hyväksytyt (pass) ja hylätyt (fail) -taulukon. Tämä sama kaksivaiheinen muoto – generoi kirjaston avulla ja todenna ulkoisilla validaattoreilla – on se, mitä jatkuvan integroinnin työn (continuous-integration job) tulisi ajaa aina, kun laskujen luontiin tehdään muutoksia, koska ainoa tapa tietää, tyydyttääkö tiedosto molemmat kerrokset, on kysyä molemmilta työkaluilta
Jos tuotantoputkesi on myös sertifioitava säiliö ennen sen allekirjoittamista, tämän työn preflight-puoli käsitellään artikkelissa läpikäyntimme PDF/A:n ja PDF/UA:n preflight-tarkistuksista Delphissä, ja laajempi sertifioi-ja-allekirjoita (certify-then-sign) -kulku on kuvattu artikkelissa vaatimustenmukaisuus- ja allekirjoitustyöpenkki. Molemmat rakentuvat sille samalle luontipolulle, joka toimitetaan osana Delphi PDF -kirjastoa Delphille ja C++Builderille, yhdessä tässä käytettyjen PDF/A:n, liitetiedostojen ja metatietojen ohjelmointirajapintojen kanssa