技術記事

HotPDFを使用したDelphiでのZUGFeRDおよびFactur-X電子請求書の処理

HotPDFは、DelphiおよびC++Builderから、ZUGFeRDおよびFactur-X電子請求書の生成、検出、および検証を行います。AddFacturXAssociatedFile は EN 16931 請求書XMLをPDF/A-3コンテナに埋め込み、DetectFacturXInvoice は受信したハイブリッド請求書を認識し、HPDFValidateEInvoice は埋め込まれたXMLに対して公式の EN 16931 Schematronビジネスルールを実行します。この記事では、これら3つのステップを詳しく説明し、検証エンジンがカバーしていない制限事項についても率直に解説します。

この機能セットが必要とされる背景にある圧力は、技術的なものではなく規制によるものです。ドイツとフランスでは、国内のB2B取引において構造化された電子請求書(e-invoicing)の義務化が段階的に進んでおり、どちらの義務化も同じハイブリッドモデルに収束しています。すなわち、ERPがすでに作成しているPDF請求書の内部に、機械読み取り可能なXMLの双子(ツイン)を同梱しなければなりません。現在プレーンなPDF請求書を出力しているDelphiの会計またはERPアプリケーションには、Factur-XまたはZUGFeRDの出力を開始するまでの厳しい猶予期限が設けられており、これら2つの名称の違いはマーケティングで言われているほど大きくありません。Factur-XとZUGFeRDは、2つのラベルの下で公開されている同じ日仏共同規格です。

PDFが有効なFactur-X請求書となる要件

有効なFactur-X請求書は、正確に1つの構造化された請求書XMLを関連ファイルとして埋め込み、ドキュメントカタログからそれをリンクし、XMP拡張メタデータで宣言したPDF/A-3ファイル(ISO 19005-3)です。3つのレイヤーが合意している必要があります。第一に、コンテナはPDF/A-3に準拠している必要があります。PDF/A-3は、PDF/A-1やPDF/A-2とは異なり、あらゆる種類のファイルの埋め込みを許可するアーカイブプロファイルです。第二に、標準プロファイルの場合は factur-x.xml、ドイツのXRechnungバリアントの場合は xrechnung.xml と名付けられた請求書XMLが、カタログの /AF 配列および /Names /EmbeddedFiles ツリーに登録され、添付ファイルの内容を示す AFRelationship エントリが設定されていなければなりません。Alternative は、XMLが可視の請求書の同等な表現であることを意味し、这是ほとんどのプロファイルが要求するセマンティクスです。第三に、Factur-XのXMP拡張スキーマは、埋め込まれたファイル名、その適合性レベル、およびドキュメントタイプを宣言しなければなりません。これにより、受信システムはXMLを解析することなく請求書を分類できます。

XMLペイロード自体は、コアな請求書(購入者、販売者、ラインアイテム、税分析、支払額など)を定義し、それぞれにビジネス用語番号(請求書番号のBT-1、通貨のBT-5など)を割り当てた欧州規格(European Norm)である EN 16931 セマンティックモデルに従います。したがって、EN 16931 の検証は2つの異なるレイヤーで行われます。XMLスキーマ検証は、ドキュメントが正しい要素名を持つ整形式のCIIであることをチェックします。一方、Schematronビジネスルールは、コンテンツが一貫していること(BR-CO-15を満たしているか、請求書総額がラインの純額と消費税の合計と本当に一致しているか、宣言されたプロファイルに対して必須の用語が存在しているかなど)をチェックします。ファイルがスキーマ検証に合格しても、請求書としては無効である場合があるため、このビジネスルールレイヤーが存在します。

ZUGFeRD 2.5が新しいコンテナを必要としない理由

ZUGFeRD 2.5は、PDFコンテナレベルでは何も変更しません。ガイドラインのURN、XMPスキーマ、およびバージョン(Version)トークンは、ZUGFeRD 2.3 / Factur-X 1.0と同一です。これは多くの開発者を驚かせる事実なので、出典を引用する価値があります。Factur-X 1.09仕様書には、「PDF/A拡張スキーマのURIに含まれるバージョン番号は、XMLデータ仕様のバージョン番号とは無関係であり、Factur-X請求書インスタンスのバージョン管理は1.0のままである」と記載されています。これはXMPの fx:Version および factur-x.xml のBT-24の両方に適用されます。公式のZUGFeRD 2.5サンプルコーパスもこれを裏付けており、すべてのサンプルが fx:Version1.0 とした urn:factur-x.eu:1p0 ガイドラインURNの下で factur-x.xml を埋め込んでいます。2.5で実際に追加されたのは、XMLデータモデルの内部(サブ請求書ラインやバッチ識別子などの新しい要素)であり、これはアプリケーションが生成するビジネスレイヤーであって、PDFライブラリが管理するコンテナレイヤーではありません。

