Uma fatura Factur-X ou ZUGFeRD é constituída por dois documentos num único ficheiro. O documento externo é um contentor PDF/A-3 que um leitor de arquivo tem de aceitar durante os próximos dez anos. O documento interno é uma fatura em XML que o sistema de contabilidade do comprador tem de processar segundo a norma EN 16931. O erro que coloca faturas incorretas em produção é acreditar que acertar no primeiro garante automaticamente o segundo. Não garante. Um ficheiro pode ser um PDF/A-3 impecável e ainda assim conter XML que nenhuma autoridade fiscal aceitará, e pode conter XML textualmente conforme com a EN 16931 dentro de um contentor que falha a validação de arquivo. As duas camadas são validadas por duas ferramentas distintas que nada sabem uma da outra, e um pipeline real tem de satisfazer ambas
Dois validadores, duas perguntas diferentes
O veraPDF é a implementação de referência para PDF/A. Apontando-o a uma fatura, responde a uma única pergunta: este ficheiro é um PDF/A-3 conforme? Verifica o que a norma ISO 19005-3 exige. Se todas as fontes estão incorporadas. Se existe um OutputIntent. Se os metadados XMP declaram a parte e o nível de conformidade corretos. Para uma fatura eletrónica, verifica também a estrutura de ficheiros associados que o PDF/A-3 requer, pois o XML viaja como ficheiro incorporado com um /AFRelationship e uma entrada na matriz /AF do catálogo do documento. O veraPDF nada diz sobre se o total da fatura está correto, pois isso não é da sua competência
O Mustang é o validador de código aberto do Mustangproject. Coloca a questão oposta: o XML incorporado é uma fatura válida? Executa o XML contra o esquema do perfil declarado e aplica depois as regras de negócio da EN 16931, bem como os conjuntos de regras específicas de cada país sobrepostos, incluindo o CIUS do XRechnung. Verifica que um identificador de IVA do vendedor está presente quando os totais o exigem, que os montantes de descontos e encargos coincidem com o total do documento, e que o URN do perfil no XML corresponde ao que o ficheiro afirma ser. O Mustang não se preocupa se o PDF envolvente incorpora as fontes, pois esse é o trabalho do veraPDF
Nenhuma ferramenta é um superconjunto da outra. O veraPDF aprova um contentor estruturalmente perfeito à volta de XML sem sentido. O Mustang aprova XML perfeito dentro de um contentor com um OutputIntent em falta. Cada um deteta exatamente a classe de defeito à qual o outro é cego, e é precisamente esta a razão pela qual um sistema de validação sério executa ambos e considera um ficheiro pronto para envio apenas quando os dois concordam
A matriz de validação
Para provar que a biblioteca produz ficheiros que passam em ambas as verificações, o sistema de teste constrói uma matriz. Seis perfis de fatura cobrem a gama que um pipeline europeu encontra na prática: Factur-X EN 16931, Factur-X BASIC, a variante Factur-X EXTENDED France B2B, XRechnung 3.0, ZUGFeRD 1.0 COMFORT e ZUGFeRD 2.0 BASIC. Cada perfil é gerado para dois níveis de subconformidade PDF/A, 3b e 3u, pois os requisitos do nível B e do nível U divergem no mapeamento Unicode, e um ficheiro que passa num pode falhar no outro. Seis perfis vezes dois níveis perfazem doze ficheiros, todos eles gerados sem interface gráfica pelo mesmo caminho de código que o exemplo GUI utiliza, pelo que os artefactos testados não foram ajustados manualmente para o teste
O gerador escreve os doze e um script alimenta cada um aos dois validadores. Na primeira execução completa, o veraPDF aprovou todos os doze. A estrutura do contentor estava correta em todos: ficheiros associados registados, conformidade XMP declarada, output intents presentes. O Mustang aprovou oito. Quatro faturas eram ficheiros PDF/A-3 estruturalmente válidos com XML que o validador de regras de negócio rejeitou, o que é precisamente a separação que a abordagem com dois instrumentos existe para revelar. Se o sistema de validação tivesse confiado apenas no veraPDF, esses quatro teriam parecido terminados
As duas correções que fecharam a diferença
As quatro falhas do Mustang tiveram duas causas distintas, e a correção de cada uma é um detalhe que vale a pena conhecer antes de gerar estes perfis
A primeira foi o perfil Factur-X EXTENDED France B2B. O gerador original passou um rótulo interno como nível de conformidade e um URN interno como guia, e o Mustang rejeitou o ficheiro com um erro de valor-de-conformidade-inválido seguido de um erro de tipo-de-perfil-não-suportado. A razão é que o campo XMP fx:ConformanceLevel não é um campo de texto livre para a sua própria nomenclatura de perfil. O Factur-X define exatamente cinco valores padrão para ele: MINIMUM, BASIC WL, BASIC, EN 16931 e EXTENDED. Uma fatura France B2B específica continua a ser um documento de perfil EXTENDED no que diz respeito aos metadados XMP. O carácter francês da fatura não se expressa inventando um sexto valor de conformidade. Expressa-se pelo código de país, FR, e pelo identificador de guia dentro do XML, que tem de levar o prefixo urn:cen.eu:en16931:2017#conformant# que assinala um CIUS conforme com a EN 16931. Passar o valor EXTENDED padrão com FR como código de país e o URN de guia correto tornou o ficheiro conforme
Na API da biblioteca, isso é uma chamada a AddFacturXAssociatedFileFromString com a conformidade, o país e a guia alinhados. O argumento do nível de conformidade leva o token padrão, o argumento do código de país leva FR, e o URN da guia reside nos bytes XML que passa
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;
A segunda causa foi o perfil ZUGFeRD 1.0 COMFORT, e não tinha nada a ver com metadados. O ZUGFeRD 1.0 é validado contra o XSD :1p0, que é mais restritivo em relação à cardinalidade do que os resumos em prosa sugerem. O XSD exige que a totalização de liquidação do cabeçalho, ram:SpecifiedTradeSettlementMonetarySummation, contenha ram:ChargeTotalAmount e ram:AllowanceTotalAmount cada um exatamente uma vez. O XML gerado omitiu ambos, pelo que o Mustang reportou que os elementos devem ocorrer exatamente uma vez. Estes não são opcionais quando o esquema define minOccurs como um. Emitir ambos na ordem de sequência XSD, imediatamente após ram:LineTotalAmount, com o valor 0.00 quando não há encargos ou descontos, satisfez o esquema. Um zero é um elemento presente; um elemento ausente é uma violação do esquema. Com estas duas correções efetuadas, a matriz passou a doze em doze no Mustang, mantendo doze em doze no veraPDF
Os campos XRechnung que convertem inválido em válido
O XRechnung merece uma nota própria porque o seu CIUS alemão adiciona regras de negócio ausentes do conjunto base da EN 16931, e falham de formas que à primeira vista nada parecem ter de errado com o documento. Duas delas dizem respeito a endereços eletrónicos. BT-34 é o endereço eletrónico do vendedor e BT-49 é o endereço eletrónico do comprador, os pontos de encaminhamento que um portal do setor público alemão utiliza para entregar e acusar receção da fatura. O modelo base EN 16931 trata-os como opcionais. O XRechnung não. Omitir qualquer um deles resulta numa fatura bem formada, válida segundo o esquema, e rejeitada
O terceiro é a regra BR-DE-6, que exige a presença do número de telefone do contacto do vendedor. É o tipo de campo que um programador remove por parecer informação de apresentação em vez de dados, e a sua ausência produz uma falha de validação que aponta para o grupo de contacto do vendedor em vez de para algo obviamente em falta. Fornecer BT-34, BT-49 e o número de telefone do vendedor é o que move um ficheiro XRechnung de inválido para válido no Mustang, e nada disso altera o que o veraPDF vê, pois os três residem no XML
Ligar a saída da biblioteca a um validador
O ponto arquitetural por detrás do sistema de validação generaliza-se a qualquer sistema empresarial. A biblioteca PDF escreve um contentor conforme e incorpora o XML. Não deve, nem deveria, pretender ser a autoridade das regras de negócio EN 16931. ValidateFacturXInvoice na biblioteca verifica a consistência do contentor — que a matriz /AF do catálogo, a árvore de nomes dos ficheiros incorporados, o DocumentFileName XMP, o perfil, a guia e o /AFRelationship concordam — mas não valida códigos fiscais nem reconcilia montantes. A divisão correta de trabalho é o sistema empresarial extrair o XML e entregá-lo a um validador de faturas dedicado, exatamente como o sistema de validação o entrega ao Mustang
Ler o ficheiro de volta indica o que foi efetivamente escrito. DetectFacturXInvoice reporta se uma fatura foi reconhecida, e GetFacturXInvoiceInfo lê os campos de metadados por etiqueta: a etiqueta 1 é o nome do ficheiro incorporado, a etiqueta 2 o DocumentFileName XMP, a etiqueta 5 o nível de conformidade, a etiqueta 6 o identificador de guia e a etiqueta 7 o /AFRelationship. Confirmar que o nível de conformidade lido de volta é o token padrão e não um rótulo interno é a forma mais barata de detetar o erro EXTENDED antes de um ficheiro sair do seu build
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 devolve os bytes XML em bruto como uma AnsiString, pronta para escrever num ficheiro ou transmitir a um processo validador. No sistema de validação, esse destino é o Mustang, invocado através do seu jar de linha de comandos, com o veraPDF executado na mesma passagem sobre o mesmo ficheiro. A ligação é simples: um gerador de consola, EInvoiceValidation.dpr, escreve os doze ficheiros utilizando o modelo de fatura partilhado do exemplo, e um script, run-validation.ps1, executa ambos os validadores sobre o diretório de saída e imprime uma tabela de aprovações e reprovações. A mesma forma em dois passos — gerar com a biblioteca e verificar com validadores externos — é o que um trabalho de integração contínua deve executar em cada alteração à geração de faturas, porque a única forma de saber se um ficheiro satisfaz ambas as camadas é perguntar a ambas as ferramentas
Se o seu pipeline também precisar de certificar o contentor antes de assinar, a vertente de preflight deste trabalho está coberta na nossa descrição do preflight PDF/A e PDF/UA em Delphi, e o fluxo de certificação e assinatura é descrito no banco de trabalho de conformidade e assinatura. Ambos assentam no mesmo caminho de geração que é fornecido como parte da Biblioteca PDF para Delphi para Delphi e C++Builder, juntamente com as APIs de PDF/A, ficheiros associados e metadados utilizadas aqui