Tehnički članak

Validacija e-računa (e-invoices): veraPDF i Mustang u Delphiju

Jedan Factur-X ili ZUGFeRD račun (invoice) su zapravo dva dokumenta koja nose jedno te isto ime datoteke (filename). Vanjski dokument (outer document) jest PDF/A-3 kontejner kojeg neki arhivski čitač (archival reader) mora prihvaćati sljedećih deset godina. Unutarnji pak dokument jest jedan XML račun kojeg računovodstveni sustav samog kupca mora parsirati (parse) naspram EN 16931. Pogreška koja isporučuje pokvarene (broken) račune u samu produkciju jest vjerovanje da kada pogodite s onim prvim da onda dobijete točno i ono drugo, i to sasvim besplatno. Ali to nije tako. Datoteka može biti i besprijekoran PDF/A-3 ali još uvijek nositi XML koji neće prihvatiti niti jedno porezno tijelo (tax authority), a ona može također nositi i savršeni EN 16931 XML iz udžbenika unutar nekog kontejnera koji naprosto pada (fails) na arhivskoj validaciji. Ta dva sloja validiraju se pomoću dva različita alata koji jedan o drugom ne znaju baš ništa, dok jedan stvarni cjevovod (pipeline) mora zadovoljiti njih oba

Dva validatora, dva različita pitanja

veraPDF jest sama referentna implementacija (reference implementation) za PDF/A. Uperite ga u jedan račun i on odgovara na jedno pitanje: jest li to jedna sukladna (conformant) PDF/A-3 datoteka. On provjerava stvari do kojih je ISO 19005-3 stalo. Je li svaki font ugrađen (embedded). Postoji li OutputIntent. Deklariraju li se u XMP metapodacima onaj ispravan dio (part) kao i razina sukladnosti (conformance level). Za jedan e-račun (e-invoice) on nadalje provjerava još i sve instalacije pridružene datoteke (associated-file plumbing) koje PDF/A-3 i zahtijeva, a to je zato što se XML vozi usput (rides along) kao jedna ugrađena (embedded) datoteka koja pak ima i /AFRelationship uz jedan unos u samom /AF nizu (array) dokument kataloga (document catalog). veraPDF pak ne kaže baš ništa o tome zbraja li se ukupan iznos na računu, zato što mu to uopće nije u nadležnosti (remit)

Mustang je open-source validator iz Mustangprojecta. On postavlja ortogonalno pitanje: jest li taj ugrađeni (embedded) XML jedan validni račun. On pokreće taj XML naspram sheme za taj deklarirani profil te zatim primjenjuje EN 16931 poslovna pravila i setove pravila specifične za državu slojevito poslagane povrh (layered on top), a XRechnungov CIUS je među njima. On provjerava je li PDV identifikator prodavatelja prisutan kada ga ukupni iznosi zahtijevaju, jesu li iznosi popusta (allowance) i naknada (charge) izravnani (reconcile) naspram ukupnog iznosa dokumenta, poklapa li se URN profila u XML-u s onim što ta datoteka tvrdi da jest. Mustangu uopće nije stalo do toga ugrađuje li okružujući PDF svoje fontove, zato što je to veraPDF-ov posao

Niti jedan od ova dva alata nije nadskup (superset) onog drugog. veraPDF pušta (passes) jedan strukturalno savršeni kontejner oko nekog sasvim besmislenog (nonsense) XML-a. Mustang pušta (passes) savršeni XML omotan (wrapped) u jednom kontejneru sa nedostajućim OutputIntent. Svaki od njih hvata točno onu klasu defekata (defect) na koju je onaj drugi slijep (blind), a to je i čitavi onaj razlog zašto neki ozbiljni uređaj za validaciju (validation harness) pokreće oba i jednu datoteku tretira spremnom za isporuku (shippable) tek onda kada se ta oba alata s time slože (agree)

Matrica validacije

Da bi dokazao da biblioteka proizvodi datoteke koje preživljavaju oba vrata (gates), uređaj za validaciju (harness) gradi jednu matricu. Šest profila računa pokrivaju onaj raspon s kojim se neki europski cjevovod susreće u praksi: Factur-X EN 16931, Factur-X BASIC, Factur-X EXTENDED France B2B varijanta, XRechnung 3.0, ZUGFeRD 1.0 COMFORT, i ZUGFeRD 2.0 BASIC. Svaki se profil generira naspram dvije razine pod-sukladnosti (sub-conformance levels) za PDF/A, 3b i 3u, zato što se zahtjevi razine B i razine U razilaze (diverge) oko Unicode mapiranja a jedna datoteka koja prođe jednu može pasti (fail) na onoj drugoj. Šest profila puta dvije razine jest dvanaest datoteka, a svaka od njih izgrađena je headless pomoću istog onog puta koda koji se isporučuje (ships) u GUI primjeru (sample), tako da artefakti koji se testiraju nisu ručno podešavani (hand-tuned) isključivo za taj test

