Teknisk artikkel

Factur-X og ZUGFeRD hybridfakturaer i Delphi

En konform elektronisk faktura er ikke en PDF med en XML-fil stiftet på siden. Det er et enkelt PDF/A-3-dokument som bærer fakturaen to ganger: en gang som en side et menneske leser, og en gang som en maskinlesbar Cross Industry Invoice XML lagret inne i filen som en tilknyttet fil. De to representasjonene beskriver den samme fakturaen. Den doble naturen er hele poenget med formatfamiliene som europeiske mandater nå krever, Factur-X i Frankrike og Tyskland, ZUGFeRD over tysktalende markeder, og XRechnung for tysk fakturering i offentlig sektor. Denne artikkelen går gjennom hvordan PDFlibPas setter sammen en slik hybridfaktura i Delphi, hvor standardene gir rom for å gjøre feil, og hvorfor én profil i katalogen trenger en helt separat XML-bygger

Hva en hybridfaktura faktisk er

Den synlige siden og den innebygde XML-en tjener forskjellige lesere. En saksbehandler som godkjenner en betaling, ser på den gjengitte siden. Et leverandørgjeldssystem inntar XML-en, leser totalene og avgiftsoppdelingen som strukturerte felt, og bokfører oppføringen uten at et menneske taster noe. Det semantiske innholdet i den XML-en styres av EN 16931, den europeiske standarden som definerer fakturadatamodellen: hvilke felt som finnes, hva de betyr, og hvilke som er obligatoriske. EN 16931 er en semantisk modell, ikke et filformat. Factur-X, ZUGFeRD 2.x og XRechnung realiserer alle den modellen som et UN/CEFACT Cross Industry Invoice-dokument, syntaksen som bærer EN 16931-feltene på linjen

For at dokumentet skal være både arkiverbart og selvbeskrivende, er beholderen PDF/A-3, definert av ISO 19005-3. PDF/A-3 er konformansnivået som tillater vilkårlige innebygde filer, som er nøyaktig hva en faktura-XML må være. PDF/A-2 forbyr å bygge inn filer som ikke selv er PDF/A, så en Factur-X-faktura kan ikke være PDF/A-2. Valget av PDF/A-3 er derfor ikke en preferanse, det er et krav som følger direkte av å ville bygge inn ikke-PDF-data i et arkiveringsdokument

Hvorfor relasjonen er Alternative

Å bygge inn bytene er den enkle delen. ISO 32000 §7.11.4 definerer den innebygde filstrømmen, objektet som holder den rå XML-en og dens parametere. Den delen som gjør filen til en gyldig tilknyttet fil er §14.13, som legger til konseptet om en tilknyttet fil og nøkkelen /AFRelationship. Den nøkkelen angir hvordan de innebygde dataene forholder seg til innholdet den er festet til, og verdien Factur-X krever er Alternative

Valget betyr noe fordi de andre verdiene ville hevde noe usant om dokumentet. Source ville bety at XML-en er materialet som det synlige innholdet ble generert fra, en master som siden stammer fra. Supplement ville bety at XML-en legger til informasjon utover det siden viser, et tillegg som ikke finnes i gjengivelsen. Ingen av delene er hva en Factur-X-faktura er. XML-en og siden er to likeverdige uttrykk for én faktura, og bærer det samme juridiske innholdet i to former. Alternative er verdien som sier nøyaktig det: en likeverdig alternativ representasjon av det synlige innholdet. En validator som leser enhver annen relasjon på en Factur-X-fil vil avvise den, og med rette, fordi relasjonen er en maskinlesbar påstand om hva vedlegget er til for

Profilkatalogen

E-Invoice-eksemplet som leveres med PDFlibPas driver den samme genereringsbanen på tvers av seks profiler, definert som en matrise av poster i InvoiceModel.pas. Hver profil bærer verdiene forfatteren trenger: et visningsnavn, det innebygde filnavnet, et konformansnivå, /AFRelationship, en versjon, en valgfri landskode og GuidelineID URN-en som XML-en kunngjør inne i sin dokumentkontekst

De seks er Factur-X EN16931, Factur-X BASIC, Factur-X EXTENDED for Frankrike, XRechnung 3.0, ZUGFeRD 1.0 COMFORT og ZUGFeRD 2.0 BASIC. GuidelineID-en er feltet som forteller en mottaker nøyaktig hvilken profil den kan forvente, og verdiene er spesifikke. Factur-X EN16931 kunngjør urn:cen.eu:en16931:2017. XRechnung 3.0 kunngjør urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0. ZUGFeRD 2.0 BASIC kunngjør urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p0:basic. Det innebygde filnavnet er også en del av kontrakten. Factur-X-profiler bygger inn factur-x.xml, XRechnung bygger inn xrechnung.xml, og ZUGFeRD-profilene bygger inn ZUGFeRD-invoice.xml eller zugferd-invoice.xml. En mottaker skanner vedleggsnavnene for å finne fakturaen, så filnavnet er ikke kosmetisk

Én detalj i katalogen er verdt å lese nøye. De fleste profiler bruker Alternative-relasjonen, men XRechnung 3.0-oppføringen i eksemplet bruker Source. De to formatene svarer på forskjellige validatorer og konvensjoner, og eksemplet setter hver profils relasjon fra katalogen i stedet for å hardkode en enkelt verdi, som er grunnen til at per-profil-feltet finnes i stedet for en konstant

ZUGFeRD 1.0-fellen

