Technical Article

DelphiでのFactur-XおよびZUGFeRDハイブリッドインボイス

準拠した電子インボイスは、XMLファイルが横にホッチキスで留められたPDFではありません。それは、インボイスを2回保持する単一のPDF/A-3ドキュメントです。1回目は人間が読むページとして、2回目は関連ファイルとしてファイル内に保存される機械可読のCross Industry Invoice XMLとしてです。この2つの表現は同じインボイスを記述しています。その二重の性質こそが、ヨーロッパの義務付けが現在要求しているフォーマットファミリーの要点です。フランスとドイツでのFactur-X、ドイツ語圏市場全体のZUGFeRD、そしてドイツの公共部門の請求のためのXRechnungです。この記事では、DelphiにおいてPDFlibPasがそのようなハイブリッドインボイスをどのように組み立てるか、標準がどこで間違いを犯す余地を残しているか、そしてカタログ内の1つのプロファイルが完全に別個のXMLビルダーを必要とする理由について順を追って説明します

ハイブリッドインボイスとは実際には何か

可視ページと埋め込みXMLは、異なる読者にサービスを提供します。支払いを承認する事務員は、レンダリングされたページを見ます。買掛金システムはXMLを取り込み、合計と税の内訳を構造化されたフィールドとして読み取り、人間が何も入力せずにエントリを予約します。そのXMLの意味的なコンテンツ(セマンティックコンテンツ)は、インボイスデータモデル(どのフィールドが存在し、それらが何を意味し、どれが必須か)を定義するヨーロッパの標準であるEN 16931によって管理されています。EN 16931はセマンティックモデルであり、ファイル形式ではありません。Factur-X、ZUGFeRD 2.x、およびXRechnungはすべて、そのモデルをUN/CEFACT Cross Industry Invoiceドキュメントとして実現します。これは、EN 16931フィールドをネットワーク上で運ぶための構文です

ドキュメントがアーカイブ可能であり、かつ自己記述的であるために、コンテナはISO 19005-3で定義されたPDF/A-3です。PDF/A-3は、任意の埋め込みファイルを許可する適合性レベルであり、インボイスXMLはまさにそうである必要があります。PDF/A-2は、それ自体がPDF/Aではないファイルを埋め込むことを禁じているため、Factur-XのインボイスはPDF/A-2にはなり得ません。したがって、PDF/A-3の選択は好みではなく、アーカイブドキュメントに非PDFデータを埋め込みたいという欲求から直接生じる要件です

リレーションシップがAlternativeである理由

バイトを埋め込むことは簡単な部分です。ISO 32000 §7.11.4は、生のXMLとそのパラメータを保持するオブジェクトである埋め込みファイルストリームを定義しています。ファイルを有効な関連ファイル(associated file)にする部分は§14.13であり、これに関連ファイルの概念と/AFRelationshipキーが追加されています。そのキーは、埋め込まれたデータが添付されているコンテンツにどのように関連するかを述べており、Factur-Xが義務付けている値はAlternativeです

この選択は重要です。なぜなら、他の値はドキュメントについて誤ったことを主張してしまうからです。Sourceは、XMLが可視コンテンツの生成元となったマテリアルであり、ページが派生するマスターであることを意味します。Supplementは、XMLがページに表示されているものを超える情報(レンダリングに含まれていない追加情報)を追加することを意味します。どちらもFactur-Xのインボイスではありません。XMLとページは、1つのインボイスの2つの等価な表現であり、同じ法的な内容を2つの形式で保持しています。Alternativeは、まさにそれ(可視コンテンツの等価な代替表現)を意味する値です。Factur-Xファイルで他のリレーションシップを読み取ったバリデーターは、それを拒否します。リレーションシップは添付ファイルが何のためのものかについての機械可読な主張であるため、これは正しいことと言えます

プロファイルカタログ

PDFlibPasに同梱されているE-Invoiceサンプルは、InvoiceModel.pasのレコードの配列として定義された6つのプロファイルにわたって、同じ生成パスを駆動します。各プロファイルは、ライターが必要とする値を保持しています。表示名、埋め込みファイル名、適合性レベル、/AFRelationship、バージョン、オプションの国コード、およびXMLがそのドキュメントコンテキスト内でアナウンスするGuidelineID URNです

