Articol Tehnic

Facturi hibride Factur-X și ZUGFeRD în Delphi

O factură electronică conformă nu este un PDF cu un fișier XML atașat pe lateral. Este un singur document PDF/A-3 care conține factura de două ori: o dată ca pagină pe care o citește un om, și o dată ca XML Cross Industry Invoice lizibil de mașini, stocat în interiorul fișierului ca fișier asociat. Cele două reprezentări descriu aceeași factură. Această natură duală este punctul central al familiilor de formate pe care mandatele europene le cer acum: Factur-X în Franța și Germania, ZUGFeRD pe piețele vorbitoare de limbă germană, și XRechnung pentru facturarea sectorului public german. Acest articol parcurge modul în care PDFlibPas asamblează o astfel de factură hibridă în Delphi, unde standardele lasă loc pentru greșeli, și de ce un profil din catalog necesită un constructor XML complet separat

Ce este de fapt o factură hibridă

Pagina vizibilă și XML-ul încorporat servesc cititori diferiți. Un funcționar care aprobă o plată privește pagina redată. Un sistem de conturi plătibile ingerează XML-ul, citește totalurile și defalcarea impozitelor ca câmpuri structurate, și înregistrează intrarea fără ca un om să tasteze nimic. Conținutul semantic al acelui XML este guvernat de EN 16931, standardul european care definește modelul de date al facturii: ce câmpuri există, ce înseamnă acestea, și care sunt obligatorii. EN 16931 este un model semantic, nu un format de fișier. Factur-X, ZUGFeRD 2.x, și XRechnung realizează toate acel model ca document UN/CEFACT Cross Industry Invoice, sintaxa care transportă câmpurile EN 16931 pe fir

Pentru ca documentul să fie atât arhivabil cât și auto-descriptiv, containerul este PDF/A-3, definit de ISO 19005-3. PDF/A-3 este nivelul de conformitate care permite fișiere încorporate arbitrare, ceea ce este exact ceea ce un XML de factură trebuie să fie. PDF/A-2 interzice încorporarea fișierelor care nu sunt ele însele PDF/A, deci o factură Factur-X nu poate fi PDF/A-2. Alegerea PDF/A-3 nu este prin urmare o preferință, ci o cerință care decurge direct din dorința de a încorpora date non-PDF într-un document de arhivă

De ce relația este Alternative

Încorporarea octeților este partea ușoară. ISO 32000 §7.11.4 definește fluxul de fișiere încorporate, obiectul care conține XML-ul brut și parametrii săi. Partea care face fișierul un fișier asociat valid este §14.13, care adaugă conceptul de fișier asociat și cheia /AFRelationship. Acea cheie specifică modul în care datele încorporate se raportează la conținutul la care sunt atașate, iar valoarea pe care Factur-X o impune este Alternative

Alegerea contează deoarece celelalte valori ar afirma ceva fals despre document. Source ar însemna că XML-ul este materialul din care a fost generat conținutul vizibil, un master din care derivă pagina. Supplement ar însemna că XML-ul adaugă informații dincolo de ceea ce arată pagina, un plus care nu este conținut în redare. Niciuna nu este ceea ce este o factură Factur-X. XML-ul și pagina sunt două expresii echivalente ale unei facturi, purtând același conținut legal în două forme. Alternative este valoarea care spune exact asta: o reprezentare alternativă echivalentă a conținutului vizibil. Un validator care citește orice altă relație pe un fișier Factur-X îl va respinge, și pe bună dreptate, deoarece relația este o afirmație lizibilă de mașini despre scopul atașamentului

Catalogul de profiluri

Eșantionul E-Invoice care este livrat cu PDFlibPas conduce același drum de generare pe șase profiluri, definite ca o matrice de înregistrări în InvoiceModel.pas. Fiecare profil poartă valorile de care scriitorul are nevoie: un nume de afișare, numele fișierului încorporat, un nivel de conformitate, /AFRelationship, o versiune, un cod de țară opțional și URN-ul GuidelineID pe care XML-ul îl anunță în contextul documentului său

