HotPDF luo, tunnista ja validoi ZUGFeRD- ja Factur-X-verkkolaskut Delphistä ja C++Builderistä. AddFacturXAssociatedFile upottaa EN 16931 -laskun XML-tiedoston PDF/A-3-säiliöön, DetectFacturXInvoice tunnistaa saapuvan yhdistelmälaskun (hybrid invoice) ja HPDFValidateEInvoice suorittaa viralliset EN 16931 Schematron -liiketoimintasäännöt upotettua XML-tiedostoa vasten. Tässä artikkelissa käydään läpi kaikki kolme vaihetta ja kerrotaan rehellisesti, mihin validointimoottorin rajat päättyvät
Tämän ominaisuussarjan taustalla oleva paine on lakisääteistä, ei teknistä. Saksa ja Ranska ottavat vaiheittain käyttöön pakollisen strukturoidun sähköisen laskutuksen kotimaisissa B2B-tapahtumissa, ja molemmat vaatimukset kohtaavat samassa yhdistelmämallissa: ERP-järjestelmäsi jo tuottaman PDF-laskun on sisällettävä konekielinen XML-kaksoiskappale. Delphi-kirjanpito- tai ERP-sovelluksella, joka nykyään tuottaa tavallisia PDF-laskuja, on tiukka määräaika alkaa tuottaa Factur-X- tai ZUGFeRD-laskuja, ja erot näiden kahden nimen välillä ovat pienempiä kuin markkinointi antaa ymmärtää — Factur-X ja ZUGFeRD ovat sama ranskalais-saksalainen standardi, joka on julkaistu kahdella eri nimellä
Mikä tekee PDF-tiedostosta kelvollisen Factur-X-laskun?
Kelvollinen Factur-X-lasku on PDF/A-3-tiedosto (ISO 19005-3), joka upottaa täsmälleen yhden strukturoidun laskun XML-tiedoston liitetyksi tiedostoksi (associated file), linkittää sen asiakirjan katalogista ja ilmoittaa sen XMP-laajennuksen metatiedoissa (XMP extension metadata). Kolmen kerroksen on oltava yhtä mieltä. Ensinnäkin säiliön on vastattava PDF/A-3-standardia, eli arkistointiprofiilia, joka — toisin kuin PDF/A-1 ja PDF/A-2 — sallii minkä tahansa tyyppisten tiedostojen upottamisen. Toiseksi laskun XML:n, nimeltään factur-x.xml standardiprofiileille tai xrechnung.xml saksalaiselle XRechnung-variantille, on oltava rekisteröitynä katalogin /AF-taulukkoon ja /Names /EmbeddedFiles -puuhun suhteella AFRelationship, joka ilmoittaa liitteen tyypin: Alternative tarkoittaa, että XML on vastaava esitysmuoto näkyvästä laskusta, mikä on useimpien profiilien vaatima semantiikka. Kolmanneksi Factur-X:n XMP-laajennusskeeman on nimettävä upotettu tiedosto, sen vaatimustenmukaisuustaso (conformance level) ja asiakirjatyyppi, jotta vastaanottava järjestelmä voi luokitella laskun lukematta itse XML-tietoa lainkaan
Itse XML-sisältö noudattaa EN 16931 -semanttista mallia, eli eurooppalaista standardia, joka määrittelee ydinlaskun: ostajan, myyjän, laskurivit, veroerittelyn ja maksettavan summan, kukin varustettuna liiketoimintatermin numerolla (esimerkiksi BT-1 laskun numerolle, BT-5 valuutalle jne.). EN 16931 -validointi tapahtuukin kahdella eri tasolla. XML-skeeman validointi varmistaa, että asiakirja on oikein muotoiltu CII oikeilla elementtien nimillä. Schematron-liiketoimintasäännöt tarkistavat, että sisältö on johdonmukaista — esimerkiksi että sääntö BR-CO-15 pätee ja laskun loppusumma todella vastaa rivien nettosummien ja arvonlisäveron summaa, ja että pakolliset termit ovat mukana ilmoitetussa profiilissa. Tiedosto voi läpäistä skeemavalidoinnin ja olla silti virheellinen lasku, minkä vuoksi liiketoimintasääntökerros on olemassa
Miksi ZUGFeRD 2.5 ei vaadi uutta säiliötä
ZUGFeRD 2.5 ei muuta mitään PDF-säiliötasolla: ohjeistuksen URN:t, XMP-skeema ja versiotunnus (version token) ovat identtisiä ZUGFeRD 2.3 / Factur-X 1.0 -versioiden kanssa. Tämä yllättää useimmat toteuttajat, joten on syytä lainata lähdettä. Factur-X 1.09 -spesifikaatio ilmoittaa, että PDF/A-laajennusskeeman URI-osoitteessa oleva versionumero ei liity XML-tietomäärittelyn versionumeroon, ja että Factur-X-laskujen versiointi säilyy arvossa 1.0 — sekä XMP:n fx:Version-kohdassa että factur-x.xml:n kohdassa BT-24. Virallinen ZUGFeRD 2.5 -esimerkkikokoelma vahvistaa tämän: jokainen näyte upottaa factur-x.xml-tiedoston urn:factur-x.eu:1p0-ohje-URN:n alle, jossa fx:Version on 1.0. Se, mitä 2.5 todellisuudessa lisää, sijaitsee XML-tietomallin sisällä — uusia elementtejä, kuten alilaskurivejä ja erätunnisteita — mikä on sovelluksesi tuottama liiketoimintakerros, ei PDF-kirjaston hallitsema säiliökerros
HotPDF koodaa tämän suoraan. AddFacturXAssociatedFile ottaa vastaan Version-argumentin ja normalisoi sen HPDFFacturXNormalizeVersion-funktion avulla ennen XMP:n kirjoittamista, joten 2p5-aliaksen välittävä kutsuja tuottaa silti spesifikaation mukaisen tunnuksen sen sijaan, että keksisit uuden 2p5 XMP-version, jota mikään validaattori ei hyväksyisi. Tunnistustyökalut käsittelevät 2p5-arvoa vastaavana sitä luettaessa, joten edestakaiset prosessit säilyvät symmetrisinä. Jos jokin kirjasto tai työkalu väittää kirjoittavansa erityisen ZUGFeRD 2.5 -säiliön, spesifikaatio itse sanoo, ettei sellaista ole olemassa
Laskun XML-upottaminen Delphistä
THotPDF.AddFacturXAssociatedFile muuttaa tavallisen PDF/A-3-luontiajon Factur-X-laskuksi yhdellä kutsulla: se upottaa XML-tavut liitetyksi tiedostoksi, rekisteröi factur-x.xml-tiedoston katalogin /AF-taulukkoon ja /Names /EmbeddedFiles -puuhun ja tuottaa vastaavan Factur-X XMP -laajennuksen metatiedon. Piirrät ihmisen luettavan laskusivun tavallisella HotPDF canvas-rajapinnalla; säiliötyö on vain yksi rivi BeginDoc- ja EndDoc-kutsujen välissä
var
Pdf: THotPDF;
InvoiceXML, UBLXML: TBytes;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'invoice-2026-0042.pdf';
Pdf.PDFACompliance := '3B'; // PDF/A-3b-säiliö
Pdf.BeginDoc;
// embed the EN 16931 CII invoice XML as factur-x.xml
InvoiceXML := BuildInvoiceXML; // liiketoimintakerroksesi tuottama
Pdf.AddFacturXAssociatedFile(InvoiceXML, 'EN 16931');
// optional: attach a UBL view as a supplementary representation
UBLXML := BuildUBLXML;
Pdf.AddUBLSupplementaryFile(UBLXML); // factur-xubl.xml, Alternative
// ... draw the visible invoice page here ...
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
Oletukset noudattavat spesifikaatiota: tiedostonimi factur-x.xml, suhde Alternative, versio 1.0. AddUBLSupplementaryFile toteuttaa Factur-X 1.09 -spesifikaation kohdan 6.4, joka sallii saman laskun UBL-esityksen liittämisen toisena liitteenä nimellä factur-xubl.xml MIME-tyypillä application/xml ja suhteella Alternative. Järjestys on tässä tärkeä, ja HotPDF valvoo spesifikaation asettamaa rajoitusta: UBL-tiedosto on täydentävä näkymä, ei koskaan ensisijainen lasku, joten se on lisättävä vasta sen jälkeen, kun AddFacturXAssociatedFile on rekisteröinyt CII-päätiedoston. Huomaa, että piirretyn sivun ja upotetun XML:n on ilmoitettava sama lasku — PDF, joka näyttää yhden summan ja koodaa toisen, on juuri se virhetila, jota yhdistelmälaskutus on suunniteltu estämään. Tämän työnkulun PDF/A-3-puolta, mukaan lukien OutputIntent ja fonttien upottaminen, käsitellään tarkemmin aiheeseen liittyvässä artikkelissa PDF/A-, PDF/X- ja PDF/UA-validoinnista Delphissä
Miten Factur-X-lasku havaitaan saapuvasta PDF-tiedostosta?
THotPDF.DetectFacturXInvoice vastaa säännöksen vastaanottavasta puolesta: lataa mikä tahansa PDF ja selvitä yhdellä kutsulla, onko kyseessä yhdistelmälaskusivu, minkä profiilin se ilmoittaa ja mitä liitteitä se kantaa. Funktio palauttaa THPDFFacturXInvoiceInfo-tietueen, joka sisältää upotetun XML-tiedostonimen, XMP-ilmoitetun tiedostonimen ja -tyypin, versiotunnuksen, vaatimustenmukaisuustason, ohje-URN:n ja AFRelationship-arvon. Kaksi muuta kenttää raportoi täydentäviä näkymiä: UBLFileName täytetään, kun factur-xubl.xml-liite on mukana, ja EDIFACTFileName kun EDIFACT-näkymä on mukana — kumpikaan ei vaikuta tunnistustulokseen, sillä täydentävää esitystä ei voi olla olemassa ilman ensisijaista CII-laskua
var
Reader: THotPDF;
Info: THPDFFacturXInvoiceInfo;
begin
Reader := THotPDF.Create(nil);
try
if Reader.LoadFromFile('incoming-invoice.pdf') <= 0 then
raise Exception.Create('No pages loaded.');
if Reader.DetectFacturXInvoice(Info) then
begin
Writeln('Invoice XML: ', string(Info.FileName));
Writeln('Conformance: ', string(Info.ConformanceLevel));
Writeln('Guideline URN: ', string(Info.GuidelineID));
Writeln('Relationship: ', string(Info.AFRelationship));
if Info.UBLFileName <> '' then
Writeln('UBL view: ', string(Info.UBLFileName));
end
else
Writeln('Not a Factur-X / ZUGFeRD hybrid invoice.');
finally
Reader.Free;
end;
end;
Tunnistus lukee samat signaalit, jotka vaatimustenmukainen validaattori lukee — eli XMP-laajennusskeeman ja liitostiedostorekisterin — pelkän tiedostonimen arvaamisen sijaan. Sama ladattu asiakirja-olio paljastaa myös XMP- ja Info-sanakirjan metatiedot tarkasteltaviksi tai korjattaviksi, työnkulku jota kuvaillaan ladattujen PDF-asiakirjojen metatietojen muokkaamista käsittelevässä artikkelissa
Liiketoimintasäännöt vai skeema: mitä EN 16931 -validointi tarkoittaa?
Erillinen funktio HPDFValidateEInvoice yksikössä HPDFEInvoiceValidator ajaa molemmat validointikerrokset peräkkäin: ensin säiliörakenteen tarkistuksen ValidateFacturXInvoice-metodilla ja sen jälkeen EN 16931 Schematron -liiketoimintasäännöt purettua laskun XML-tiedostoa vasten. HotPDF toimittaa viisi virallista Factur-X 1.09 -sääntötiedostoa — MINIMUM, BASIC WL, BASIC, EN 16931 ja EXTENDED — kansioon Lib/resources/Schematron/, ja HPDFSchematronFileForProfile valitsee havaitun profiilin mukaisen sääntötiedoston. Siten EN 16931 -lasku tarkistetaan automaattisesti tiedostoa Factur-X_1.09_EN16931.sch vasten. Funktio palauttaa arvon True vain, kun säiliö on kelvollinen eikä mikään liiketoimintasääntö lauennut
uses HPDFEInvoiceValidator;
var
Reader: THotPDF;
Report: THPDFEInvoiceValidationReport;
I: Integer;
begin
Reader := THotPDF.Create(nil);
try
Reader.LoadFromFile('incoming-invoice.pdf');
if HPDFValidateEInvoice(Reader, SchematronDir, Report) then
Writeln('Container and business rules: OK')
else
Writeln('Validation found issues.');
if Report.BusinessRulesEvaluated then
begin
Writeln(Report.BusinessSummary.Evaluated, ' rules evaluated, ',
Report.BusinessSummary.Skipped, ' skipped (XPath 2.0)');
for I := 0 to High(Report.BusinessRules) do
if (not Report.BusinessRules[I].Skipped) and
(Report.BusinessRules[I].Severity = stsError) then
Writeln(string(Report.BusinessRules[I].RuleID), ': ',
string(Report.BusinessRules[I].Message));
end;
finally
Reader.Free;
end;
end;
Jokainen merkintä BusinessRules-luettelossa kantaa mukanaan säännön tunnuksen purettuna väitetekstistä (assertion text) — kuten BR-45, BR-CO-17 ja muu EN 16931 -nimeämiskäytäntö — sekä säännön kontekstin XPath-polun, testilausekkeen ja Schematronin mukaisen vakavuusasteen. Tämä tekee raportista käytännöllisen: pelkän hyväksytty/hylätty-tiedon sijaan saat tarkan liiketoimintatermin, joka on ristiriitainen, mikä on juuri sitä, mitä tukitiimisi tarvitsee asiakkaan kysyessä syytä laskun hylkäämiseen. Tiimit, jotka jo tuottavat strukturoituja vaatimustenmukaisuusraportteja arkisto-PDF-tiedostoille, voivat yhdistää nämä tulokset samaan putkeen, jota kuvaillaan artikkelissa PDF-esitarkastusraporttien automatisoinnista
Mihin validaattori todella pysähtyy
Schematron-moottori perustuu MSXML-kirjastoon, joka suorittaa XPath 1.0 -hakuja. EN 16931 -sääntötiedosto ilmoittaa queryBinding="xslt2" ja sisältää 424 väitettä (assertions), joista useimmat käyttävät vain XPath 1.0 -funktioita kuten string-length ja substring-after — nämä suoritetaan natiivisti. Vähemmistö tukeutuu XPath 2.0 -rakenteisiin, kuten xs:decimal-tyyppimuunnoksiin tai exists()-funktioon; HotPDF esiskannaa jokaisen testilausekkeen tunnettujen XPath 2.0 -tunnusten varalta ja merkitsee kyseiset säännöt tilaan Skipped vakavuusasteella info sen sijaan, että yritettäisiin laskentaa, joka epäonnistuisi hiljaa. Yhteenvetotietue raportoi arvioidut ja ohitetut määrät erikseen, joten lokisi kertoo aina tarkasti, kuinka suuri osa säännöistä suoritettiin. Delphi 7 -ympäristössä, jossa MSXML-sidokset eivät ole käytettävissä, yksikkö kääntää tynkäversion (stub), joka ilmoittaa moottorin olevan poissa käytöstä vaikuttamatta säiliön tarkistustulokseen; täysi moottori vaatii Delphi XE2 -version tai uudemman
Toinen rajoitus on vieläkin tärkeämpi. Vihreä tulos HPDFValidateEInvoice-kutsusta tarkoittaa, että säiliörakenne on kunnossa eikä mikään arvioitu sääntö lauennut — se ei ole verolainsäädännön vaatimustenmukaisuuden hyväksyntä. Kansalliset laajennussäännöt, vastaanottajakohtaiset vaatimukset ja laskun sisältöön liittyvät lakisääteiset velvoitteet sijaitsevat EN 16931 -ytimen yläpuolella ja minkä tahansa kirjaston ulkopuolella. Käsittele validaattoria porttina, joka pitää rakenteellisesti rikkinäiset laskut poissa lähtevistä laskuistasi ja liputtaa epäjohdonmukaiset laskut saapuvissa, aja täysi XPath 2.0 -validaattori julkaisuputkessasi sertifioinnin sitä vaatiessa ja anna verokonsulttien vastata varsinaiseen lainsäädännön vaatimustenmukaisuuskysymykseen. Tässä esitetyt upotus-, tunnistus- ja validointirajapinnat ovat osa standardia HotPDF Component -tuotetta Delphille ja C++Builderille mukana täydellinen verkkolaskuesimerkki, joka kattaa kaikki seitsemän ZUGFeRD- ja Factur-X-profiilia