Technical Article

Elektroninių sąskaitų faktūrų validavimas: veraPDF ir Mustang Delphi aplinkoje

Factur-X arba ZUGFeRD sąskaita faktūra yra du dokumentai, turintys vieną failo pavadinimą. Išorinis dokumentas yra PDF/A-3 konteineris, kurį archyvų skaitytuvas turi priimti ateinančius dešimt metų. Vidinis dokumentas yra XML sąskaita faktūra, kurią pirkėjo apskaitos sistema turi išnagrinėti (parse) pagal EN 16931. Klaida, dėl kurios į gamybą (production) paleidžiamos sugadintos sąskaitos faktūros, yra tikėjimas, kad teisingai padarius pirmąjį, antrasis gaunamas nemokamai. Taip nėra. Failas gali būti nepriekaištingas PDF/A-3 ir vis tiek turėti XML, kurio nepriims jokia mokesčių institucija, ir jis gali turėti vadovėlinį EN 16931 XML konteineryje, kuris nepraeina archyvinio validavimo. Šiuos du sluoksnius tikrina du skirtingi įrankiai, kurie nieko nežino vienas apie kitą, ir realus konvejeris (pipeline) turi atitikti juos abu

Du validatoriai, du skirtingi klausimai

veraPDF yra etaloninė PDF/A realizacija (reference implementation). Nukreipkite jį į sąskaitą faktūrą ir jis atsakys į vieną klausimą: ar tai atitinkantis PDF/A-3 failas. Jis tikrina dalykus, kurie rūpi ISO 19005-3. Ar kiekvienas šriftas įterptas (embedded). Ar yra OutputIntent. Ar XMP metaduomenys deklaruoja teisingą dalį ir atitikties lygį (conformance level). Elektroninei sąskaitai faktūrai jis taip pat patikrina susieto failo (associated-file) mechanizmus, kurių reikalauja PDF/A-3, nes XML keliauja kartu kaip įterptas failas su /AFRelationship ir įrašu dokumento katalogo /AF masyve. veraPDF nieko nesako apie tai, ar sąskaitos faktūros suma sutampa, nes tai neįeina į jo kompetenciją (remit)

Mustang yra atvirojo kodo validatorius iš Mustangproject. Jis užduoda ortogonalų klausimą: ar įterptas XML yra teisinga (valid) sąskaita faktūra. Jis patikrina XML pagal deklaruoto profilio schemą ir tada pritaiko EN 16931 verslo taisykles bei šaliai būdingus taisyklių rinkinius, išdėstytus viršuje, tarp jų ir XRechnung CIUS. Jis tikrina, ar yra pardavėjo PVM mokėtojo kodas, kai sumos to reikalauja, ar nuolaidų (allowance) ir mokesčių (charge) sumos suderintos (reconcile) su dokumento bendra suma, ar profilio URN XML faile atitinka tai, kuo failas teigia esąs. Mustang nerūpi, ar apgaubiantis PDF įterpia savo šriftus, nes tai yra veraPDF darbas

Nė vienas įrankis nėra kito poaibis (superset). veraPDF praleidžia struktūriškai tobulą konteinerį, apgaubiantį beprasmį XML. Mustang praleidžia tobulą XML, įvyniotą į konteinerį su trūkstamu OutputIntent. Kiekvienas jų pagauna būtent tos klasės defektą, kuriam kitas yra aklas, ir tai yra pagrindinė priežastis, kodėl rimta validavimo sistema (validation harness) naudoja abu ir failą laiko tinkamu siuntimui (shippable) tik tada, kai abu sutinka

Validavimo matrica

Kad įrodytų, jog biblioteka sukuria failus, kurie išgyvena abejus vartus, testavimo sistema (harness) sukuria matricą. Šeši sąskaitų faktūrų profiliai apima diapazoną, su kuriuo Europos konvejeris susiduria praktikoje: Factur-X EN 16931, Factur-X BASIC, Factur-X EXTENDED Prancūzijos B2B variantas, XRechnung 3.0, ZUGFeRD 1.0 COMFORT ir ZUGFeRD 2.0 BASIC. Kiekvienas profilis generuojamas pagal du PDF/A atitikties polygius (sub-conformance levels), 3b ir 3u, nes lygio B ir lygio U reikalavimai išsiskiria ties Unicode atvaizdavimu (mapping), ir failas, kuris praeina vieną, gali nepraeiti kito. Šeši profiliai kart du lygiai yra dvylika failų, ir kiekvienas iš jų sukuriamas be vartotojo sąsajos (headless) naudojant tą patį kodo kelią (code path), kurį pristato GUI pavyzdys, todėl testuojami artefaktai (artifacts) nėra rankiniu būdu priderinti testui