Cele șase sunt Factur-X EN16931, Factur-X BASIC, Factur-X EXTENDED pentru Franța, XRechnung 3.0, ZUGFeRD 1.0 COMFORT și ZUGFeRD 2.0 BASIC. GuidelineID este câmpul care spune unui receptor exact ce profil să aștepte, iar valorile sunt specifice. Factur-X EN16931 anunță urn:cen.eu:en16931:2017. XRechnung 3.0 anunță urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0. ZUGFeRD 2.0 BASIC anunță urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p0:basic. Numele fișierului încorporat face parte din contract. Profilurile Factur-X încorporează factur-x.xml, XRechnung încorporează xrechnung.xml, iar profilurile ZUGFeRD încorporează ZUGFeRD-invoice.xml sau zugferd-invoice.xml. Un receptor scanează numele atașamentelor pentru a găsi factura, deci numele fișierului nu este cosmetic

Un detaliu din catalog merită citit cu atenție. Majoritatea profilurilor folosesc relația Alternative, dar intrarea XRechnung 3.0 din eșantion folosește Source. Cele două formate răspund la validatori și convenții diferite, iar eșantionul setează relația fiecărui profil din catalog în loc să codifice dur o singură valoare, ceea ce este motivul pentru care câmpul per-profil există în loc de o constantă

Capcana ZUGFeRD 1.0

Este tentant să presupunem că fiecare profil este EN 16931 Cross Industry Invoice cu variații minore în câte câmpuri opționale completați. Aceasta se aplică pentru cinci din șase. Nu se aplică pentru ZUGFeRD 1.0 COMFORT, iar motivul este structural mai degrabă decât cosmetic

Profilurile moderne emit un UN/CEFACT Cross Industry Invoice cu versiunea de spațiu de nume :100, al cărui element rădăcină este rsm:CrossIndustryInvoice. ZUGFeRD 1.0 predatează acea schemă. Este CrossIndustryDocument din 2014 cu versiunea de spațiu de nume :1p0, și elementul său rădăcină este rsm:CrossIndustryDocument. URN-urile spațiului de nume diferă, elementul rădăcină diferă, iar arborele de elemente diferă pe tot parcursul: schema :1p0 grupează datele sub ApplicableSupplyChainTradeAgreement, ApplicableSupplyChainTradeDelivery, și ApplicableSupplyChainTradeSettlement, în timp ce :100 folosește ApplicableHeaderTradeAgreement, ApplicableHeaderTradeDelivery, și ApplicableHeaderTradeSettlement. Denumirea este suficient de similară pentru a induce în eroare și suficient de diferită pentru a produce erori

Cuvântul COMFORT din numele profilului descrie cât de bogate sunt datele, un profil de grad de automatizare cu articole complete, defalcare fiscală și termeni de plată, nu schema care îl transportă. Deci nu puteți lua un document :100 și îl redenumi pentru ZUGFeRD 1.0. Eșantionul gestionează aceasta cu un indicator pe fiecare înregistrare de profil și două funcții de constructor separate, selectând pe cea corectă înainte ca orice XML să fie generat

function BuildInvoiceXMLText(const AProfile: TeInvoiceProfile;
  const Data: TInvoiceData): string;
begin
  // XMLFamily = 1 means the legacy ZUGFeRD 1.0 :1p0 schema; every
  // other profile is the modern UN/CEFACT :100 Cross Industry Invoice.
  if AProfile.XMLFamily = 1 then
    Result := BuildZUGFeRD1Text(AProfile, Data)
  else
    Result := BuildCII100Text(AProfile, Data);
end;

Separarea nu este o rafinare de implementare. Alimentarea unui arbore :100 unui receptor ZUGFeRD 1.0 produce un document care eșuează validarea schemei la elementul rădăcină, deci cele două familii trebuie construite de cod care știe pe care îl scrie

Selectarea nivelului PDF/A-3

PDF/A-3 are trei niveluri de conformitate, iar PDFlibPas le selectează prin SetPDFAMode. Modul 5 este PDF/A-3b, nivelul care garantează reproducerea vizuală fiabilă. Modul 6 este PDF/A-3a, care adaugă cerințele de structură etichetată și accesibilitate ale nivelului a. Modul 7 este PDF/A-3u, care impune ca tot textul să fie mapat la Unicode. Activarea modului încorporează și intenția de ieșire sRGB integrată a bibliotecii, caracterizarea culorii pe care PDF/A o cere astfel încât culoarea redată să fie definită mai degrabă decât dependentă de dispozitiv

Majoritatea fluxurilor de facturi rulează la 3b, care este suficient pentru o pagină vizibilă fidelă plus XML-ul încorporat. Dacă aveți nevoie de un profil ICC explicit în locul celui integrat, LoadOutputIntentProfile îl înlocuiește după ce modul este setat. Eșantionul încarcă profilul sRGB din depozit în acest mod și cade înapoi pe intenția integrată când fișierul nu este accesibil, deci intenția de ieșire este întotdeauna prezentă