その6つとは、Factur-X EN16931、Factur-X BASIC、フランス向けのFactur-X EXTENDED、XRechnung 3.0、ZUGFeRD 1.0 COMFORT、そしてZUGFeRD 2.0 BASICです。GuidelineIDは、どのプロファイルが期待されるかをレシーバーに正確に伝えるフィールドであり、その値は特定のものです。Factur-X EN16931はurn:cen.eu:en16931:2017をアナウンスします。XRechnung 3.0はurn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0をアナウンスします。ZUGFeRD 2.0 BASICはurn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p0:basicをアナウンスします。埋め込みファイル名も契約の一部です。Factur-Xプロファイルはfactur-x.xmlを埋め込み、XRechnungはxrechnung.xmlを埋め込み、ZUGFeRDプロファイルはZUGFeRD-invoice.xmlまたはzugferd-invoice.xmlを埋め込みます。レシーバーはインボイスを見つけるために添付ファイル名をスキャンするため、ファイル名は表面的なものではありません

カタログ内の1つの詳細は、注意深く読む価値があります。ほとんどのプロファイルはAlternativeリレーションシップを使用しますが、サンプルのXRechnung 3.0エントリはSourceを使用します。この2つのフォーマットは異なるバリデーターと規則に応答するため、サンプルは単一の値をハードコーディングするのではなく、カタログから各プロファイルのリレーションシップを設定します。定数ではなくプロファイルごとのフィールドが存在するのはこのためです

ZUGFeRD 1.0の罠

すべてのプロファイルが、いくつのオプションフィールドにデータを入力するかにわずかな違いがあるだけのEN 16931 Cross Industry Invoiceであると思い込みたくなります。それは6つのうち5つには当てはまります。しかし、ZUGFeRD 1.0 COMFORTには当てはまりません。その理由は表面的というよりは構造的なものです

最新のプロファイルは、ルート要素がrsm:CrossIndustryInvoiceであるネームスペースバージョン:100のUN/CEFACT Cross Industry Invoiceを出力します。ZUGFeRD 1.0はそのスキーマよりも前のものです。それはネームスペースバージョン:1p0の2014年のCrossIndustryDocumentであり、そのルート要素はrsm:CrossIndustryDocumentです。ネームスペースURNが異なり、ルート要素が異なり、要素ツリーが全体にわたって異なります。:1p0スキーマはApplicableSupplyChainTradeAgreementApplicableSupplyChainTradeDelivery、およびApplicableSupplyChainTradeSettlementの下にデータをグループ化しますが、:100はApplicableHeaderTradeAgreementApplicableHeaderTradeDelivery、およびApplicableHeaderTradeSettlementを使用します。この命名は誤解を招くほど似ていますが、壊れるほど十分に異なっています

プロファイル名にあるCOMFORTという単語は、データの豊富さ(完全な明細、税の内訳、および支払い条件を備えた自動化グレードのプロファイルであること)を表しており、どのスキーマがそれを伝達するかを表しているわけではありません。したがって、:100のドキュメントを取得して、それをZUGFeRD 1.0用にラベルを付け直すことはできません。サンプルは、各プロファイルレコードのフラグと2つの別々のビルダー関数を使用してこれを処理し、XMLが生成される前に正しいものを選択します

function BuildInvoiceXMLText(const AProfile: TeInvoiceProfile;
  const Data: TInvoiceData): string;
begin
  // XMLFamily = 1 はレガシーなZUGFeRD 1.0 :1p0スキーマを意味する。それ以外の
  // すべてのプロファイルは最新のUN/CEFACT :100 Cross Industry Invoiceである。
  if AProfile.XMLFamily = 1 then
    Result := BuildZUGFeRD1Text(AProfile, Data)
  else
    Result := BuildCII100Text(AProfile, Data);
end;

この分割は実装上の些細なことではありません。:100ツリーをZUGFeRD 1.0レシーバーにフィードすると、ルート要素でスキーマ検証に失敗するドキュメントが生成されるため、この2つのファミリーは、どちらを書き込んでいるかを認識しているコードによって構築されなければなりません

PDF/A-3レベルの選択

PDF/A-3には3つの適合性レベルがあり、PDFlibPasはSetPDFAModeを通じてそれらを選択します。モード5はPDF/A-3bであり、信頼性の高い視覚的再現を保証するレベルです。モード6はPDF/A-3aであり、レベルaのタグ付き構造(tagged-structure)およびアクセシビリティ要件を追加します。モード7はPDF/A-3uであり、すべてのテキストがUnicodeにマッピングされることを要求します。モードを有効にすると、ライブラリに組み込まれているsRGB出力インテントも埋め込まれます。これは、レンダリングされる色がデバイスに依存するのではなく定義されるようにPDF/Aが要求するカラー特性です