Generatorius įrašo visus dvylika, o skriptas (script) pateikia kiekvieną iš jų abiem validatoriams. Pirmojo pilno paleidimo metu veraPDF praleido visus dvylika. Konteinerio mechanizmai (plumbing) buvo teisingi visur: susieti failai užregistruoti, XMP atitiktis deklaruota, išvesties ketinimai (output intents) savo vietose. Mustang praleido aštuonis. Ketčios sąskaitos faktūros buvo struktūriškai teisingi PDF/A-3 failai, turintys XML, kurį verslo taisyklių validatorius atmetė, o tai yra būtent tas išsiskyrimas (split), kuriam iškelti (surface) ir egzistuoja dviejų įrankių metodas. Jei testavimo sistema (harness) būtų pasitikėjusi vien tik veraPDF, tos keturios būtų atrodžiusios baigtos

Du pataisymai (fixes), kurie užpildė spragą

Keturios Mustang klaidos atsirado dėl dviejų skirtingų priežasčių, ir kiekvienos jų pataisymas (fix) yra detalė, kurią verta žinoti prieš patiems generuojant šiuos profilius

Pirmasis buvo Factur-X EXTENDED Prancūzijos B2B profilis. Pradinis generatorius perdavė vidinę etiketę (internal label) kaip atitikties lygį (conformance level) ir vidinį URN kaip gaires (guideline), o Mustang atmetė failą pateikdamas „invalid-conformance-value“ klaidą, po kurios sekė „unsupported-profile-type“ klaida. Priežastis ta, kad XMP fx:ConformanceLevel laukas nėra laisvo teksto (free-text) vieta jūsų pačių profilių pavadinimams. Factur-X apibrėžia lygiai penkias standartines jo reikšmes: MINIMUM, BASIC WL, BASIC, EN 16931 ir EXTENDED. Prancūzijai būdinga B2B sąskaita faktūra vis dar yra EXTENDED profilio dokumentas, kalbant apie XMP metaduomenis. Prancūziškas sąskaitos faktūros pobūdis neišreiškiamas išrandant šeštąją atitikties reikšmę. Tai išreiškiama šalies kodu, FR, ir gairių identifikatoriumi XML viduje, kuris turi turėti urn:cen.eu:en16931:2017#conformant# priešdėlį (prefix), žymintį CIUS atitiktį EN 16931. Perdavus standartinę EXTENDED reikšmę su FR kaip šalies kodu ir teisingu gairių URN, failas tapo atitinkantis (conformant)

Bibliotekos API tai yra AddFacturXAssociatedFileFromString iškvietimas su suderintais atitikties, šalies ir gairių argumentais. Atitikties lygio argumentas neša standartinį žetoną (token), šalies kodo argumentas neša FR, o gairių URN gyvena XML baituose, kuriuos perduodate

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;

Antroji priežastis buvo ZUGFeRD 1.0 COMFORT profilis, ir tai neturėjo nieko bendro su metaduomenimis. ZUGFeRD 1.0 validuojamas pagal :1p0 XSD, kuris griežčiau vertina kardinalumą (cardinality), nei rodo prozinės santraukos. XSD reikalauja, kad antraštės (header) atsiskaitymo sumoje, ram:SpecifiedTradeSettlementMonetarySummation, būtų ram:ChargeTotalAmount ir ram:AllowanceTotalAmount po lygiai vieną kartą. Sugeneruotame XML nebuvo abiejų, todėl Mustang pranešė, kad elementai turi atsirasti lygiai vieną kartą. Jie nėra neprivalomi (optional), kai schema nurodo minOccurs kaip vienetą (one). Išvedus abu XSD sekos tvarka, iškart po ram:LineTotalAmount, su verte 0.00, kai nėra jokių mokesčių ar nuolaidų, schema buvo patenkinta. Nulis yra esantis elementas; nesantis elementas yra schemos pažeidimas. Įvedus šiuos du pataisymus (fixes), matrica pasiekė dvylika iš dvylikos Mustang atveju, tuo pat metu išlaikydama dvylika iš dvylikos veraPDF atveju

XRechnung laukai, kurie paverčia negaliojantį (invalid) galiojančiu (valid)

XRechnung nusipelno atskiros pastabos, nes jo Vokietijos CIUS prideda verslo taisyklių, kurių nėra baziniame EN 16931 rinkinyje, ir jos sužlunga taip, kad iš pirmo žvilgsnio atrodo, jog dokumentui nieko netrūksta. Dvi iš jų liečia elektroninius adresus. BT-34 yra pardavėjo elektroninis adresas, o BT-49 yra pirkėjo elektroninis adresas – maršrutizavimo galiniai taškai (routing endpoints), kuriuos Vokietijos viešojo sektoriaus portalas nausoja sąskaitai faktūrai pristatyti ir patvirtinti (acknowledge). Bazinis EN 16931 modelis laiko juos neprivalomais. XRechnung taip nedaro. Praleiskite bet kurį iš jų, ir sąskaita faktūra bus gerai suformuota (well-formed), schemiškai teisinga (schema-valid) ir atitinkamai atmesta