Det er fristende å anta at hver profil er EN 16931 Cross Industry Invoice med mindre variasjoner i hvor mange valgfrie felt du fyller ut. Det gjelder for fem av de seks. Det gjelder ikke for ZUGFeRD 1.0 COMFORT, og grunnen er strukturell snarere enn kosmetisk

De moderne profilene avgir en UN/CEFACT Cross Industry Invoice med navneromversjon :100, hvis rotelement er rsm:CrossIndustryInvoice. ZUGFeRD 1.0 går forut for det skjemaet. Det er 2014 CrossIndustryDocument med navneromversjon :1p0, og rotelementet er rsm:CrossIndustryDocument. Navnerom-URN-ene er forskjellige, rotelementet er forskjellig, og elementtreet er annerledes tvers igjennom: :1p0-skjemaet grupperer data under ApplicableSupplyChainTradeAgreement, ApplicableSupplyChainTradeDelivery og ApplicableSupplyChainTradeSettlement, der :100 bruker ApplicableHeaderTradeAgreement, ApplicableHeaderTradeDelivery og ApplicableHeaderTradeSettlement. Navngivingen er lik nok til å villede og ulik nok til å knekke

Ordet COMFORT i profilnavnet beskriver hvor rike dataene er, en profil i automatiseringsgrad med fullstendige linjeelementer, avgiftsoppdeling og betalingsbetingelser, ikke hvilket skjema som bærer det. Så du kan ikke ta et :100-dokument og merke det om for ZUGFeRD 1.0. Eksemplet håndterer dette med et flagg på hver profilpost og to separate byggerfunksjoner, og velger den rette før noe XML genereres

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;

Delingen er ikke en implementeringsfinurlighet. Å mate et :100-tre til en ZUGFeRD 1.0-mottaker produserer et dokument som feiler skjemavalidering på rotelementet, så de to familiene må bygges av kode som vet hvilken den skriver

Valg av PDF/A-3-nivået

PDF/A-3 har tre konformansnivåer, og PDFlibPas velger dem gjennom SetPDFAMode. Modus 5 er PDF/A-3b, nivået som garanterer pålitelig visuell gjengivelse. Modus 6 er PDF/A-3a, som legger til tagget-struktur og tilgjengelighetskrav til nivå a. Modus 7 er PDF/A-3u, som krever at all tekst tilordnes til Unicode. Aktivering av modusen bygger også inn bibliotekets innebygde sRGB-utdataintensjon, fargekarakteriseringen som PDF/A krever slik at gjengitt farge er definert snarere enn enhetsavhengig

De fleste fakturaflyter kjører på 3b, noe som er tilstrekkelig for en trofast synlig side pluss den innebygde XML-en. Hvis du trenger en eksplisitt ICC-profil i stedet for den innebygde, bytter LoadOutputIntentProfile den inn etter at modusen er satt. Eksemplet laster inn repository-sRGB-profilen på denne måten og faller tilbake på den innebygde intensjonen når filen ikke er tilgjengelig, slik at utdataintensjonen alltid er til stede

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;

Bygging av hybridfakturaen

Med beholderen konfigurert er resten tre trinn i rekkefølge: sett PDF/A-3-modusen, tegn den menneskelesbare siden, og fest deretter XML-en som en tilknyttet fil. Den synlige siden er ordinært innhold. Den ene begrensningen som er verdt å huske er at PDF/A forbyr ikke-innebygde Standard 14-skrifter, så siden må bygge inn en ekte skrifttype fremfor å referere til en innebygd en

Vedlegget er et enkelt kall. AddFacturXAssociatedFileFromString tar de rå UTF-8 XML-bytene pluss profilmetadataene, skriver den innebygde filstrømmen, registrerer den i Catalog /AF-matrisen som PDF/A-3 krever, anvender /AFRelationship, og genererer XMP e-faktura-metadataene som identifiserer dokumentet som Factur-X, ZUGFeRD eller XRechnung. Det sjekker også at XML-ens retningslinje-ID stemmer med konformansnivået du ba om, slik at et avvik mellom XML-en du bygde og profilen du navnga fanges opp i stedet for å sendes stille avgårde

// 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);

En subtilitet i databanen er kodingen. Den innebygde XML-en erklærer encoding="UTF-8", og metoden tar dens byter som en AnsiString, så et ikke-ASCII selger- eller kjøpernavn må nå kallet som rå UTF-8-oktetter. En ren cast gjennom systemets ANSI-kodeside ville korrumpere disse tegnene og stille produsere en faktura der XML-en ikke lenger stemmer overens med sin egen deklarasjon. Eksemplet koder til UTF-8 eksplisitt før bytene leveres over, noe som er den trygge måten å mate et byteorientert PDF-API fra en Unicode-string

For å feste XML som ikke er en anerkjent e-fakturaprofil, er AddPDFA3AssociatedFileFromString den generiske motparten. Den tar et filnavn, MIME-type, beskrivelse, relasjon og byter, og skriver en ren PDF/A-3 tilknyttet fil uten noen fakturaspesifikke metadata eller retningslinjesjekker. Bruk den for tilleggsdata; bruk Factur-X-metoden for fakturaer, slik at profilmetadataene og retningslinjematsjen skrives for deg

Når dokumentet er produsert, er de neste spørsmålene om det består PDF/A- og tilgjengelighetsvalidering, og om det kan signeres uten å bryte konformiteten. Disse er dekket i vår gjennomgang av PDF/A og PDF/UA preflight og samsvars- og signeringsarbeidsbenken. Alt dette leveres som en del av PDFlibPas Delphi PDF Library, ved siden av PDF/A-, taggings- og dokumentegenskap-API-ene som e-fakturabanen bygger på