HotPDFはこの事実を直接反映しています。AddFacturXAssociatedFileVersion 引数を受け取り、XMPを書き出す前に HPDFFacturXNormalizeVersion を介して正規化するため、呼び出し元が 2p5 エイリアスを渡しても、仕様準拠のトークンを出力します。これにより、どのバリデータも受け入れない架空の 2p5 XMPバージョンが作成されるのを防ぎます。検出ヘルパーも読み込み時に 2p5 を同等として扱うため、往復処理の対称性が保たれます。もしライブラリやツールが「特別なZUGFeRD 2.5コンテナを書き出す」と言っているなら、仕様書自体がそのようなものは存在しないと述べています。

Delphiからの請求書XMLの埋め込み

THotPDF.AddFacturXAssociatedFile は、1回の呼び出しで通常のPDF/A-3生成処理をFactur-X請求書に変換します。XMLバイトを関連ファイルとして埋め込み、factur-x.xml をカタログの /AF 配列および /Names /EmbeddedFiles ツリーに登録し、一致するFactur-X XMP拡張メタデータを出力します。人間が読める請求書ページは通常のHotPDFキャンバスAPIで描き、コンテナの処理は BeginDocEndDoc の間のわずか1行で済みます。

var
  Pdf: THotPDF;
  InvoiceXML, UBLXML: TBytes;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'invoice-2026-0042.pdf';
    Pdf.PDFACompliance := '3B';            // PDF/A-3b container
    Pdf.BeginDoc;
    // embed the EN 16931 CII invoice XML as factur-x.xml
    InvoiceXML := BuildInvoiceXML;         // produced by your business layer
    Pdf.AddFacturXAssociatedFile(InvoiceXML, 'EN 16931');
    // optional: attach a UBL view as a supplementary representation
    UBLXML := BuildUBLXML;
    Pdf.AddUBLSupplementaryFile(UBLXML);   // factur-xubl.xml, Alternative
    // ... draw the visible invoice page here ...
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

デフォルト値は仕様に従います。ファイル名は factur-x.xml、関係(Relationship)は Alternative、バージョンは 1.0 です。AddUBLSupplementaryFile は、同じ請求書のUBL表現を factur-xubl.xml という名前、MIMEタイプ application/xml、関係 Alternative の2番目の添付ファイルとして同乗させることを許可する、Factur-X 1.09仕様の第6.4項を実装しています。ここでは順序が重要であり、HotPDFは仕様が暗示する制約を強制します。UBLファイルは補完的なビュー(Supplementary View)であり、決して主要な請求書にはなり得ないため、AddFacturXAssociatedFile がCIIマスターを登録した後に呼び出す必要があります。描画されたページと埋め込まれたXMLが同じ請求書を指していなければならない点に注意してください。PDFに表示されている総額とXMLにエンコードされている総額が異なるケースは、ハイブリッド請求書が防ぐために設計されたまさにその失敗モードです。出力インテント(OutputIntent)やフォント埋め込みを含む、このワークフローのPDF/A-3の側面については、姉妹記事のDelphiでのPDF/A、PDF/X、およびPDF/UA検証に関する記事で詳しく説明されています。

受信したPDFからFactur-X請求書を検出する方法

THotPDF.DetectFacturXInvoice は、受信側の義務に対応します。任意のPDFをロードし、それがハイブリッド請求書であるかどうか、どのプロファイルが宣言されているか、どの添付ファイルを保持しているかを1回の呼び出しで判断します。この関数は、埋め込まれたXMLファイル名、XMPで宣言されたドキュメントファイル名とタイプ、バージョントークン、適合性レベル、ガイドラインURN、および AFRelationship 値を含む THPDFFacturXInvoiceInfo レコードを返します。さらに2つのフィールドが補完的なビューをレポートします。UBLFileNamefactur-xubl.xml 添付ファイルが存在する場合に設定され、EDIFACTFileName はEDIFACTビューが同乗している場合に設定されます。どちらも検出の判定には影響しません。なぜなら、補完的な表現は主要なCII請求書なしには存在し得ないからです。

var
  Reader: THotPDF;
  Info: THPDFFacturXInvoiceInfo;
begin
  Reader := THotPDF.Create(nil);
  try
    if Reader.LoadFromFile('incoming-invoice.pdf') <= 0 then
      raise Exception.Create('No pages loaded.');
    if Reader.DetectFacturXInvoice(Info) then
    begin
      Writeln('Invoice XML:    ', string(Info.FileName));
      Writeln('Conformance:    ', string(Info.ConformanceLevel));
      Writeln('Guideline URN:  ', string(Info.GuidelineID));
      Writeln('Relationship:   ', string(Info.AFRelationship));
      if Info.UBLFileName <> '' then
        Writeln('UBL view:       ', string(Info.UBLFileName));
    end
    else
      Writeln('Not a Factur-X / ZUGFeRD hybrid invoice.');
  finally
    Reader.Free;
  end;
end;