Generator zapisuje svih dvanaest a jedna skripta hrani (feeds) sa svakom ponaosob u oba validatora. Kod onog prvog punog pokretanja veraPDF je pustio (passed) svih dvanaest. Instalacije (plumbing) kontejnera bile su ispravne posvuda naokolo: pridružene datoteke (associated files) su bile registrirane, XMP sukladnost (conformance) deklarirana, namjere izlaza (output intents) sve redom na svom mjestu. Mustang je pustio (passed) njih osam. Četiri računa bila su pak strukturalno validne PDF/A-3 datoteke ali su one nosile XML kojeg je onaj validator za poslovna pravila jednostavno odbacio, što je zapravo točno onaj isti onaj raskol (split) kojeg ovaj pristup sa dva alata i treba izbaciti na površinu (to surface). Da je uređaj vjerovao samo veraPDF-u i ničemu drugome, ta četiri izgledala bi sasvim dovršena

Dva popravka (fixes) koja su zatvorila procjep

Ta četiri Mustangova pada (failures) proizašla su iz dva različita uzroka (causes), a popravak (fix) za svaki od njih jest jedan detalj vrijedan saznanja prije nego što i sami počnete generirati te profile

Onaj prvi bio je Factur-X EXTENDED France B2B profil. Originalni generator proslijedio je jednu internu labelu kao onu razinu sukladnosti (conformance level) i jedan interni URN kao samu smjernicu (guideline), a Mustang je onda tu datoteku odbacio uz pogrešku invalid-conformance-value po kojoj je uslijedila i ona o unsupported-profile-type. Razlog leži u tome što XMP fx:ConformanceLevel polje nije samo jedan slot slobodnog teksta (free-text) za neko vaše vlastito imenovanje profila. Factur-X definira za nju točno pet standardnih vrijednosti: MINIMUM, BASIC WL, BASIC, EN 16931, i EXTENDED. Neki specifično francuski B2B račun jest zapravo još uvijek jedan dokument EXTENDED-profila što se samih XMP metapodataka tiče. Onaj francuski karakter tog računa se ne izražava smišljanjem neke šeste vrijednosti za sukladnost. On se izražava pomoću koda države, FR, i pomoću onog identifikatora smjernice (guideline identifier) koji se nalazi unutar XML-a, a koji pak mora nositi onaj prefiks urn:cen.eu:en16931:2017#conformant# koji zapravo označava da je neki CIUS usklađen (conformant) s EN 16931. Prosljeđivanjem ove standardne EXTENDED vrijednosti uz FR kao kod same države kao i uz ispravan URN te smjernice ta je datoteka tada postala sasvim usklađena (conformant)

U bibliotečnom API-ju to je u biti jedan poziv za AddFacturXAssociatedFileFromString uz međusobno poravnate (aligned) sukladnost (conformance), državu (country), i smjernicu (guideline). Argument razine sukladnosti (conformance level argument) nosi jedan standardni token, dok onaj argument koda države (country code) nosi FR, a URN smjernice pak prebiva u samim onim XML bajtovima koje vi prosljeđujete

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;

Onaj drugi uzrok bio je ZUGFeRD 1.0 COMFORT profil, a on nije imao apsolutno nikakve veze s metapodacima. ZUGFeRD 1.0 se validira naspram onog :1p0 XSD-a, koji je stroži (stricter) kada je riječ o kardinalnosti (cardinality) nego što li to ti sažeci pisani u prozi (prose summaries) uopće znaju sugerirati. Ovaj XSD zapravo zahtijeva da ono zaglavlje zbroja podmirenja (header settlement summation), ram:SpecifiedTradeSettlementMonetarySummation, sadrži ram:ChargeTotalAmount i ram:AllowanceTotalAmount pri čemu svaki točno onako po jedanput. Taj generirani XML je pak ta oba bio izostavio, pa je onda Mustang s tim u vezi prijavio da se svaki od tih elemenata mora pojaviti točno po jedan put. Oni pak uopće i nisu opcionalni tamo gdje shema kaže da minOccurs jest upravo jedan. Emitiranje ta oba redoslijedom te XSD sekvence (sequence order), neposredno odmah nakon ram:LineTotalAmount, a sa vrijednošću 0.00 onda kada nema nikakvih naknada niti popusta, to sve je itekako uspješno zadovoljilo dotičnu shemu. Nula jest samo jedan prisutni (present) element; neki odsutni (absent) element predstavlja s druge strane kršenje same sheme (schema violation). S ta dva popravka onda prisutna na svome mjestu čitava se ova matrica popela na onih dvanaest od dvanaest u pogledu na Mustang i to pri tome sve i dalje ostavši dvanaest od dvanaest kod samog veraPDF-a

XRechnung polja koja okreću nevalidno u ono validno

