Teknik Makale

Delphi'de Javasız Yerel EN 16931 Fatura Doğrulaması

PDFlibPas, losLab'ın Delphi PDF kütüphanesi, EN 16931, XRechnung 3.0.2 ve Peppol BIS Billing 3.0 iş kurallarını yerel Pascal içinde kontrol eder. TPDFlib.ValidateInvoiceBusinessRulesBuiltIn, bir Factur-X ya da ZUGFeRD PDF'inin içine gömülü XML'i okur ve resmi kural kimlikleriyle bir JSON raporu döndürür; Java çalışma zamanına, Saxon'a ya da bir Schematron araç zincirine gerek yoktur. Rapor kendini bir alt küme olarak etiketler ve o etiket, raporun içindeki en önemli alandır

Delphi için PDFlibPas PDF kütüphanesinde yerleşik EN 16931 fatura doğrulama hattı: Factur-X tespiti, schema ve schematron aşamaları ile kapsanan alt kümeyi raporlayan JSON
yerleşik doğrulayıcı schema ve schematron aşamalarını sürecin içinde çalıştırır ve alt kümenin hangi kuralları kapsayıp hangilerini kapsamadığını raporda açıkça gösterir

Bunun arkasındaki baskı büyük ölçüde Almanya'dan geliyor. Ocak 2025'ten bu yana Almanya'daki her yerli işletme, yapılandırılmış B2B e-faturaları alabilme kabiliyetine sahip olmak zorunda; düzenleme yükümlülüğü de aşamalarla geliyor. Bir Delphi muhasebe ya da ERP uygulamasının alıcı tarafı, birdenbire daha önce hiç sormadığı bir soruya cevap vermek zorunda: bu PDF'in içindeki XML gerçekten geçerli bir EN 16931 faturası mı? Resmi cevap, XSLT 2.0'a derlenen Schematron artefaktlarından ya da XRechnung için KoSIT validator'ından gelir ve ikisi de JVM üzerinde çalışır. Kötü bir faturayı reddetmek için her masaüstü kurulumunun yanına bir JRE taşımak ve yamamak, pek çok ekibin omuzlanmak istemediği bir maliyettir

Yerleşik doğrulayıcı gerçekte neyi kontrol eder?

Yerel doğrulayıcı, fatura XML'ini EN 16931 iş terimlerine eşler ve EN 16931 doğrulama artefaktlarının 1.3.16 sürümündeki kuralların belgelenmiş bir alt kümesini çalıştırır; her bulguyu resmi kural kimliği altında raporlar. İki sözdizimi de desteklenir: Factur-X ve ZUGFeRD'nin gömdüğü UN/CEFACT CII D16B ve OASIS UBL 2.1 Invoice ile CreditNote. Kapsanan alanlar şunlar:

  • Zorunlu terimler: specification identifier, fatura numarası, düzenleme tarihi, tür kodu, para birimi, satıcı ve alıcı adları, adresler, belge toplamları, KDV dökümü ve kalemler (BR-01 ile BR-16, BR-21 ile BR-28, BR-CO-04, BR-CO-18, BR-CO-26, BR-45 ile BR-48)
  • Hesaplamalar: kalem, iskonto, masraf, vergi ve ödenecek toplamlar; ayrıca her KDV kategorisinin matrah ve vergi tutarı (BR-CO-10 ile BR-CO-17, BR-xx-08, BR-xx-09)
  • S, Z, E, AE, K, G, O, L ve M kategorileri için KDV kategori kuralları: kalem oranları, istisna gerekçeleri ve her kategorinin istediği KDV kimlikleri
  • BR-CL-01'den BR-CL-26'ya tüm kod listeleri; BR-CL-23'ün arkasındaki 2100'den fazla UN/ECE Rec 20/21 birim kodu ve BR-DEC-01, -05, -09 ile -20 ve -23 arasındaki iki ondalık kuralları dahil
  • Profil kuralları: CII ve UBL üzerinde XRechnung 3.0.2 (BR-DE-1 ile BR-DE-31, BR-DEX-01 ile BR-DEX-14) ve UBL üzerinde Peppol BIS Billing 3.0.20 core, common, kod listesi ve ulusal kuralları