PDF := TPDFlib.Create;
try
  // Mode 5 = PDF/A-3b, 6 = PDF/A-3a, 7 = PDF/A-3u.
  if PDF.SetPDFAMode(5) <> 1 then
    raise Exception.Create('PDF/A-3 mode could not be enabled');

  // Optional: swap the built-in sRGB intent for an explicit ICC profile.
  if PDF.LoadOutputIntentProfile(ICCFile, 'DeviceRGB') <> 1 then
    { fall back to the built-in sRGB intent that SetPDFAMode embedded };
finally
  // ... continue building the document
end;

Construirea facturii hibride

Cu containerul configurat, restul constă în trei pași în ordine: setați modul PDF/A-3, desenați pagina lizibilă de om, apoi atașați XML-ul ca fișier asociat. Pagina vizibilă este conținut obișnuit. Singura constrângere care merită reținută este că PDF/A interzice fonturile Standard 14 neîncorporate, deci pagina trebuie să încorporeze o față de font reală în loc să facă referire la una integrată

Atașamentul este un singur apel. AddFacturXAssociatedFileFromString ia octeții XML UTF-8 brut plus metadatele profilului, scrie fluxul de fișiere încorporate, îl înregistrează în matricea Catalog /AF pe care PDF/A-3 o cere, aplică /AFRelationship, și generează metadatele XMP ale facturii electronice care identifică documentul ca Factur-X, ZUGFeRD sau XRechnung. De asemenea verifică că ID-ul ghidului din XML corespunde cu nivelul de conformitate solicitat, deci o nepotrivire între XML-ul construit și profilul denumit este prinsă mai degrabă decât expediată în tăcere

// 1. PDF/A-3 mode and output intent are already set.
// 2. Draw the visible page (embeds a real TrueType font).
DrawInvoicePage(PDF, AProfile, Data);

// 3. Build the profile-correct XML and attach it as an
//    associated file with /AFRelationship = Alternative.
InvoiceXML := BuildInvoiceXML(AProfile, Data);   // AnsiString of UTF-8 bytes
FileID := PDF.AddFacturXAssociatedFileFromString(
  InvoiceXML,
  AProfile.ConformanceLevel,   // e.g. 'EN16931'
  AProfile.FileName,           // 'factur-x.xml'
  AProfile.Description,
  AProfile.Relationship,       // 'Alternative'
  AProfile.Version,            // '1.0'
  AProfile.CountryCode);       // '' or 'DE' or 'FR'
if FileID <= 0 then
  raise Exception.Create('Invoice XML could not be attached');

PDF.SaveToFile(TargetFile);

O subtilitate în calea de date este codificarea. XML-ul încorporat declară encoding="UTF-8", iar metoda ia octeții săi ca AnsiString, deci un nume de vânzător sau cumpărător non-ASCII trebuie să ajungă la apel ca octeți UTF-8 brut. O simplă conversie prin pagina de cod ANSI a sistemului ar corupe acele caractere și ar produce în tăcere o factură al cărei XML nu mai corespunde propriei declarații. Eșantionul codifică la UTF-8 explicit înainte de a preda octeții, ceea ce este modul sigur de a alimenta orice API PDF orientat pe octeți dintr-un string Unicode

Pentru atașarea XML-ului care nu este un profil de factură electronică recunoscut, AddPDFA3AssociatedFileFromString este echivalentul generic. Acesta ia un nume de fișier, tip MIME, descriere, relație și octeți, și scrie un fișier asociat PDF/A-3 simplu fără metadate specifice facturii sau verificări ale ghidului. Folosiți-l pentru date suplimentare; folosiți metoda Factur-X pentru facturi, astfel încât metadatele profilului și potrivirea ghidului să fie scrise pentru dvs

Odată ce documentul este produs, următoarele întrebări sunt dacă trece validarea PDF/A și accesibilitate, și dacă poate fi semnat fără a încălca conformitatea. Acestea sunt acoperite în ghidul de preflight PDF/A și PDF/UA și workbench-ul de conformitate și semnare. Toate acestea sunt livrate ca parte din PDFlibPas Delphi PDF Library, alături de API-urile PDF/A, etichetare și proprietăți de document pe care le folosește calea facturii electronice