XRechnung zaslužuje vlastitu napomenu zato što njegov njemački CIUS tu onda dodaje i takva neka poslovna pravila koja su pak sasvim odsutna (absent) iz onog osnovnog EN 16931 seta, a ona znaju i padati (fail) na način koji tako nekako na prvi pogled (at a glance) izgleda kao da sa tim dokumentom baš ništa niti nije pogrešno. Dva se pak od njih tiču samih elektroničkih adresa. BT-34 predstavlja elektroničku adresu onog prodavatelja a BT-49 jest pak ona elektronička adresa kod samog onog kupca, a oni su zapravo krajnje točke usmjeravanja (routing endpoints) koje kakav njemački portal u onom javnom sektoru i inače koristi kako bi isporučio pa usto onda i potvrdio kakav takav račun (acknowledge the invoice). Onaj osnovni EN 16931 model sasvim pak njih tretira kao tek sasvim puko opcionalne. S druge pak strane, XRechnung zasigurno nipošto to baš ne radi. Izostavite tu makar i ono bilo koje od njih i takav taj račun jest pak doduše tada i dobro formiran (well-formed), pa također onda usput i schema-valid, ali isto tako na kraju i naprosto onako sasvim do kraja glatko odbačen (rejected)

Treće je pravilo BR-DE-6, koje zahtijeva prisutnost kontakt broja telefona samog prodavatelja. To je ona vrsta polja koju programer drage volje izostavi zato jer više podsjeća na neku prezentaciju nego li na podatke, a njegova odsutnost stvara neuspjeh validacije (validation failure) koji ukazuje prema grupi kontakata prodavatelja umjesto na nešto što očito nedostaje (obviously missing). Opskrbljivanje (Supplying) s BT-34, BT-49, i telefonskim brojem prodavatelja jest ono što pomiče XRechnung datoteku od nevalidne prema validnoj pod Mustangom, a ništa od toga ne mijenja ništa od onoga što veraPDF vidi, zato što sva tri prebivaju u XML-u

Povezivanje (wiring) izlaza iz biblioteke na neki validator

Arhitektonska poanta iza ovog uređaja (harness) može se generalizirati na bilo koji poslovni sustav. PDF biblioteka zapisuje jedan sukladan kontejner (conformant container) i ugrađuje XML. Ona ne pokušava, i ne bi ni smjela pokušavati, biti autoritet za EN 16931 poslovna pravila. ValidateFacturXInvoice u biblioteci provjerava dosljednost kontejnera (container consistency), provjeravajući slažu li se /AF niz (array) iz kataloga, drvo naziva ugrađenih datoteka (embedded-files name tree), XMP DocumentFileName, profil, smjernica, te /AFRelationship, ali ona ne validira porezne kodove niti izravnava (reconcile) iznose. Prava podjela rada (division of labor) jest ta da poslovni sustav treba izvući XML i predati ga jednom dediciranom validatoru za račune, točno onako kako ga uređaj (harness) predaje Mustangu

Čitanje datoteke natrag kazuje vam što je zapravo zapisano. DetectFacturXInvoice prijavljuje je li račun bio prepoznat, a GetFacturXInvoiceInfo čita ta polja metapodataka po tagu (tag): tag 1 je naziv ugrađene datoteke, tag 2 XMP DocumentFileName, tag 5 razina sukladnosti (conformance level), tag 6 identifikator smjernice (guideline identifier), a tag 7 /AFRelationship. Potvrđivanje toga da je razina sukladnosti koju pročitate natrag jedan standardni token a ne neka interna labela jest onaj najjeftiniji način za hvatanje EXTENDED pogreške prije nego datoteka napusti vašu gradnju (build)

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 vraća sirove XML bajtove kao jedan AnsiString, spreman za ispis u datoteku ili pak za pretakanje (stream) u proces validatora. U testnom uređaju (test harness) meta (target) je Mustang, zazvan (invoked) kroz svoju jar naredbenu liniju, a veraPDF se pokreće u istom tom prolazu preko te iste datoteke. Ožičenje (wiring) je malo: konzolni generator, EInvoiceValidation.dpr, zapisuje dvanaest datoteka koristeći dijeljeni model računa iz primjera (sample), a jedna skripta, run-validation.ps1, pokreće oba validatora preko izlaznog direktorija te zatim ispisuje jednu tablicu o tome tko prolazi a tko pada (pass and fail table). Taj isti dvokoračni oblik (two-step shape), generiraj pomoću biblioteke a verificiraj pomoću vanjskih validatora, jest baš ono što bi nekakav posao neprestane integracije (continuous-integration job) trebao pokretati na svaku promjenu u samom generiranju računa, zato što je to onaj jedini način da znate da datoteka zadovoljava oba sloja to da pitate oba alata

Ako vaš cjevovod (pipeline) također mora certificirati kontejner prije potpisivanja (signing), preflight strana ovog posla pokrivena je u našem detaljnom prolasku (walkthrough) kroz PDF/A i PDF/UA preflight u Delphiju (our walkthrough of PDF/A and PDF/UA preflight in Delphi), a onaj širi certificiraj-pa-potpiši (certify-then-sign) tok opisan je u radnom stolu za sukladnost i potpisivanje (the compliance and signing workbench). Oboje se grade na istom onom putu za generiranje koji se isporučuje (ships) kao dio Delphi PDF Library za Delphi i C++Builder, zajedno uz PDF/A, pridružene datoteke (associated-file), i API-je za metapodatke koji se ovdje koriste