Kapsam raporlanır, ima edilmez. Schema dosyası olmadığında schema aşaması iyi biçimliliği ve kök öğeyi teyit eder. Her aşama, alt küme dışındaki kuralları bir notCovered alanında listeler ve GetBuiltInInvoiceRuleCatalog tüm haritayı JSON olarak döndürür; böylece arayüzünüz kullanıcıya, yeşil bir sonucun gerçekte neyi kapsadığını net biçimde gösterebilir

Atlanması kolay bir sınır daha var. Factur-X MINIMUM ve BASIC WL belgeleri EN 16931 faturası değildir; bu yüzden doğrulayıcı onlara EN 16931 kurallarını uygulamaz. Bunun yerine durumu söyleyen bilgilendirici bir BuiltIn.Coverage.Profile bulgusu ekler; bu dürüst bir cevaptır ve ayrıca bir ipucudur: Alman kuralları altında o iki profil zaten e-fatura sayılmaz

Karma PDF'in gömülü XML'ini doğrulamak

ValidateInvoiceBusinessRulesBuiltIn tek bir argüman alır: gömülü dosyanın adı; normal seçim boş dizedir. PDFlibPas sonra Factur-X tespitinin işaret ettiği eki seçer; Factur-X meta verisi olmayan bir PDF için tek .xml ekinden geri düşer. Birden fazla aday .xml eki ya da istenen adda iki ek varsa tahmin yürütmek yerine Invoice.AttachmentAmbiguous hatası verir; kod çözme 64 MB ile sınırlıdır ve fatura bulunamazsa LastErrorCode 401 olur

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]);
    // Boş ad: tespit edilen Factur-X / ZUGFeRD ekindini doğrular
    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'));        // "alt küme"
      Writeln('full:     ', Report.GetValue<Boolean>('fullConformance')); // burada asla True çıkmaz
    finally
      Report.Free;
    end;
  finally
    PDF.Free;
  end;
end;

Raporun üst düzeyi standard, version, profile, complete, valid, fullConformance, coverage, bir schema ve bir schematron aşaması ile yerleşik modda null kalan bir package nesnesi taşır. Her aşama bir durum, motoru, çalıştırılan kural sayısını, kapsamını ve bir issues dizisi raporlar. Her bulguda ruleID, fieldPath, severity, category, line ve message bulunur

Mesaj yerine kategoriye göre okumak

category alanı, raporu gerçek bir gelen kutusunda kullanılabilir kılan şeydir; çünkü sorunu kimin düzeltmesi gerektiğini söyler. Değerler şunlardır: syntax, schema, cardinality, calculation, vat, codelist, decimal, profile, coverage, processing ve integrity. Bir calculation bulgusu genelde gönderenin yuvarlama koduna, bir codelist bulgusu ana verilere, bir profile bulgusu ise alıcı referansı gibi bir XRechnung alanına işaret eder

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;  // BuiltIn.Coverage.Profile gibi kapsam notları
    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;

Aynı döngüyü schema.issues üzerinde de çalıştırın; iyi biçimlilik ve kök öğe hataları oraya düşer ve iş kuralı aşaması çözümleyemediği bir belgeyi hiçbir zaman görmez

Aynı fatura neden CII'de geçip UBL'de kalabiliyor?

Resmi artefaktlar iki sözdizimine aynı sayısal toleransı uygulamaz ve PDFlibPas kuralları uyumlu hale getirmek yerine her kuralı kendi sözdiziminin XSLT'sinden kopyalar. En net örnek BR-CO-17. Kural, KDV kategorisi vergi tutarının matrah çarpı oran olduğunu ve iki ondalığa yuvarlandığını kontrol eder; her iki sözdizimi de bir para birimi farkına izin verir. CII artefaktı bunu <= ve >= olarak yazar; UBL artefaktı ise katı bir < ile abs kullanır

Matrah 100.00, oran yüzde 19, yani beklenen vergi 19.00 olsun; fatura da 20.00 beyan etsin. CII'de 20.00 eksi 1, 19.00 eder ve bu <= koşulunu sağlar; BR-CO-17 geçer. UBL'de aynı karşılaştırma katıdır; 19.00, 19.00'dan küçük değildir ve BR-CO-17 kalır. Kimsen o faturayı bilerek göndermez, ama yuvarlama hatası olan üreticiler tam bu tür sınırların üstünde durur ve yalnızca tek sözdizimini kapsayan bir test paketi durumu fark etmez