Trečioji yra taisyklė BR-DE-6, kuri reikalauja, kad būtų nurodytas pardavėjo kontaktinis telefono numeris. Tai yra tokio tipo laukas, kurio kūrėjas atsisako, nes atrodo, kad tai labiau prezentacija, o ne duomenys, ir jo nebuvimas sukelia validavimo klaidą, kuri nurodo į pardavėjo kontaktų grupę, o ne į ką nors akivaizdžiai trūkstamo. BT-34, BT-49 ir pardavėjo telefono numerio pateikimas yra tai, kas perkelia XRechnung failą iš negaliojančio (invalid) į galiojantį (valid) naudojant Mustang, ir niekas iš to nepakeičia to, ką mato veraPDF, nes visi trys gyvena XML viduje

Bibliotekos išvesties (output) prijungimas prie validatorius

Architektūrinis taškas, esantis už testavimo sistemos (harness), apibendrinamas bet kuriai verslo sistemai. PDF biblioteka įrašo atitinkantį konteinerį ir įterpia XML. Ji nebando ir neturėtų bandyti būti EN 16931 verslo taisyklių autoritetu. Bibliotekoje esanti funkcija ValidateFacturXInvoice tikrina konteinerio nuoseklumą (consistency) – ar katalogo /AF masyvas, įterptų failų vardų medis, XMP DocumentFileName, profilis, gairės ir /AFRelationship sutampa, tačiau ji nevaliduoja mokesčių kodų ir nesuderina sumų. Teisingas darbo pasidalijimas (division of labor) yra tai, kad verslo sistema ištraukia XML ir perduoda jį specialiam sąskaitų faktūrų validatoriui, lygiai taip, kaip testavimo sistema perduoda jį Mustang

Perskaitę failą atgal, sužinosite, kas iš tikrųjų buvo įrašyta. DetectFacturXInvoice praneša, ar sąskaita faktūra buvo atpažinta, o GetFacturXInvoiceInfo nuskaito metaduomenų laukus pagal žymą (tag): žyma 1 yra įterpto failo pavadinimas, žyma 2 – XMP DocumentFileName, žyma 5 – atitikties lygis, žyma 6 – gairių identifikatorius, ir žyma 7 – /AFRelationship. Patvirtinimas, kad atitikties lygis, kurį perskaitėte atgal, yra standartinis žetonas (token), o ne vidinė etiketė, yra pigiausias būdas pagauti EXTENDED klaidą prieš failui paliekant jūsų kūrimą (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 grąžina neapdorotus (raw) XML baitus kaip AnsiString, paruoštus įrašyti į failą ar srautą (stream) į validatoriaus procesą. Testavimo sistemoje (harness) tas taikinys (target) yra Mustang, iškviestas per jo komandinės eilutės (command-line) jar, o veraPDF paleistas to paties praėjimo (pass) metu tam pačiam failui. Sujungimas (wiring) yra nedidelis: konsolės generatorius, EInvoiceValidation.dpr, įrašo dvylika failų naudodamas bendrą sąskaitos faktūros modelį iš pavyzdžio, o skriptas, run-validation.ps1, paleidžia abu validatorius išvesties (output) katalogui ir atspausdina praėjusių ir nepraėjusių testų lentelę (pass and fail table). Ta pati dviejų žingsnių forma – generuoti naudojant biblioteką ir patikrinti su išoriniais validatoriais – yra tai, ką nuolatinio integravimo (continuous-integration) darbas turėtų vykdyti kiekvieną kartą pakeitus sąskaitų faktūrų generavimą, nes vienintelis būdas žinoti, kad failas atitinka abu sluoksnius, yra paklausti abiejų įrankių

Jei jūsų konvejeryje (pipeline) taip pat reikia sertifikuoti konteinerį prieš pasirašant, šio darbo paruošiamoji pusė (preflight side) yra aprašyta mūsų PDF/A ir PDF/UA preflight Delphi aplinkoje peržiūroje, o platesnis sertifikuoti-tada-pasirašyti srautas (flow) yra aprašytas atitikties ir pasirašymo darbo aplinkoje (workbench). Abu remiasi tuo pačiu generavimo keliu, kuris pateikiamas kaip Delphi PDF Library, skirtos Delphi ir C++Builder, dalis, kartu su čia naudojamais PDF/A, susietų failų (associated-file) ir metaduomenų API