検出は、ファイル名から推測するのではなく、準拠するバリデータが読み取るシグナル(XMP拡張スキーマと関連ファイル登録)を読み取ります。また、ロードされたドキュメントオブジェクトは、検査や修正のためにXMPおよびInfoディクショナリのメタデータも公開します。これについては、ロードされたPDFドキュメントのメタデータ編集に関する記事で説明されています。

ビジネスルールかスキーマか:EN 16931検証が意味すること

HPDFEInvoiceValidator ユニットのスタンドアロン関数である HPDFValidateEInvoice は、2つの検証レイヤーを順に実行します。まず ValidateFacturXInvoice によるコンテナ構造チェック、続いて抽出された請求書XMLに対する EN 16931 Schematronビジネスルールです。HotPDFは、公式の5つのFactur-X 1.09ルールファイル(MINIMUM、BASIC WL、BASIC、EN 16931、およびEXTENDED)を Lib/resources/Schematron/ の下に同梱しており、HPDFSchematronFileForProfile が検出された適合性レベルに一致するファイルを自動的に選択するため、EN 16931 請求書は自動的に Factur-X_1.09_EN16931.sch に対してチェックされます。この関数は、コンテナが有効で、かつビジネスルールが1つもトリガーされなかった場合にのみ True を返します。

uses HPDFEInvoiceValidator;

var
  Reader: THotPDF;
  Report: THPDFEInvoiceValidationReport;
  I: Integer;
begin
  Reader := THotPDF.Create(nil);
  try
    Reader.LoadFromFile('incoming-invoice.pdf');
    if HPDFValidateEInvoice(Reader, SchematronDir, Report) then
      Writeln('Container and business rules: OK')
    else
      Writeln('Validation found issues.');
    if Report.BusinessRulesEvaluated then
    begin
      Writeln(Report.BusinessSummary.Evaluated, ' rules evaluated, ',
        Report.BusinessSummary.Skipped, ' skipped (XPath 2.0)');
      for I := 0 to High(Report.BusinessRules) do
        if (not Report.BusinessRules[I].Skipped) and
           (Report.BusinessRules[I].Severity = stsError) then
          Writeln(string(Report.BusinessRules[I].RuleID), ': ',
            string(Report.BusinessRules[I].Message));
    end;
  finally
    Reader.Free;
  end;
end;

BusinessRules の各エントリは、アサーションテキストから解析されたルール識別子(BR-45BR-CO-17 などの EN 16931 命名規則)に加えて、ルールのコンテキストXPath、テスト式、およびSchematronのflag属性を反映した深刻度(severity)を保持します。これにより、レポートが実用的になります。単なる合否ではなく、どのビジネス用語が一貫していないのかが正確に示されるため、顧客から請求書が差し戻された理由を尋ねられたときにサポートチームが即座に対応できます。アーカイブPDFの構造化適合レポートをすでに生成しているチームは、これらの結果をPDFプリフライトレポート自動化に関する記事で説明されているのと同じパイプラインに統合できます。

検証エンジンがカバーしていない制限事項について

Schematronエンジンは、XPath 1.0を評価するMSXMLに基づいて構築されています。EN 16931 ルールファイルは queryBinding="xslt2" を宣言しており、424個のアサーションを保持していますが、そのほとんどは string-lengthsubstring-after などのXPath 1.0関数のみを使用しているため、これらはネイティブに実行されます。少数は xs:decimal キャストや exists() などのXPath 2.0の構造に依存しています。HotPDFは、既知のXPath 2.0トークンについて各テスト式を事前にスキャンし、それらのルールを Skipped (スキップ)としてマークし、警告なしに評価が誤動作するのを防ぐために深刻度情報(Severity Info)を出力します。サマリーレコードは、評価されたルール数とスキップされたルール数を個別に報告するため、ログは常にルールセットのどれだけが実行されたかを正確に示します。MSXMLバインディングが使用できないDelphi 7では、コンテナの判定を妨げることなくエンジンが使用不可であることを報告するスタブがユニットによってコンパイルされます。フルエンジンにはDelphi XE2以降が必要です。

第二の制限はさらに重要です。HPDFValidateEInvoice の合格結果は、コンテナの構造が準拠しており、評価されたビジネスルールがトリガーされなかったことを意味するだけであり、税法上の準拠を保証するものではありません。各国の追加規則、受信者固有の要件、および請求書の内容に関する法的義務は、EN 16931 コアの上のレイヤーにあり、どのようなライブラリの範囲外の事項です。このバリデータは、構造的に壊れた請求書が送信されるのを防ぎ、受信した請求書の不整合にフラグを立てるためのゲートとして扱い、リリースのパイプラインでは認証が要求されたときに完全なXPath 2.0バリデータを使用し、法的準拠の確認は税理士に任せてください。ここで説明した埋め込み、検出、および検証APIは、DelphiおよびC++Builder向けの標準のHotPDF Componentの一部として提供されており、ZUGFeRDおよびFactur-Xの全7プロファイルに対応する完全な電子請求書サンプルが含まれています。