Delphi için PDFlibPas fatura doğrulamasında BR-CO-17 tolerans karşılaştırması: bir birimlik yuvarlama farkı taşıyan aynı fatura CII'de geçer, UBL'de kalır
tek bir yuvarlama birimi tam BR-CO-17 sınırına oturur; aynı fatura CII'de geçer, UBL'de kalır

Farklar BR-CO-17'nin ötesine uzanır. BR-xx-08, CII'de iki ondalığa yuvarlama sonrası tam eşitlik ister; UBL ise ±1'e izin verir ve kuralı yalnızca kategorinin gerçekten kalemi, iskontosu ya da masrafı olduğunda devreye alır. BR-O-08 her ikisinde de katıdır. CII artefaktı BR-AF-09 ile BR-AG-09'u true() olarak tanımlar, dolayısıyla orada hiç kalmez; UBL ise bunları ±1 ile kontrol eder. Bir CII KDV dökümünde oran hiç yoksa, O dışı kategori kuralları boş geçer, çünkü boş bir dizi üzerinde XPath every $rate doğrudur. Yerel kod da aynı biçimde atlamalıdır; aksi hâlde resmi örnek dosyalarda hata raporlar

İkinci tuzak kural kimlikleriyle ilgili. Kural kategorisi öneki, KDV kategori kodu değildir: K, BR-IC'ye; L, BR-AF'ye; M ise BR-AG'ye karşılık gelir. Kimliği kategori kodundan üretmek, EN 16931'de var olmayan BR-K-09 ve BR-K-10 kimliklerini doğurur. Uygulamanız kural kimliklerini yardım metinlerine eşliyorsa kimliği rapordan alın, asla vergi kategori kodundan kurmayın

XRechnung ve Peppol kuralları nasıl devreye giriyor?

Yerleşik doğrulayıcı profil kurallarını specification identifier'dan (BT-24) seçer. xrechnung içeren bir kimlik, CII ya da UBL üzerinde XRechnung 3.0.2 CIUS kurallarını çalıştırır; urn:xeinkauf.de:kosit:extension:xrechnung_3.0 uzantı kimliği buna ek olarak BR-DEX kurallarını açar. Alt fatura satırı tutarı ve KDV kontrolleri (BR-DEX-02, -03 ve -09 ile -14) yalnızca UBL'de çalışır; CII'de BR-DEX-15, alt fatura satırlarının erişilebilir olmadığını raporlar. peppol içeren bir kimlik, Peppol BIS Billing 3.0.20 kurallarını çalıştırır; Peppol bunları yalnızca UBL için tanımlar. Peppol customization iddia eden bir CII belgesi, sessiz bir geçiş yerine o kuralların kontrol edilmediğini söyleyen bilgilendirici bir BuiltIn.Coverage.Profile notu alır. Öteki CIUS profilleri için resmi artefaktları kullanın

Bilinmesi değer bir UBL ayrıntısı: BR-06 ile BR-07, satıcı ya da alıcı adı olarak yalnızca PartyLegalEntity/RegistrationName kabul eder. PartyName/Name altına düşmek hoşgörülü görünür ama gerçek BR-06 hatalarını gizler; kontrol bu yüzden katıdır

Resmi Schematron hâlâ ne zaman gerekir?

Sonucun hukuki ya da sözleşmesel ağırlığı taşıdığı her durumda resmi artefaktları kullanın: Saxon üzerinden EN 16931 Schematron'u ya da XRechnung için KoSIT validator'ı. Bir tedarikçi faturasını reddetmek, kendi üreticinizi sertifikalamak ya da bir anlaşmazlığı kapatmak bunlara girer. Yerleşik rapor fullConformance değerini hiçbir zaman true yapmaz. O bayrak; doğrulanmış bir kural paketi, her iki aşamadan geçiş, her iki aşamanın da paketin kendi kurallarını çalıştıran bir motorla koşması ve notCovered içinde kalan hiçbir şey olmamasını gerektirir

