PDFlibPas, losLabs Delphi PDF Library, tjekker forretningsreglerne i EN 16931, XRechnung 3.0.2 og Peppol BIS Billing 3.0 nativt i Pascal. TPDFlib.ValidateInvoiceBusinessRulesBuiltIn læser den XML, der er indlejret i en Factur-X- eller ZUGFeRD-PDF, og returnerer en JSON-rapport med officielle regel-ID'er, uden et Java runtime, Saxon eller en Schematron-toolchain. Rapporten kalder sig selv et subset, og den betegnelse er det vigtigste felt i den
Presset bag det her kommer mest fra Tyskland. Siden januar 2025 skal enhver indenlandsk virksomhed i Tyskland kunne modtage strukturerede B2B e-fakturaer, og udgivelsespligten træder i kraft trin for trin. Pludselig skal modtagersiden af et Delphi-regnskabs- eller ERP-program svare på et spørgsmål, den aldrig havde stillet før: er XML'en i den her PDF faktisk en gyldig EN 16931-faktura? Det officielle svar kommer fra Schematron-artefakter kompileret til XSLT 2.0 eller fra KoSIT-validatoren til XRechnung, og begge kører på JVM'en. At skibe og patche en JRE ved siden af hver desktop-installation bare for at afvise en dårlig faktura er en omkostning, mange teams helst vil slippe for
Hvad tjekker den indbyggede validator faktisk?
Den native validator mapper faktura-XML'en over på EN 16931-forretningsbegreberne og kører et dokumenteret subset af reglerne fra EN 16931-valideringsartefakterne 1.3.16 og rapporterer hvert fund under dets officielle regel-ID. Begge syntakser understøttes: UN/CEFACT CII D16B, som er det, Factur-X og ZUGFeRD indlejrer, og OASIS UBL 2.1 Invoice og CreditNote. De dækkede områder er:
- Obligatoriske begreber: specifikationsidentifikator, fakturanummer, udstedelsesdato, typekode, valuta, sælger- og købernavne, adresser, dokumenttotaler, momsspecifikation og linjer (BR-01 til BR-16, BR-21 til BR-28, BR-CO-04, BR-CO-18, BR-CO-26, BR-45 til BR-48)
- Beregninger: linje-, rabat-, gebyr-, moms- og betalingstotaler plus det momspligtige beløb og momsbeløbet for hver momskategori (BR-CO-10 til BR-CO-17, BR-xx-08, BR-xx-09)
- Momskategoriregler for S, Z, E, AE, K, G, O, L og M: linjesatser, fritagelsesgrunde og de moms-ID'er, hver kategori kræver
- Alle kodelisterne BR-CL-01 til BR-CL-26, inklusiv de over 2100 UN/ECE Rec 20/21-enhedsnumre bag BR-CL-23, og de to-decimals-regler BR-DEC-01, -05, -09 til -20 og -23
- Profilregler: XRechnung 3.0.2 (BR-DE-1 til BR-DE-31, BR-DEX-01 til BR-DEX-14) på CII og UBL, samt Peppol BIS Billing 3.0.20 core-, common-, kodeliste- og nationalregler på UBL
Omfanget rapporteres, det er ikke underforstået. Uden en schema-fil bekræfter schema-trinnet kun velformethed og rodelementet. Hvert trin lister reglerne uden for subsettet i et notCovered-felt, og GetBuiltInInvoiceRuleCatalog returnerer hele mappet som JSON, så din UI kan vise en bruger præcis, hvad et grønt resultat dækker
En grænse mere er let at overse. Factur-X MINIMUM- og BASIC WL-dokumenter er ikke EN 16931-fakturaer, så validatoren anvender ikke EN 16931-reglerne på dem. I stedet tilføjer den en informativ BuiltIn.Coverage.Profile-finding, der siger netop det, hvilket er det ærlige svar og samtidig et hint: efter de tyske regler tæller de to profiler i forvejen ikke som e-fakturaer
Validering af den indlejrede XML i en hybrid-PDF
ValidateInvoiceBusinessRulesBuiltIn tager ét argument, navnet på den indlejrede fil, og en tom streng er det normale valg. PDFlibPas vælger derefter det attachment, som Factur-X-detektionen identificerer; uden Factur-X-metadata falder den tilbage til det eneste .xml-attachment. Flere kandidat-.xml-attachments eller to attachments med det efterspurgte navn giver en Invoice.AttachmentAmbiguous-fejl i stedet for et gæt, afkodningen er begrænset til 64 MB, og en manglende faktura sætter LastErrorCode til 401
uses
System.SysUtils, System.JSON, PDFlibrary;
procedure CheckInboundInvoice(const FileName: string);
var
PDF: TPDFlib;
Report: TJSONObject;
begin
PDF := TPDFlib.Create;
try
if PDF.LoadFromFile(FileName, '') <> 1 then
raise Exception.CreateFmt('Cannot open %s (error %d)',
[FileName, PDF.LastErrorCode]);
// Tomt navn: valider det fundne Factur-X / ZUGFeRD-attachment
Report := TJSONObject.ParseJSONValue(
PDF.ValidateInvoiceBusinessRulesBuiltIn('')) as TJSONObject;
try
Writeln('profile: ', Report.GetValue<string>('profile'));
Writeln('valid: ', Report.GetValue<Boolean>('valid'));
Writeln('coverage: ', Report.GetValue<string>('coverage')); // "subset"
Writeln('full: ', Report.GetValue<Boolean>('fullConformance')); // aldrig True her
finally
Report.Free;
end;
finally
PDF.Free;
end;
end;
Topniveauet i rapporten indeholder standard, version, profile, complete, valid, fullConformance, coverage, et schema- og et schematron-trin samt et package-objekt, der er null i built-in-tilstand. Hvert trin rapporterer en status, motoren, antallet af udførte regler, sin dækning og et issues-array. Hver finding har ruleID, fieldPath, severity, category, line og message
Læs findings efter kategori i stedet for efter besked
category-feltet er det, der gør rapporten brugbar i en rigtig indbakke, fordi det fortæller dig, hvem der skal rette problemet. Værdierne er syntax, schema, cardinality, calculation, vat, codelist, decimal, profile, coverage, processing og integrity. En calculation-finding peger typisk på afsenderens afrundingskode, en codelist-finding på masterdata og en profile-finding på et XRechnung-felt som køberreferencen
procedure ListIssues(Report: TJSONObject);
var
Item: TJSONValue;
Issue: TJSONObject;
begin
for Item in Report.GetValue<TJSONArray>('schematron.issues') do
begin
Issue := Item as TJSONObject;
if Issue.GetValue<string>('severity') = 'info' then
Continue; // coverage-noter som BuiltIn.Coverage.Profile
Writeln(Format('[%s] %s at %s: %s', [
Issue.GetValue<string>('category'),
Issue.GetValue<string>('ruleID'),
Issue.GetValue<string>('fieldPath'),
Issue.GetValue<string>('message')]));
end;
end;
Kør den samme løkke over schema.issues også, for velformetheds- og rodelement-fejl lander der, og forretningsregel-trinnet ser aldrig et dokument, det ikke kunne parse
Hvorfor kan den samme faktura bestå i CII og fejle i UBL?
De officielle artefakter anvender ikke samme numeriske tolerance i begge syntakser, og PDFlibPas kopierer hver regel fra sin egen syntakses XSLT i stedet for at harmonisere dem. BR-CO-17 er det tydeligste tilfælde. Den tjekker, at momskategoriens momsbeløb er lig momspligtigt beløb gange sats, afrundet til to decimaler, og begge syntakser tillader en afvigelse på én valutaenhed. CII-artefaktet skriver det som <= og >=; UBL-artefaktet bruger abs med et strengt <
Tag et momspligtigt beløb på 100,00 med 19 procent, så det forventede momsbeløb er 19,00, og en faktura, der opgiver 20,00. I CII giver 20,00 minus 1 lig 19,00, hvilket tilfredsstiller <=, og BR-CO-17 består. I UBL er sammenligningen streng, 19,00 er ikke mindre end 19,00, og BR-CO-17 fejler. Ingen sender sådan en faktura med vilje, men generatorer med en afrundingsbug ligger præcis på sådanne grænser, og en test suite, der kun dækker én syntaks, lægger det ikke mærke til
Forskellene går videre end BR-CO-17. BR-xx-08 kræver eksakt lighed efter afrunding til to decimaler i CII, mens UBL tillader ±1 og kun slår til, når kategorien faktisk har linjer, rabatter eller gebyrer. BR-O-08 er eksakt i begge. CII-artefaktet definerer BR-AF-09 og BR-AG-09 som true(), så de aldrig fejler der, mens UBL tjekker dem med ±1. Og når en CII-momsspecifikation slet ingen sats har, går de ikke-O-kategoriregler igennem uden at teste noget, fordi XPath every $rate over en tom sekvens er sand. Den native kode skal springe dem over på samme måde, ellers rapporterer den fejl på officielle eksempelfiler
En anden fælde handler om regel-ID'er. Regelkategoripræfikset er ikke momskategorikoden: K mappes til BR-IC, L til BR-AF og M til BR-AG. At aflede ID'et fra kategorikoden giver BR-K-09 og BR-K-10, ID'er, der ikke findes i EN 16931. Hvis dit program mapper regel-ID'er til hjælpetekster, så tag ID'et fra rapporten og byg det aldrig ud fra momskategorikoden
Hvordan slås XRechnung- og Peppol-reglerne til?
Den indbyggede validator vælger profilregler ud fra specifikationsidentifikatoren (BT-24). En identifikator, der indeholder xrechnung, kører XRechnung 3.0.2 CIUS-reglerne på CII eller UBL; udvidelsesidentifikatoren urn:xeinkauf.de:kosit:extension:xrechnung_3.0 slår desuden BR-DEX-reglerne til. Tjek af sub invoice line amount og moms (BR-DEX-02, -03 og -09 til -14) kører kun på UBL; på CII rapporterer BR-DEX-15, at sub invoice lines ikke er tilgængelige. En identifikator, der indeholder peppol, kører Peppol BIS Billing 3.0.20-reglerne, som Peppol kun definerer for UBL. Et CII-dokument, der påstår en Peppol-customization, får en informativ BuiltIn.Coverage.Profile-note om, at de regler ikke blev tjekket, i stedet for et lydløst bestået. Til andre CIUS-profiler skal du bruge de officielle artefakter
En UBL-detalje er værd at kende: BR-06 og BR-07 accepterer kun PartyLegalEntity/RegistrationName som sælger- eller købernavn. At falde tilbage til PartyName/Name ser eftergivende ud, men skjuler rigtige BR-06-fejl, og derfor er tjekket strengt
Hvornår skal du stadig bruge den officielle Schematron?
Brug de officielle artefakter, EN 16931 Schematron via Saxon eller KoSIT-validatoren til XRechnung, hver gang et resultat har juridisk eller kontraktlig vægt: at afvise en leverandørfaktura, certificere din egen generator eller bilægge en uenighed. Den indbyggede rapport sætter aldrig fullConformance til true. Det flag kræver en verificeret regelpakke, begge trin bestået, begge trin kørt af en motor, der udfører pakkens egne regler, og intet tilbage i notCovered
ValidateInvoiceBusinessRulesWithPackage kører den vej. En regelpakke er en mappe med et rulepackage.xml-manifest, skrevet af CreateInvoiceRulePackage, som angiver standard, version, schema og Schematron-stylesheet og noterer SHA-256 for hver fil. PDFlibPas tjekker hver hash, når pakken loades, og igen lige før en motor læser filen, hvilket snævrer vinduet ind for et bytte efter verificeringen. Stier må ikke forlade pakkemappen, og den native XSD-loader nægter imports, som manifestet ikke lister. Med xmllint konfigureret læses importerede schemas af xmllint selv, så kun tjekket ved load beskytter dem
const
// SHA-256 for den rulepackage.xml, du har gennemgået og godkendt
ApprovedManifestSHA256 = '<64 hex digits from your release review>';
var
Report, Pkg: TJSONObject;
begin
// PDF: en TPDFlib med fakturaen allerede loadet, som i det første eksempel
Report := TJSONObject.ParseJSONValue(
PDF.ValidateInvoiceBusinessRulesWithPackage('',
'C:\Rules\EN16931-CII-1.3.16', ApprovedManifestSHA256,
'', // tomt: native XSD-trin
'C:\Java\bin\java.exe', 'C:\Saxon\saxon-he.jar',
0)) as TJSONObject; // 0: 30000 ms pr. tool
try
if not (Report.FindValue('package') is TJSONObject) then
raise Exception.Create('No rule package in the report');
Pkg := Report.GetValue<TJSONObject>('package');
if not (Pkg.GetValue<Boolean>('pinned') and Pkg.GetValue<Boolean>('verified')) then
raise Exception.Create('Rule package failed its integrity check');
if Report.GetValue<Boolean>('fullConformance') then
Writeln('Conforms to the complete rule package');
finally
Report.Free;
end;
end;
Den låste hash er ikke valgfri pynt. Filhashes beviser, at filerne matcher manifestet, men intet inde i en pakke beviser, at manifestet selv er ægte: en angriber, der kan omskrive reglerne, kan omskrive manifestet sammen med dem. Kun en hash, som den kaldende fastlåser i kode eller i signet konfiguration, lukker hullet, og rapporten holder pinned og verified adskilt, så du kan se, hvilken garanti du reelt har
Giver du en tom JavaExecutable, validerer schema-trinnet stadig nativt mod pakkens XSD, inklusiv facets og identity constraints siden v3.672.0, men forretningsreglerne falder tilbage til det indbyggede subset, og fullConformance forbliver false. Schema-konstrukter, den native loader ikke understøtter, som substitution groups, får loadet til at fejle med XSD.Unsupported i stedet for at blive sprunget over
Hvor veraPDF og Mustang passer ind
Ingen af disse funktioner kigger på PDF-containeren. PDF/A-3-overensstemmelse under ISO 19005-3, Factur-X XMP-udvidelsesschemaet og associated-file-relationen er separate spørgsmål, og veraPDF og Mustang forbliver referencerværktøjerne til dem, som beskrevet i validering af e-fakturaer med veraPDF og Mustang. Producersiden, fra at vedhæfte XML'en til at erklære profilen, er dækket i opbygning af Factur-X- og ZUGFeRD-hybridfakturaer i Delphi og PDF/A-3-udvidelsesschemas til Factur-X-metadata
En fornuftig opdeling kører det indbyggede tjek in-process på hver indgående faktura, hvor det koster millisekunder: en UBL-faktura på omkring 1 MB med base64-attachments valideres på 15 ms, eller 79 ms med UBL 2.1 XSD. Saxon-kørslen med pakke kommer i CI og i afvisningsstien, og veraPDF dækker containeren. Uanset det indbyggede resultat beskriver det det dækkede subset, ikke standarden — og det er præcis dét, coverage-feltet er der for at minde dig om
Fakturavalideringsfunktionerne, regelkataloget og regelpakke-værktøjerne følger med i Delphi PDF Library til Delphi og C++Builder, sammen med Factur-X-, PDF/A-3- og embedded file-API'erne brugt ovenfor