Техническая статья

Электронные счета ZUGFeRD и Factur-X в Delphi с помощью HotPDF

HotPDF позволяет генерировать, обнаруживать и валидировать электронные счета ZUGFeRD и Factur-X из Delphi и C++Builder. Метод AddFacturXAssociatedFile встраивает XML-код счета EN 16931 в контейнер PDF/A-3, функция DetectFacturXInvoice распознает входящий гибридный счет, а HPDFValidateEInvoice запускает официальные бизнес-правила Schematron стандарта EN 16931 для встроенного XML. В этой статье рассматриваются все три шага, а также честно описываются ограничения валидатора

Сценарий, требующий внедрения этих функций, носит скорее нормативный, нежели технический характер. Германия и Франция поэтапно вводят обязательное использование структурированных электронных счетов для внутренних транзакций B2B, и обе эти инициативы сходятся на одной гибридной модели: PDF-счет, который уже создает ваша ERP-система, должен нести внутри себя машиночитаемую копию в формате XML. Для учетных систем или ERP на Delphi, которые сегодня создают обычные PDF-счета, установлен жесткий срок для начала экспорта в Factur-X или ZUGFeRD, причем разница между этими двумя названиями меньше, чем утверждает маркетинг — Factur-X и ZUGFeRD представляют собой один и тот же франко-германский стандарт, публикуемый под двумя названиями

Что делает PDF валидным счетом Factur-X?

Валидный счет Factur-X — это файл PDF/A-3 (ISO 19005-3), который содержит ровно один структурированный XML-файл счета в качестве связанного файла, ссылается на него из каталога документов и декларирует его в метаданных расширения XMP. Три уровня должны согласовываться. Во-первых, контейнер должен соответствовать PDF/A-3, архивному профилю, который (в отличие от PDF/A-1 и PDF/A-2) разрешает встраивание файлов любого типа. Во-вторых, XML-код счета с именем factur-x.xml для стандартных профилей или xrechnung.xml для немецкого варианта XRechnung должен быть зарегистрирован в массиве каталога /AF и дереве /Names /EmbeddedFiles с записью AFRelationship, описывающей тип вложения: Alternative означает, что XML является эквивалентным представлением визуального счета, что требуется в большинстве профилей. В-третьих, схема расширения XMP Factur-X должна указывать имя встроенного файла, уровень его соответствия и тип документа, чтобы принимающая система могла классифицировать счет без разбора самого XML

Сам XML-документ соответствует семантической модели EN 16931 — европейскому стандарту, определяющему ядро счета: покупатель, продавец, позиции строк, детализация налогов, сумма к оплате, каждый из которых имеет номер бизнес-термина (BT-1 для номера счета, BT-5 для валюты и т. д.). Валидация EN 16931 проходит на двух уровнях. Проверка схемы XML контролирует, что документ представляет собой корректный CII с правильными именами элементов. Бизнес-правила Schematron проверяют связность содержимого: выполняется ли BR-CO-15 и действительно ли итог счета равен сумме чистых сумм строк плюс НДС, присутствуют ли обязательные термины для заявленного профиля. Файл может пройти проверку схемы, но оставаться невалидным счетом, поэтому и существует уровень бизнес-правил

Почему ZUGFeRD 2.5 не требует нового контейнера

ZUGFeRD 2.5 ничего не меняет на уровне PDF-контейнера: URN руководства, схема XMP и токен версии идентичны ZUGFeRD 2.3 / Factur-X 1.0. Это удивляет многих разработчиков, поэтому стоит процитировать первоисточник. Спецификация Factur-X 1.09 гласит, что номер версии в URI схемы расширения PDF/A не связан с номером версии спецификации XML-данных, и что версии экземпляров счетов Factur-X остаются равными 1.0 — как в XMP fx:Version, так и в BT-24 файла factur-x.xml. Официальный набор примеров ZUGFeRD 2.5 подтверждает это: каждый пример встраивает factur-x.xml под URN-руководством urn:factur-x.eu:1p0 с fx:Version равной 1.0. То, что на самом деле добавляет версия 2.5, находится внутри модели данных XML (новые элементы, такие как вложенные строки счетов и идентификаторы партий) — это бизнес-слой, генерируемый вашим приложением, а не слой контейнера, управляемый библиотекой PDF

HotPDF кодирует это соглашение напрямую. Метод AddFacturXAssociatedFile принимает аргумент Version и нормализует его через HPDFFacturXNormalizeVersion перед записью XMP, поэтому вызывающий код, передающий псевдоним 2p5, все равно генерирует соответствующий спецификации токен вместо изобретения версии XMP 2p5, которую не примет ни один валидатор. Вспомогательные средства обнаружения трактуют 2p5 как эквивалент при чтении, поэтому операции чтения/записи остаются симметричными. Если библиотека или инструмент утверждает, что записывает специальный контейнер ZUGFeRD 2.5, сама спецификация заявляет, что такого понятия не существует

Встраивание XML-кода счета из Delphi

