Рахунок-фактура Factur-X або ZUGFeRD - це два документи під одним іменем файлу. Зовнішній документ - це контейнер PDF/A-3, який програма для читання архівів повинна підтримувати наступні десять років. Внутрішній документ - це рахунок-фактура у форматі XML, який облікова система покупця повинна аналізувати відповідно до EN 16931. Помилка, яка призводить до випуску недійсних рахунків у робоче середовище, полягає у впевненості, що правильне створення першого документа автоматично забезпечує правильність другого. Це не так. Файл може бути бездоганним PDF/A-3 і водночас містити XML, який не прийме жоден податковий орган, або ж він може містити еталонний XML EN 16931 всередині контейнера, який не проходить перевірку для архівування. Ці два рівні перевіряються двома різними інструментами, які нічого не знають один про одного, а реальний конвеєр розробки має задовольняти вимоги обох
Два валідатори, два різні запитання
veraPDF - це еталонна реалізація для PDF/A. Направте його на рахунок-фактуру, і він відповість на одне запитання: чи відповідає цей файл стандарту PDF/A-3. Він перевіряє те, що важливо для ISO 19005-3. Чи вбудований кожен шрифт. Чи є OutputIntent. Чи вказують метадані XMP правильну частину та рівень відповідності. Для електронного рахунку він також перевіряє механізм пов'язаних файлів, який вимагає PDF/A-3, оскільки XML передається як вбудований файл за допомогою /AFRelationship і запису в масиві /AF каталогу документа. veraPDF нічого не говорить про те, чи збігається загальна сума рахунку, оскільки це не входить до його компетенції
Mustang - це валідатор з відкритим кодом від Mustangproject. Він ставить ортогональне запитання: чи є вбудований XML дійсним рахунком-фактурою. Він перевіряє XML на відповідність схемі заявленого профілю, а потім застосовує бізнес-правила EN 16931 і специфічні для країни набори правил, накладені зверху, серед яких CIUS для XRechnung. Він перевіряє, чи присутній ідентифікатор платника ПДВ продавця, коли суми цього вимагають, чи суми знижок і зборів узгоджуються із загальною сумою документа, чи URN профілю в XML збігається з тим, що заявлено у файлі. Mustang не хвилює, чи містить навколишній PDF вбудовані шрифти, оскільки це робота veraPDF
Жоден інструмент не є розширеною версією іншого. veraPDF пропускає структурно ідеальний контейнер навколо безглуздого XML. Mustang пропускає ідеальний XML, загорнутий у контейнер з відсутнім OutputIntent. Кожен з них виявляє саме той клас дефектів, до якого інший сліпий, і це є головною причиною того, чому серйозний механізм перевірки запускає обидва і вважає файл готовим до відправки лише тоді, коли обидва інструменти погоджуються
Матриця перевірки
Щоб довести, що бібліотека створює файли, які проходять обидва етапи, механізм будує матрицю. Шість профілів рахунків-фактур охоплюють діапазон, з яким європейський конвеєр стикається на практиці: Factur-X EN 16931, Factur-X BASIC, варіант Factur-X EXTENDED France B2B, XRechnung 3.0, ZUGFeRD 1.0 COMFORT та ZUGFeRD 2.0 BASIC. Кожен профіль генерується відповідно до двох рівнів відповідності PDF/A: 3b і 3u, оскільки вимоги рівня B і рівня U розходяться щодо відображення Unicode, і файл, який проходить один рівень, може не пройти інший. Шість профілів помножені на два рівні дають дванадцять файлів, кожен з яких створюється без візуального інтерфейсу (headless) тим самим шляхом коду, що й зразок GUI, тому артефакти, що тестуються, не налаштовуються вручну для тесту
Генератор записує всі дванадцять файлів, а сценарій передає кожен з них обом валідаторам. Під час першого повного запуску veraPDF пропустив усі дванадцять. Механізм контейнера був правильним за всіма параметрами: пов'язані файли зареєстровані, відповідність XMP заявлена, output intents на місці. Mustang пропустив вісім. Чотири рахунки-фактури були структурно дійсними файлами PDF/A-3, що містили XML, який валідатор бізнес-правил відхилив, а це саме та проблема, для виявлення якої існує підхід з двома інструментами. Якби механізм довіряв лише veraPDF, ці чотири файли виглядали б завершеними
Два виправлення, які усунули розбіжності
Чотири помилки Mustang виникли з двох різних причин, і виправлення кожної з них - це деталь, яку варто знати, перш ніж ви самі генеруватимете ці профілі
Першим був профіль Factur-X EXTENDED France B2B. Оригінальний генератор передав внутрішню мітку як рівень відповідності та внутрішній URN як вказівку, і Mustang відхилив файл із помилкою недійсного значення відповідності, за якою послідувала помилка непідтримуваного типу профілю. Причина полягає в тому, що поле fx:ConformanceLevel у XMP не є місцем для вільного введення тексту для вашої власної назви профілю. Factur-X визначає для нього рівно п'ять стандартних значень: MINIMUM, BASIC WL, BASIC, EN 16931 та EXTENDED. Що стосується метаданих XMP, B2B рахунок-фактура, специфічний для Франції, все ще залишається документом профілю EXTENDED. Французький характер рахунку не виражається шляхом винайдення шостого значення відповідності. Він виражається кодом країни FR та ідентифікатором вказівки всередині XML, який повинен містити префікс urn:cen.eu:en16931:2017#conformant#, що позначає CIUS, відповідний до EN 16931. Передача стандартного значення EXTENDED з FR як кодом країни та правильним URN вказівки зробила файл відповідним стандарту
У API бібліотеки це виклик AddFacturXAssociatedFileFromString із узгодженими відповідністю, країною та вказівкою. Аргумент рівня відповідності містить стандартний токен, аргумент коду країни містить FR, а URN вказівки знаходиться в байтах XML, які ви передаєте
var
FileID: Integer;
begin
PDF.SetPDFAMode(5); // PDF/A-3b
PDF.NewDocument;
// ... draw the human-readable invoice page ...
// ExtendedXML carries an EN 16931 guideline URN of the form
// urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended
FileID := PDF.AddFacturXAssociatedFileFromString(
ExtendedXML,
'EXTENDED', // standard fx:ConformanceLevel, not an internal label
'factur-x.xml',
'Factur-X EXTENDED invoice',
'Alternative', // /AFRelationship
'1.0',
'FR'); // France B2B marked by country code, not by conformance
if FileID = 0 then
raise Exception.Create('Factur-X attachment rejected');
PDF.SaveToFile('02_Factur-X-EXTENDED-FR_PDFA-3b.pdf');
end;
Другою причиною був профіль ZUGFeRD 1.0 COMFORT, і він не мав нічого спільного з метаданими. ZUGFeRD 1.0 перевіряється на відповідність XSD :1p0, який суворіше ставиться до кількості елементів, ніж припускають текстові описи. XSD вимагає, щоб підсумок розрахунків у заголовку ram:SpecifiedTradeSettlementMonetarySummation містив ram:ChargeTotalAmount та ram:AllowanceTotalAmount рівно по одному разу. Згенерований XML пропускав обидва, тому Mustang повідомив, що елементи повинні зустрічатися рівно один раз. Вони не є необов'язковими, коли схема вказує, що minOccurs дорівнює один. Виведення обох у порядку послідовності XSD, одразу після ram:LineTotalAmount, зі значенням 0.00, коли немає зборів або знижок, задовольнило схему. Нуль - це присутній елемент; відсутній елемент - це порушення схеми. З цими двома виправленнями матриця досягла дванадцяти з дванадцяти на Mustang, залишаючись дванадцятьма з дванадцяти на veraPDF
Поля XRechnung, які перетворюють недійсний файл на дійсний
XRechnung заслуговує на окрему згадку, оскільки його німецький CIUS додає бізнес-правила, які відсутні в базовому наборі EN 16931, і вони викликають помилки у спосіб, який на перший погляд здається нормальним для документа. Два з них стосуються електронних адрес. BT-34 - це електронна адреса продавця, а BT-49 - електронна адреса покупця, тобто кінцеві точки маршрутизації, які німецький портал державного сектору використовує для доставки та підтвердження рахунку. Базова модель EN 16931 розглядає їх як необов'язкові. XRechnung - ні. Пропустіть будь-яку з них, і рахунок-фактура буде правильно сформована, відповідатиме схемі, але буде відхилена
Третє - це правило BR-DE-6, яке вимагає присутності контактного номера телефону продавця. Це таке поле, яке розробник пропускає, оскільки воно більше схоже на представлення, ніж на дані, і його відсутність призводить до помилки перевірки, яка вказує на групу контактів продавця, а не на щось очевидно відсутнє. Надання BT-34, BT-49 та номера телефону продавця - це те, що перетворює файл XRechnung з недійсного на дійсний у Mustang, і жодне з цього не змінює нічого з того, що бачить veraPDF, оскільки всі три знаходяться в XML
Підключення виводу бібліотеки до валідатора
Архітектурний сенс цього механізму можна узагальнити для будь-якої бізнес-системи. Бібліотека PDF записує відповідний контейнер і вбудовує XML. Вона не намагається і не повинна намагатися бути авторитетом з бізнес-правил EN 16931. Функція ValidateFacturXInvoice у бібліотеці перевіряє цілісність контейнера, чи збігаються масив /AF каталогу, дерево імен вбудованих файлів, DocumentFileName у XMP, профіль, вказівка та /AFRelationship, але вона не перевіряє податкові коди і не звіряє суми. Правильний розподіл праці полягає в тому, щоб бізнес-система витягувала XML і передавала його спеціалізованому валідатору рахунків-фактур, точно так само, як механізм передає його в Mustang
Зворотне читання файлу повідомляє вам, що насправді було записано. DetectFacturXInvoice повідомляє, чи було розпізнано рахунок-фактуру, а GetFacturXInvoiceInfo зчитує поля метаданих за тегами: тег 1 - це ім'я вбудованого файлу, тег 2 - DocumentFileName у XMP, тег 5 - рівень відповідності, тег 6 - ідентифікатор вказівки, а тег 7 - /AFRelationship. Підтвердження того, що рівень відповідності, який ви прочитали, є стандартним токеном, а не внутрішньою міткою, є найдешевшим способом виявити помилку EXTENDED до того, як файл покине вашу збірку
function ExtractAndInspect(const PdfPath: string): AnsiString;
var
Profile, Guideline: WideString;
begin
Result := '';
PDF.LoadFromFile(PdfPath);
if PDF.DetectFacturXInvoice = 1 then
begin
Profile := PDF.GetFacturXInvoiceInfo(5); // fx:ConformanceLevel
Guideline := PDF.GetFacturXInvoiceInfo(6); // XML guideline ID
Writeln('Profile: ', Profile);
Writeln('Guideline: ', Guideline);
// Hand the raw XML to a dedicated EN 16931 / Mustang validator.
Result := PDF.ExtractFacturXXMLToString;
end;
end;
ExtractFacturXXMLToString повертає необроблені байти XML як AnsiString, готові для запису у файл або потокового передавання в процес валідатора. У тестовому середовищі цією метою є Mustang, який викликається через його jar-файл командного рядка, при цьому veraPDF запускається в тому самому проході над тим самим файлом. Підключення невелике: консольний генератор EInvoiceValidation.dpr записує дванадцять файлів, використовуючи спільну модель рахунку-фактури зі зразка, а сценарій run-validation.ps1 керує обома валідаторами в каталозі виводу і друкує таблицю проходження та помилок. Такий самий двоетапний формат, генерація за допомогою бібліотеки та перевірка зовнішніми валідаторами, - це те, що завдання безперервної інтеграції повинно запускати при кожній зміні в генерації рахунків-фактур, оскільки єдиний спосіб дізнатися, що файл задовольняє обидва рівні, - це запитати обидва інструменти
Якщо ваш конвеєр також повинен сертифікувати контейнер перед підписанням, етап попередньої перевірки (preflight) цієї роботи описано в нашому огляді попередньої перевірки PDF/A та PDF/UA у Delphi, а ширший процес сертифікації з подальшим підписанням описано в робочому середовищі відповідності та підписання. Обидва спираються на той самий шлях генерації, який постачається як частина Delphi PDF Library для Delphi та C++Builder, поряд із API для PDF/A, пов'язаних файлів та метаданих, що використовуються тут