Фактура 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 метаданните правилната част (part) и ниво на съответствие (conformance level). За електронна фактура той проверява и механизма за асоцииран файл, който 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. Всеки улавя точно този клас дефекти, за които другият е сляп, което е цялата причина една сериозна среда за валидиране да изпълнява и двете и да третира даден файл като готов за изпращане само когато и двете са съгласни
Матрицата за валидиране
За да докаже, че библиотеката произвежда файлове, които оцеляват и през двете порти, средата (harness) изгражда матрица. Шест профила на фактури покриват диапазона, който европейският процес среща на практика: 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 като ръководство (guideline), и Mustang отхвърли файла с грешка за невалидна стойност на съответствие (invalid-conformance-value), последвана от грешка за неподдържан тип профил. Причината е, че XMP полето fx:ConformanceLevel не е слот за свободен текст за ваше собствено именуване на профили. Factur-X дефинира точно пет стандартни стойности за него: MINIMUM, BASIC WL, BASIC, EN 16931 и EXTENDED. Специфична за Франция B2B фактура все още е документ с профил EXTENDED, що се отнася до XMP метаданните. Френският характер на фактурата не се изразява чрез измисляне на шеста стойност на съответствие. Той се изразява чрез кода на страната, 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 се валидира спрямо :1p0 XSD, който е по-строг относно кардиналността (cardinality), отколкото подсказват текстовите резюмета. 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 е електронният адрес на купувача, маршрутните крайни точки (endpoints), които немски публичен портал използва за доставяне и потвърждаване на фактурата. Базовият модел EN 16931 ги третира като незадължителни. XRechnung не го прави. Пропуснете което и да е от двете и фактурата е добре оформена (well-formed), валидна по схема и отхвърлена
Третото е правилото BR-DE-6, което изисква да присъства телефонен номер за контакт на продавача. Това е от типа полета, които програмистът изпуска, защото го усеща по-скоро като представяне (presentation), отколкото като данни, а отсъствието му води до грешка при валидиране, която сочи към групата за контакт на продавача, а не към нещо очевидно липсващо. Предоставянето на BT-34, BT-49 и телефонния номер на продавача е това, което премества файл на XRechnung от невалиден към валиден под Mustang, и нищо от това не променя каквото и да било, което veraPDF вижда, защото и трите живеят в XML
Свързване на изхода от библиотеката към валидатор
Архитектурната цел зад средата се обобщава за всяка бизнес система. Библиотеката за PDF записва съответстващ контейнер и вгражда XML. Тя не се опитва и не трябва да се опитва да бъде авторитет по бизнес правилата на EN 16931. ValidateFacturXInvoice в библиотеката проверява съгласуваността на контейнера, че масивът /AF в каталога, дървото с имена на вградените файлове, XMP DocumentFileName, профилът, ръководството и /AFRelationship се съгласуват, но не валидира данъчни кодове или равняване на суми. Правилното разделение на труда е бизнес системата да извлече XML и да го предаде на специализиран валидатор на фактури, точно както средата го предава на Mustang
Прочитането на файла обратно ви казва какво действително е записано. DetectFacturXInvoice съобщава дали е разпозната фактура, а GetFacturXInvoiceInfo чете полетата с метаданни по етикет: етикет 1 е името на вградения файл, етикет 2 е XMP DocumentFileName, етикет 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 се изпълнява в същия пас над същия файл. Свързването (wiring) е малко: конзолен генератор, EInvoiceValidation.dpr, записва дванадесетте файла, използвайки споделения модел на фактура от примера, а скрипт, run-validation.ps1, задвижва и двата валидатора над изходната директория и отпечатва таблица за преминаване и провал. Същата двустъпкова форма, генериране с библиотеката и проверка с външни валидатори, е това, което една задача за непрекъсната интеграция (continuous integration) трябва да изпълнява при всяка промяна в генерирането на фактури, защото единственият начин да се знае, че даден файл удовлетворява и двата слоя, е да се попитат и двата инструмента
Ако вашият процес (pipeline) също трябва да сертифицира контейнера преди подписване, страната на preflight на тази работа е обхваната в нашето ръководство за PDF/A и PDF/UA preflight в Delphi, а по-широкият процес сертифицирай-и-след-това-подпиши е описан в работната среда за съответствие и подписване. И двете надграждат върху същия път на генериране, който се доставя като част от Delphi PDF Library за Delphi и C++Builder, заедно с API-тата за PDF/A, асоцииран файл и метаданни, използвани тук