Uma nota fiscal eletrônica em conformidade não é um PDF com um arquivo XML grampeado do lado. É um único documento PDF/A-3 que carrega a nota fiscal duas vezes: uma vez como uma página que um ser humano lê e uma vez como um XML Cross Industry Invoice legível por máquina armazenado dentro do arquivo como um arquivo associado. As duas representações descrevem a mesma nota fiscal. Essa natureza dual é o ponto central das famílias de formatos que os mandatos europeus agora exigem - Factur-X na França e Alemanha, ZUGFeRD nos mercados de língua alemã e XRechnung para faturamento do setor público alemão. Este artigo mostra como o PDFlibPas monta tal nota fiscal híbrida no Delphi, onde os padrões deixam margem para erros e por que um perfil no catálogo precisa de um construtor XML completamente separado
O que é uma nota fiscal híbrida
A página visível e o XML incorporado servem a leitores diferentes. Um funcionário que aprova um pagamento olha para a página renderizada. Um sistema de contas a pagar ingere o XML, lê os totais e o detalhamento de impostos como campos estruturados e registra a entrada sem que um humano precise digitar nada. O conteúdo semântico desse XML é regido pelo EN 16931, o padrão europeu que define o modelo de dados de nota fiscal: quais campos existem, o que eles significam e quais são obrigatórios. O EN 16931 é um modelo semântico, não um formato de arquivo. O Factur-X, o ZUGFeRD 2.x e o XRechnung todos realizam esse modelo como um documento UN/CEFACT Cross Industry Invoice, a sintaxe que transporta os campos do EN 16931
Para que o documento seja arquivável e autodescritivo, o contêiner é PDF/A-3, definido pela ISO 19005-3. O PDF/A-3 é o nível de conformidade que permite arquivos incorporados arbitrários, que é exatamente o que um XML de nota fiscal precisa ser. O PDF/A-2 proíbe a incorporação de arquivos que não sejam eles mesmos PDF/A, portanto uma nota fiscal Factur-X não pode ser PDF/A-2. A escolha do PDF/A-3 não é uma preferência - é um requisito que decorre diretamente do desejo de incorporar dados não-PDF em um documento de arquivamento
Por que o relacionamento é Alternative
Incorporar os bytes é a parte fácil. A ISO 32000 §7.11.4 define o fluxo de arquivo incorporado, o objeto que contém o XML bruto e seus parâmetros. A parte que torna o arquivo um arquivo associado válido é o §14.13, que adiciona o conceito de arquivo associado e a chave /AFRelationship. Essa chave declara como os dados incorporados se relacionam com o conteúdo ao qual estão anexados, e o valor que o Factur-X exige é Alternative
A escolha importa porque os outros valores afirmariam algo falso sobre o documento. Source significaria que o XML é o material a partir do qual o conteúdo visível foi gerado, um original do qual a página deriva. Supplement significaria que o XML adiciona informações além do que a página mostra, um extra não contido na renderização. Nenhum desses é o que uma nota fiscal Factur-X é. O XML e a página são duas expressões equivalentes de uma nota fiscal, carregando o mesmo conteúdo legal em duas formas. Alternative é o valor que diz exatamente isso: uma representação alternativa equivalente do conteúdo visível. Um validador que lê qualquer outro relacionamento em um arquivo Factur-X vai rejeitá-lo, e com razão, porque o relacionamento é uma declaração legível por máquina sobre para que serve o anexo
O catálogo de perfis
O exemplo de Nota Fiscal Eletrônica que acompanha o PDFlibPas usa o mesmo caminho de geração em seis perfis, definidos como um array de registros em InvoiceModel.pas. Cada perfil carrega os valores que o escritor precisa: um nome de exibição, o nome do arquivo incorporado, um nível de conformidade, o /AFRelationship, uma versão, um código de país opcional e o URN GuidelineID que o XML anuncia dentro de seu contexto de documento
Os seis são Factur-X EN16931, Factur-X BASIC, Factur-X EXTENDED para a França, XRechnung 3.0, ZUGFeRD 1.0 COMFORT e ZUGFeRD 2.0 BASIC. O GuidelineID é o campo que informa ao receptor exatamente qual perfil esperar, e os valores são específicos. O Factur-X EN16931 anuncia urn:cen.eu:en16931:2017. O XRechnung 3.0 anuncia urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0. O ZUGFeRD 2.0 BASIC anuncia urn:cen.eu:en16931:2017#compliant#urn:zugferd.de:2p0:basic. O nome do arquivo incorporado também faz parte do contrato. Os perfis Factur-X incorporam factur-x.xml, o XRechnung incorpora xrechnung.xml e os perfis ZUGFeRD incorporam ZUGFeRD-invoice.xml ou zugferd-invoice.xml. Um receptor verifica os nomes dos anexos para encontrar a nota fiscal, portanto o nome do arquivo não é apenas cosmético
Um detalhe no catálogo merece atenção especial. A maioria dos perfis usa o relacionamento Alternative, mas a entrada do XRechnung 3.0 no exemplo usa Source. Os dois formatos respondem a validadores e convenções diferentes, e o exemplo define o relacionamento de cada perfil a partir do catálogo em vez de codificar um único valor fixo, que é por isso que o campo por perfil existe em vez de uma constante
A armadilha do ZUGFeRD 1.0
É tentador assumir que cada perfil é o EN 16931 Cross Industry Invoice com variações menores em quantos campos opcionais você preenche. Isso vale para cinco dos seis. Não vale para o ZUGFeRD 1.0 COMFORT, e a razão é estrutural, não cosmética
Os perfis modernos emitem um UN/CEFACT Cross Industry Invoice com versão de namespace :100, cujo elemento raiz é rsm:CrossIndustryInvoice. O ZUGFeRD 1.0 é anterior a esse esquema. É o CrossIndustryDocument de 2014 com versão de namespace :1p0, e seu elemento raiz é rsm:CrossIndustryDocument. Os URNs de namespace diferem, o elemento raiz difere e a árvore de elementos difere em toda a estrutura: o esquema :1p0 agrupa dados sob ApplicableSupplyChainTradeAgreement, ApplicableSupplyChainTradeDelivery e ApplicableSupplyChainTradeSettlement, enquanto o :100 usa ApplicableHeaderTradeAgreement, ApplicableHeaderTradeDelivery e ApplicableHeaderTradeSettlement. A nomenclatura é suficientemente semelhante para enganar e diferente o suficiente para quebrar
A palavra COMFORT no nome do perfil descreve o quão rico é o dado - um perfil de nível de automação com itens de linha completos, detalhamento de impostos e condições de pagamento - não qual esquema o transporta. Portanto, você não pode pegar um documento :100 e rerotulá-lo para ZUGFeRD 1.0. O exemplo lida com isso por meio de um sinalizador em cada registro de perfil e duas funções construtoras separadas, selecionando a correta antes de qualquer XML ser gerado
function BuildInvoiceXMLText(const AProfile: TeInvoiceProfile;
const Data: TInvoiceData): string;
begin
// XMLFamily = 1 means the legacy ZUGFeRD 1.0 :1p0 schema; every
// other profile is the modern UN/CEFACT :100 Cross Industry Invoice.
if AProfile.XMLFamily = 1 then
Result := BuildZUGFeRD1Text(AProfile, Data)
else
Result := BuildCII100Text(AProfile, Data);
end;
A divisão não é uma conveniência de implementação. Alimentar uma árvore :100 a um receptor ZUGFeRD 1.0 produz um documento que falha na validação do esquema no elemento raiz, portanto as duas famílias precisam ser construídas por código que sabe qual está escrevendo
Selecionando o nível PDF/A-3
O PDF/A-3 tem três níveis de conformidade, e o PDFlibPas os seleciona por meio de SetPDFAMode. O modo 5 é PDF/A-3b, o nível que garante reprodução visual confiável. O modo 6 é PDF/A-3a, que adiciona os requisitos de estrutura marcada e acessibilidade do nível a. O modo 7 é PDF/A-3u, que exige que todo o texto seja mapeado para Unicode. Habilitar o modo também incorpora a intenção de saída sRGB integrada na biblioteca, a caracterização de cor que o PDF/A exige para que a cor renderizada seja definida e não dependente de dispositivo
A maioria dos fluxos de nota fiscal funciona em 3b, que é suficiente para uma página visível fiel mais o XML incorporado. Se você precisar de um perfil ICC explícito em vez do integrado, LoadOutputIntentProfile o substitui após o modo ser definido. O exemplo carrega o perfil sRGB do repositório dessa forma e usa a intenção integrada como fallback quando o arquivo não está acessível, portanto a intenção de saída está sempre presente
PDF := TPDFlib.Create;
try
// Mode 5 = PDF/A-3b, 6 = PDF/A-3a, 7 = PDF/A-3u.
if PDF.SetPDFAMode(5) <> 1 then
raise Exception.Create('PDF/A-3 mode could not be enabled');
// Optional: swap the built-in sRGB intent for an explicit ICC profile.
if PDF.LoadOutputIntentProfile(ICCFile, 'DeviceRGB') <> 1 then
{ fall back to the built-in sRGB intent that SetPDFAMode embedded };
finally
// ... continue building the document
end;
Montando a nota fiscal híbrida
Com o contêiner configurado, o restante são três etapas em ordem: definir o modo PDF/A-3, desenhar a página legível por humano e então anexar o XML como um arquivo associado. A página visível é conteúdo comum. A única restrição que vale lembrar é que o PDF/A proíbe as 14 fontes padrão não incorporadas, portanto a página deve incorporar uma face de fonte real em vez de fazer referência a uma integrada
O anexo é uma única chamada. AddFacturXAssociatedFileFromString recebe os bytes XML UTF-8 brutos mais os metadados do perfil, grava o fluxo do arquivo incorporado, o registra no array /AF do Catálogo que o PDF/A-3 exige, aplica o /AFRelationship e gera os metadados XMP de nota fiscal eletrônica que identificam o documento como Factur-X, ZUGFeRD ou XRechnung. Ele também verifica se o ID de diretriz do XML corresponde ao nível de conformidade que você solicitou, portanto uma incompatibilidade entre o XML que você construiu e o perfil que você nomeou é detectada em vez de enviada silenciosamente
// 1. PDF/A-3 mode and output intent are already set.
// 2. Draw the visible page (embeds a real TrueType font).
DrawInvoicePage(PDF, AProfile, Data);
// 3. Build the profile-correct XML and attach it as an
// associated file with /AFRelationship = Alternative.
InvoiceXML := BuildInvoiceXML(AProfile, Data); // AnsiString of UTF-8 bytes
FileID := PDF.AddFacturXAssociatedFileFromString(
InvoiceXML,
AProfile.ConformanceLevel, // e.g. 'EN16931'
AProfile.FileName, // 'factur-x.xml'
AProfile.Description,
AProfile.Relationship, // 'Alternative'
AProfile.Version, // '1.0'
AProfile.CountryCode); // '' or 'DE' or 'FR'
if FileID <= 0 then
raise Exception.Create('Invoice XML could not be attached');
PDF.SaveToFile(TargetFile);
Uma sutileza no caminho de dados é a codificação. O XML incorporado declara encoding="UTF-8", e o método recebe seus bytes como uma AnsiString, portanto um nome de vendedor ou comprador não-ASCII deve chegar à chamada como octetos UTF-8 brutos. Uma conversão simples pela página de código ANSI do sistema corromperia esses caracteres e produziria silenciosamente uma nota fiscal cujo XML não corresponde mais à sua própria declaração. O exemplo codifica para UTF-8 explicitamente antes de entregar os bytes, que é a maneira segura de alimentar qualquer API de PDF orientada a bytes a partir de uma string Unicode
Para anexar XML que não é um perfil de nota fiscal eletrônica reconhecido, AddPDFA3AssociatedFileFromString é a contrapartida genérica. Ela recebe um nome de arquivo, tipo MIME, descrição, relacionamento e bytes, e grava um arquivo associado PDF/A-3 simples sem metadados específicos de nota fiscal ou verificações de diretriz. Use-a para dados suplementares; use o método Factur-X para notas fiscais, para que os metadados do perfil e a verificação de correspondência de diretriz sejam escritos para você
Uma vez que o documento é produzido, as próximas questões são se ele passa na validação de PDF/A e acessibilidade, e se pode ser assinado sem quebrar a conformidade. Isso é abordado em o guia de preflight de PDF/A e PDF/UA e em a bancada de conformidade e assinatura. Tudo isso acompanha a Biblioteca Delphi PDF do PDFlibPas, ao lado das APIs de PDF/A, marcação e propriedades de documento sobre as quais o caminho de nota fiscal eletrônica se baseia