ValidateInvoiceBusinessRulesWithPackage bu yolu çalıştırır. Kural paketi, CreateInvoiceRulePackage tarafından yazılan, standart, sürüm, schema ve Schematron stil sayfasını adlandıran ve her dosyanın SHA-256 değerini kaydeden bir rulepackage.xml manifesti taşıyan bir dizindir. PDFlibPas her özeti, paket yüklenirken ve motor dosyayı okumadan hemen önce bir kez daha kontrol eder; böylece doğrulamadan sonraki takas penceresi daralır. Yollar paket dizininden dışarı çıkamaz ve yerel XSD yükleyicisi, manifestin listelemediği import'ları reddeder. xmllint yapılandırıldığında import edilen şemaları xmllint'in kendisi okur; o zaman onları yalnızca yükleme anındaki kontrol korur

const
  // İnceleyip onayladığınız rulepackage.xml'in SHA-256 özeti
  ApprovedManifestSHA256 = '<64 hex digits from your release review>';
var
  Report, Pkg: TJSONObject;
begin
  // PDF: fatura zaten yüklü bir TPDFlib; ilk örnekteki gibi
  Report := TJSONObject.ParseJSONValue(
    PDF.ValidateInvoiceBusinessRulesWithPackage('',
      'C:\Rules\EN16931-CII-1.3.16', ApprovedManifestSHA256,
      '',                          // boş: yerel XSD aşaması
      'C:\Java\bin\java.exe', 'C:\Saxon\saxon-he.jar',
      0)) as TJSONObject;          // 0: araç başına 30000 ms
  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;

Sabitlenen özet süs değil. Dosya özetleri, dosyaların manifestle eşleştiğini kanıtlar; ama paketin içinde hiçbir şey manifestin kendisinin gerçek olduğunu kanıtlamaz: kuralları yeniden yazabilen bir saldırgan, manifesti de onlarla birlikte yeniden yazabilir. O açığı yalnızca çağıranın, kod içinde ya da imzalı yapılandırmada sabitlediği bir özet kapatır; rapor, hangi güvenceye gerçekten sahip olduğunuzu ayırt edebilmeniz için pinned ile verified değerini ayrı tutar

Delphi'de PDFlibPas fatura kural paketi için bütünlük akışı: manifest SHA-256 özetleri yüklemede ve her okuma öncesinde yeniden kontrol edilir, çağıran sabitler
dosya özetleri paket içeriğini korur; manifestin kendisini yalnızca çağıranın sabitlediği özet korur

Boş bir JavaExecutable geçirirseniz schema aşaması yine de paket XSD'sine karşı yerel olarak doğrular (v3.672.0'dan beri facet ve kimlik kısıtları dahil), ama iş kuralları yerleşik alt kümeye düşer ve fullConformance false kalır. Yerel yükleyicinin desteklemediği substitution group gibi schema yapıları ise atlanmak yerine yüklemeyi XSD.Unsupported ile başarısız kılar

veraPDF ve Mustang nereye oturuyor?

Bu işlevlerin hiçbiri PDF kapsayıcısına bakmaz. ISO 19005-3 altında PDF/A-3 uyumu, Factur-X XMP uzantı şeması ve associated-file ilişkisi ayrı sorulardır; bunlar için veraPDF ve Mustang referans araçlar olmayı sürdürüyor, bkz. veraPDF ve Mustang ile e-fatura doğrulama. XML'i eklemekten profili beyan etmeye uzanan üretici tarafı ise Delphi'de Factur-X ve ZUGFeRD karma faturaları kurma ile Factur-X meta verisi için PDF/A-3 uzantı şemaları yazılarında kapsanıyor

Akıllıca bir bölünme, yerleşik kontrolü her gelen faturada sürecin içinde çalıştırır; orada maliyeti milisaniyelerdir: base64 ekli, yaklaşık 1 MB'lık bir UBL faturası 15 ms'de doğrulanır, UBL 2.1 XSD ile 79 ms. Paketli Saxon koşusu CI'ye ve reddetme yoluna gider, kapsayıcıyı ise veraPDF karşılar. Yerleşik sonuç ne derse desin, o kapsanan alt kümeyi anlatır, standardı değil; coverage alanı tam olarak bunu hatırlatmak için orada durur

Fatura doğrulama işlevleri, kural kataloğu ve kural paketi araçları, yukarıda kullanılan Factur-X, PDF/A-3 ve gömülü dosya API'leriyle birlikte, Delphi ve C++Builder için Delphi PDF Library ile gelir