Метод THotPDF.AddFacturXAssociatedFile превращает обычный процесс генерации PDF/A-3 в счет Factur-X за один вызов: он встраивает байты XML как связанный файл, регистрирует factur-x.xml в массиве каталога /AF и дереве /Names /EmbeddedFiles, а также выводит соответствующие метаданные расширения Factur-X XMP. Вы рисуете визуальную страницу счета с помощью стандартного API холста HotPDF; работа по созданию контейнера сводится к одной строке между BeginDoc и EndDoc

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, тип связи Alternative, версия 1.0. Метод AddUBLSupplementaryFile реализует раздел 6.4 спецификации Factur-X 1.09, который позволяет передавать UBL-представление того же счета в качестве второго вложения с именем factur-xubl.xml, MIME-типом application/xml и связью Alternative. Порядок имеет значение, и HotPDF контролирует это ограничение: файл UBL является дополнительным представлением, а не основным счетом, поэтому его нужно добавлять после того, как AddFacturXAssociatedFile зарегистрирует мастер-файл CII. Обратите внимание, что визуальная страница и встроенный XML должны описывать один и тот же счет — ситуация, когда в PDF отображается один итог, а в XML закодирован другой, является именно тем сбоем, для предотвращения которого и разработано гибридное выставление счетов. Особенности PDF/A-3 в этом процессе, включая OutputIntent и внедрение шрифтов, подробно описаны в сопутствующей статье о валидации PDF/A, PDF/X и PDF/UA в Delphi

Как обнаружить счет Factur-X во входящем PDF-файле?

Метод THotPDF.DetectFacturXInvoice отвечает за приемную сторону: загрузите любой PDF и узнайте за один вызов, является ли он гибридным счетом, какой профиль декларирует и какие вложения несет. Функция возвращает запись THPDFFacturXInvoiceInfo с именем встроенного XML-файла, объявленным в XMP именем и типом файла документа, токеном версии, уровнем соответствия, URN руководства и значением AFRelationship. Еще два поля сообщают о дополнительных представлениях: UBLFileName заполняется при наличии вложения factur-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?

Функция HPDFValidateEInvoice в модуле HPDFEInvoiceValidator последовательно запускает оба уровня валидации: сначала проверку структуры контейнера через ValidateFacturXInvoice, а затем бизнес-правила Schematron стандарта EN 16931 для извлеченного XML-кода счета. HotPDF поставляется с пятью официальными файлами правил Factur-X 1.09 (MINIMUM, BASIC WL, BASIC, EN 16931 и EXTENDED) в каталоге Lib/resources/Schematron/, а метод HPDFSchematronFileForProfile выбирает файл, соответствующий определенному уровню соответствия, поэтому счет EN 16931 автоматически проверяется по файлу Factur-X_1.09_EN16931.sch. Нативная функция возвращает 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-45, BR-CO-17 и другие правила схемы именования EN 16931), а также контекст правила XPath, тестовое выражение и уровень серьезности, отражающий атрибут флага Schematron. Это делает отчет практически применимым: вместо простого ответа "пройдено/не пройдено" вы получаете конкретный несогласованный бизнес-термин, что важно для службы поддержки, когда клиент спрашивает, почему счет был отклонен. Команды, которые уже создают структурированные отчеты о соответствии для архивных PDF-файлов, могут включить эти результаты в тот же конвейер, который описан в статье об автоматизации отчетов предпечатной проверки PDF

Честно о том, где валидатор останавливается

Движок Schematron построен на MSXML, который вычисляет XPath 1.0. Файл правил EN 16931 декларирует queryBinding="xslt2" и содержит 424 утверждения, большинство из которых используют только функции XPath 1.0 (такие как string-length и substring-after) — они выполняются нативно. Меньшая часть полагается на конструкции XPath 2.0 вроде приведения типов xs:decimal или функции exists(). HotPDF предварительно сканирует каждое тестовое выражение на наличие известных токенов XPath 2.0 и помечает эти правила как Skipped (пропущенные) с уровнем серьезности info вместо попытки вычисления, которая завершилась бы ошибкой. Сводная запись сообщает о количестве вычисленных и пропущенных правил отдельно, поэтому ваш лог всегда точно отражает, какая часть набора правил была выполнена. В Delphi 7, где привязки MSXML недоступны, модуль компилирует заглушку, сообщающую о недоступности движка без влияния на вердикт контейнера; полноценная работа движка требует Delphi XE2 или новее

Второе ограничение имеет еще большее значение. Зеленый результат от HPDFValidateEInvoice означает, что структура контейнера соответствует нормам и ни одно из проверенных бизнес-правил не было нарушено — это не является подтверждением налогового соответствия. Национальные правила расширения, требования конкретных получателей и юридические обязательства в отношении содержимого счетов стоят над ядром EN 16931 и находятся вне ведения какой-либо библиотеки. Относитесь к валидатору как к шлюзу, который не пускает структурно неверные счета в вашу исходящую почту и помечает противоречивые счета во входящей. Запускайте полноценный валидатор XPath 2.0 в вашем конвейере выпуска, когда этого требует сертификация, а вопросы соответствия законодательству оставьте вашим налоговым консультантам. API встраивания, обнаружения и валидации, показанные здесь, поставляются в составе стандартного комплекта HotPDF Component для Delphi и C++Builder с готовым примером электронного выставления счетов, охватывающим все семь профилей ZUGFeRD и Factur-X