ほとんどのインボイスフローは3bで実行されます。これは、忠実な可視ページと埋め込みXMLには十分です。組み込みのプロファイルではなく明示的なICCプロファイルが必要な場合は、モードの設定後にLoadOutputIntentProfileでスワップインします。サンプルは、この方法でリポジトリのsRGBプロファイルをロードし、ファイルに到達できない場合は組み込みのインテントにフォールバックするため、出力インテントは常に存在します

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

  // オプション:組み込みのsRGBインテントを明示的なICCプロファイルにスワップする。
  if PDF.LoadOutputIntentProfile(ICCFile, 'DeviceRGB') <> 1 then
    { SetPDFAModeが埋め込んだ組み込みのsRGBインテントにフォールバックする };
finally
  // ... ドキュメントの構築を続行する
end;

ハイブリッドインボイスの構築

コンテナが構成されたら、残りは順番に3つのステップです。PDF/A-3モードを設定し、人間が読めるページを描画し、次にXMLを関連ファイルとして添付します。可視ページは通常のコンテンツです。覚えておくべき1つの制約は、PDF/Aが埋め込まれていないStandard 14フォントを禁じているため、ページは組み込みフォントを参照するのではなく、実際のフォントフェースを埋め込む必要があるということです

添付は1回の呼び出しです。AddFacturXAssociatedFileFromStringは、生のUTF-8 XMLバイトとプロファイルメタデータを受け取り、埋め込みファイルストリームを書き込み、PDF/A-3が要求するカタログ/AF配列にそれを登録し、/AFRelationshipを適用し、ドキュメントをFactur-X、ZUGFeRD、またはXRechnungとして識別するXMP電子インボイスメタデータを生成します。また、XMLのガイドラインIDがあなたが要求した適合性レベルと一致することもチェックするため、構築したXMLと名前を付けたプロファイルの不一致がキャッチされ、暗黙のうちに出荷されることはありません

// 1. PDF/A-3モードと出力インテントはすでに設定されている。
// 2. 可視ページを描画する(実際のTrueTypeフォントを埋め込む)。
DrawInvoicePage(PDF, AProfile, Data);

// 3. プロファイルとして正しいXMLを構築し、それを関連ファイルとして添付する。
//    関連ファイルの /AFRelationship = Alternative。
InvoiceXML := BuildInvoiceXML(AProfile, Data);   // UTF-8バイトのAnsiString
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);

データパスにおける1つの微妙な点は、エンコーディングです。埋め込まれたXMLはencoding="UTF-8"を宣言しており、メソッドはそのバイトをAnsiStringとして受け取るため、非ASCIIの売り手または買い手の名前は生のUTF-8オクテットとして呼び出しに到達しなければなりません。システムのANSIコードページを介した単純なキャストは、それらの文字を破損させ、XMLが自分自身の宣言と一致しなくなったインボイスを気付かれないように生成してしまうでしょう。サンプルは、バイトを引き渡す前に明示的にUTF-8にエンコードします。これは、Unicodeのstringからバイト指向のPDF APIに供給するための安全な方法です

認識されている電子インボイスプロファイルではないXMLを添付する場合は、AddPDFA3AssociatedFileFromStringがその汎用的なカウンターパートとなります。これはファイル名、MIMEタイプ、説明、リレーションシップ、およびバイトを受け取り、インボイス固有のメタデータやガイドラインのチェックなしで、プレーンなPDF/A-3関連ファイルを書き込みます。補足データにはこれを使用してください。インボイスにはFactur-Xメソッドを使用し、プロファイルメタデータとガイドラインの一致があなたに代わって書き込まれるようにします

ドキュメントが生成されたら、次の疑問は、それがPDF/Aとアクセシビリティの検証に合格するかどうか、そしてコンプライアンスを破ることなく署名できるかどうかです。これらは、PDF/AおよびPDF/UAプリフライトのチュートリアルコンプライアンスと署名のワークベンチでカバーされています。これらはすべて、電子インボイスパスの基盤となるPDF/A、タグ付け、およびドキュメントプロパティのAPIとともに、PDFlibPas Delphi PDF Libraryの一部として出荷されています