Gerando o arquivo

O objetivo da SpedLibrary é separar a camada de dados da camada de formatação e geração do arquivo. O desenvolvedor terá de se preocupar somente em identificar as informações que precisa gerar e passar para a Library. A Library se encarregará de transformar os dados passados para o formato exigido pelo Layout e gerar o arquivo.

Definindo quais registros serão gerados

O primeiro passo para gerar o arquivo do Sped Fiscal ou EFD-Contribuições é, com base no guia prático da EFD, identificar junto com o setor contábil/fiscal quais os registros seu sistema precisará gerar para o perfil da empresa e o tipo de atividade que realiza.

Identificando os dados

Após definir quais os registros que precisam ser gerados, a próxima etapa é identificar em quais tabelas e campos do seu sistema se encontram as informações que você precisa informar no arquivo.

Durante este processo, pode ser que você descubra que vários dados que você precisará informar no arquivo nem mesmo existem nos campos da sua base de dados e você terá de criá-los.

A parte fácil: gerando o arquivo

Por incrível que pareça, a geração do arquivo é a parte mais fácil. A ideia da Library para gerar o arquivo é muito simples, conforme você verá logo mais!

O coração da SpedLibrary: a estrutura tSpedFiscalPisCofinsArquivo

A principal função da Library é a função SpedFiscalPisCofinsGeraArquivo. Esta é a função que gera o arquivo. O mais importante parâmetro que esta função deve receber é uma variável do tipo tSpedFiscalPisCofinsArquivo. Este tipo é uma estrutura que contém todos os blocos e registros do arquivo do Sped. A definição deste tipo pode ser vista no pacote SpedFiscalPisCofinsStrucures.pkg:

Struct tSpedFiscalPisCofinsArquivo
tSpedFiscalPisCofinsBloco0 vtBloco0
tSpedPisCofinsBlocoA vtBlocoA
tSpedFiscalPisCofinsBlocoC vtBlocoC
tSpedFiscalPisCofinsBlocoD vtBlocoD
tSpedFiscalBlocoE vtBlocoE
tSpedPisCofinsBlocoF vtBlocoF
tSpedFiscalBlocoG vtBlocoG
tSpedFiscalBlocoH vtBlocoH
tSpedPisCofinsBlocoM vtBlocoM
tSpedFiscalPisCofinsBloco1 vtBloco1
tSpedFiscalPisCofinsBloco9 vtBloco9
End_Struct // tSpedFiscalPisCofinsArquivo

Como se nota na definição acima, cada campo desta estrutura corresponde a um dos blocos do arquivo.

Para ficar fácil de saber se um bloco ou registro pertence a um determinado arquivo, utilizamos os seguintes prefixos nos nomes das estruturas:

  • tSpedFiscalXXX: este prefixo indica que o bloco ou registro é usado somente no arquivo do Sped Fiscal.
  • tSpedFiscalPisCofinsXXX: este prefixo indica que o bloco ou registro é usado em ambos os arquivos.
  • tSpedPisCofinsXXX: este prefixo indica que o bloco ou registro é usado somente no arquivo do EFD-Contribuições.

Se descermos mais um nível na estrutura para ver a definição de um bloco, encontraremos o seguinte:

Struct tSpedFiscalPisCofinsBloco0
tSpedFiscalPisCofinsRegistro0000 vtRegistro0000
tSpedFiscalPisCofinsRegistroAbreBloco vtRegistro0001
tSpedFiscalRegistro0005 vtRegistro0005
tSpedFiscalRegistro0015[] vtRegistros0015
tSpedFiscalPisCofinsRegistro0100 vtRegistro0100
tSpedPisCofinsRegistro0110 vtRegistro0110
tSpedPisCofinsRegistro0140[] vtRegistros0140
tSpedFiscalPisCofinsRegistro0150[] vtRegistros0150
tSpedFiscalPisCofinsRegistro0190[] vtRegistros0190
tSpedFiscalPisCofinsRegistro0200[] vtRegistros0200
tSpedFiscalPisCofinsRegistro0300[] vtRegistros0300
tSpedFiscalPisCofinsRegistro0400[] vtRegistros0400
tSpedFiscalPisCofinsRegistro0450[] vtRegistros0450
tSpedFiscalPisCofinsRegistro0460[] vtRegistros0460
tSpedFiscalPisCofinsRegistro0500[] vtRegistros0500
tSpedFiscalPisCofinsRegistro0600[] vtRegistros0600
tSpedFiscalPisCofinsRegistro0990 vtRegistro0990
End_Struct // tSpedFiscalPisCofinsBloco0

A estrutura do bloco segue quase de forma idêntica a estrutura hierárquica da definição do Guia Prático com pequenas diferenças. Veja a imagem abaixo:

EstruturaBloco0

A imagem acima mostra a estrutura hierárquica do bloco 0. O primeiro registro do bloco 0 é o registro 0000 de abertura do arquivo. Este registro só existe no bloco 0. O registro seguinte é o registro 0001 de abertura do bloco, o qual é o nível 1. Todos os demais registros, com exceção do registro de encerramento do Bloco, são de nível superior ao do registro 0001. Então, se seguíssimos à risca, em nossa estrutura do bloco 0 existiria somente o registro 0000 e todos os demais estariam dentro dele. Para não criar um nível à mais na estrutura, optamos por deixar o registro de abertura do arquivo e também os registros de abertura e encerramento do bloco diretamente definidos dentro da estrutura do bloco, como pode ser visto mais acima.

Dentro de cada bloco, existem registros que tem ocorrência única e registros que podem se repetir.

Os registros que são únicos são formados por uma variável simples do tipo estrutura. Por exemplo, dentro da estrutura do bloco 0, o registro 0000 que é único no arquivo, é criado como uma variável simples do tipo tSpedFiscalPisCofinsRegistro0000.

Já o registro 0015, que pode ter várias ocorrências por arquivo é criado como um vetor (array) do tipo tSpedFiscalRegistro0015:

tSpedFiscalRegistro0015[] vtRegistros0015 // n por arquivo - Dados do Contribuinte Substituto

Portanto, para preencher os registros que podem ter várias ocorrências no arquivo, você precisará especificar o índice do array:

Move "BA" to vtArquivo.vtBloco0.vtRegistros0015[0].sUF_ST

Como você já deve ter percebido, o domínio na utilização de estruturas e arrays é um pré-requisito para usar com sucesso a SpedLibrary. O Help do Visual DataFlex contém mais detalhes sobre o uso deste recursos na linguagem.

Preenchendo a estrutura

Sua principal tarefa para usar a Library para gerar o arquivo será preencher a variável do tipo tSpedFiscalPisCofinsArquivo com os dados que identificou no passo anterior, e no final chamar a função SpedFiscalPisCofinsGeraArquivo.

É importante ressaltar que o layout do arquivo do Sped Fiscal é diferente do layout do arquivo do EFD-Contribuições, por isso, o preenchimento dos campos deve ser feito sempre consultado o Guia Prático da EFD para que não haja dúvidas nos registros e campos que devem ser preenchidos.

Para preencher a variável, você não precisará abrir o pacote com a definição das estruturas, pois o Code-Complete do VDF Studio irá exibir os campos dentro da estrutura logo após teclar o ".":

CodeCompleteNivelBloco

O trecho de código abaixo mostra um exemplo de preenchimento do registro 0000:

tSpedFiscalPisCofinsArquivo vtArquivo
:
:
// Preenchendo o registro 0000
Move Empresa.iCOD_VER to vtArquivo.vtBloco0.vtRegistro0000.iCOD_VER
Move 0 to vtArquivo.vtBloco0.vtRegistro0000.iCOD_FIN
Move dInicial to vtArquivo.vtBloco0.vtRegistro0000.dDT_INI
Move dFinal to vtArquivo.vtBloco0.vtRegistro0000.dDT_FIN
Move Empresa.sNome to vtArquivo.vtBloco0.vtRegistro0000.sNOME
Move Empresa.sCNPJ to vtArquivo.vtBloco0.vtRegistro0000.sCNPJ_CPF
Move Empresa.sUF to vtArquivo.vtBloco0.vtRegistro0000.sUF
Move Empresa.sIE to vtArquivo.vtBloco0.vtRegistro0000.sIE
Move Empresa.iCOD_MUN to vtArquivo.vtBloco0.vtRegistro0000.iCOD_MUN
Move Empresa.sIM to vtArquivo.vtBloco0.vtRegistro0000.sIM
Move "" to vtArquivo.vtBloco0.vtRegistro0000.sSUFRAMA
Move Empresa.sIND_PERFIL to vtArquivo.vtBloco0.vtRegistro0000.sIND_PERFIL
Move 0 to vtArquivo.vtBloco0.vtRegistro0000.iIND_ATIV

Durante a digitação acima, sempre que você digitar o "." após um campo da estrutura (vtArquivo, vtBloco0 ou vtRegistro0000) o Code-Complete do VDF irá exibir os campos dentro do próximo nível da estrutura.

Encurtando o caminho

Mesmo com a ajuda do Code-Complete, você pode achar tedioso e também pouco produtivo ter de digitar o caminho completo "vtArquivo....vtRegistro0000.iCOD_VER".

Tomando como base o exemplo acima, se você quiser economizar dedo e teclado, então você poderia, além de criar a variável vtArquivo, criar uma varíavel vtRegistro0000 do tipo tSpedFiscalPisCofinsRegistro0000, preencher esta variável e depois no final mover a variável para a sua posição dentro da estrutura da variável vtArquivo. O código ficaria da seguinte forma:

tSpedFiscalPisCofinsArquivo vtArquivotSpedFiscalPisCofinsRegistro0000
tSpedFiscalPisCofinsRegistro0000 vtRegistro0000
:
:
// Preenchendo o registro 0000
Move Empresa.iCOD_VER to vtRegistro0000.iCOD_VER
Move 0 to vtRegistro0000.iCOD_FIN
Move dInicial to vtRegistro0000.dDT_INI
Move dFinal to vtRegistro0000.dDT_FIN
Move Empresa.sNome to vtRegistro0000.sNOME
Move Empresa.sCNPJ to vtRegistro0000.sCNPJ_CPF
Move Empresa.sUF to vtRegistro0000.sUF
Move Empresa.sIE to vtRegistro0000.sIE
Move Empresa.iCOD_MUN to vtRegistro0000.iCOD_MUN
Move Empresa.sIM to vtRegistro0000.sIM
Move "" to vtRegistro0000.sSUFRAMA
Move Empresa.sIND_PERFIL to vtRegistro0000.sIND_PERFIL
Move 0 to vtRegistro0000.iIND_ATIV

// Movendo o registro vtRegistro0000 para a estrutura da variável vtArquivo
Move vtRegistro0000 to vtArquivo.vtBloco0.vtRegistro0000

Quebrando o código em funções menores

Uma das recomendações de boas práticas de programação é que as subrotinas (Procedures e Functions) não sejam muito extensas. Se uma subrotina for muito extensa, a manutenção será mais trabalhosa. Portanto, recomenda-se que quando uma subrotina fique muito grande o código seja quebrado em subrotinas menores chamadas a partir da subrotina principal.

Se você for fazer o preenchimento de todos os registros da estrutura em uma única subrotina, esta subrotina ficará enorme. O trecho acima onde preenchemos o registro 0000 poderia ser feito em uma função separada, chamada a partir da subrotina principal. Esta função poderia receber o registro 0000 via referência, para que a alteração se reflita na variável que foi passada como parâmetro e os demais parâmetros necessários para o preenchimento. Abaixo um exemplo de como poderia ser definida esta função:

Function GeraRegistro0000 tSpedFiscalPisCofinsRegistro0000 ByRef vtRegistro0000 ;
Date dInicial Date dFinal Returns Boolean

Move Empresa.iCOD_VER to vtRegistro0000.iCOD_VER
Move 0 to vtRegistro0000.iCOD_FIN
Move dInicial to vtRegistro0000.dDT_INI
Move dFinal to vtRegistro0000.dDT_FIN
Move Empresa.sNome to vtRegistro0000.sNOME
Move Empresa.sCNPJ to vtRegistro0000.sCNPJ_CPF
Move Empresa.sUF to vtRegistro0000.sUF
Move Empresa.sIE to vtRegistro0000.sIE
Move Empresa.iCOD_MUN to vtRegistro0000.iCOD_MUN
Move Empresa.sIM to vtRegistro0000.sIM
Move "" to vtRegistro0000.sSUFRAMA
Move Empresa.sIND_PERFIL to vtRegistro0000.sIND_PERFIL
Move 0 to vtRegistro0000.iIND_ATIV
Function_Return True
End_Function // GeraRegistro0000

Esta função retornará sempre True, indicando que foi bem sucedida, mas se um dia for necessário adicionar alguma validação, basta retornar False para indicar que houve um erro.

Abaixo como ficaria a chamada desta função dentro da subrotina principal:

tSpedFiscalPisCofinsArquivo vtArquivo
tSpedFiscalPisCofinsRegistro0000 vtRegistro0000
:
// Gerando o registro 0000
Get GeraRegistro0000 (&vtArquivo.vtBloco0.vtRegistro0000) dInicial dFinal to bOk
If (not(bOk)) Procedure_Return

No exemplo acima, se o retorno da função for False, o que indicaria que houve um erro, executamos um Procedure_Return para abortar a Procedure principal que está executando a geração completa do arquivo (provavelmente em uma procedure OnProcess de um BPO).

Preenchendo registros múltiplos

Boa parte dos registros podem ter várias ocorrências no arquivo. Este é o caso, por exemplo, do registro C100 do arquivo do Sped Fiscal. Como dissemos anteriormente, os registros que podem aparecer várias vezes no arquivo foram definidos como vetores na estrutura e devem se preenchidos especificando um índice que terá de ser incrementado a cada registro preenchido. O fragmento de código abaixo mostra o preenchimento de registros C100 dentro da estrutura:

// Varrendo notas de entrada (emissão de terceiros e própria)
// Notas de saída estão em outra tabela e serão varridas em outro ponto do código
Move -1 to iIndiceC100
Clear AMCD1012 // Cabeça da NF
Move iCdEmpresa to AMCD1012.CD_EMPRESA
Move dInicial to AMCD1012.DT_LANCTO
Find Gt AMCD1012 by 3
While (AMCD1012.DT_LANCTO <= dFinal and iCdEmpresa = AMCD1012.CD_EMPRESA and Found)
Move (SpedCodMod(Self,AMCD1012.TP_ESP_NF)) to sCodMod
Move ("|" + sCodMod + "|") to sCodMod

If (AMCD1012.TP_SITUACAO = "B" and ("|01|04|1B|55|" Contains sCodMod)) Begin
If (PosicionaGEFA0101ByIndex1(AMCD1012.ID_GEFA0101)) Begin
If (AMCD1012.DT_EMI_NF <= AMCD1012.DT_LANCTO) Begin
// Gerando registro C100
Increment iIndiceC100
Move 0 to vtArquivo.vtBlocoC.vtRegistrosC100[iIndiceC100].sIND_OPER
Move AMCD1012.TP_EMITENTE to vtArquivo.vtBlocoC.vtRegistrosC100[iIndiceC100].sIND_EMIT
Move GEFA0101.NR_CPF_CNPJ to vtArquivo.vtBlocoC.vtRegistrosC100[iIndiceC100].sCOD_PART
:
:

O trecho de código acima contém particularidades específicas do sistema onde foi implementado para chegar à informação desejada. O objetivo principal é demonstrar o preenchimento do registro C100.

O que não é necessário preencher

Existem vários registros e campos que você não precisará preencher, pois eles serão preenchidos automaticamente pela SpedlLibrary:

  • Todos os registros do bloco 9.
  • Todos os campos na posição 01 de todos os registros.
  • Os registros de abertura de bloco. Por padrão a Library irá gerar os registros de abertura de bloco com o valor 0-Bloco com dados informados. Você só precisará preencher este campo quando o valor for 1-Bloco sem dados informados.
  • Registros de encerramento de blocos (0990, por exemplo).
  • Campo 06-QTD-PARC do registro C140. Ele será calculado automaticamente a partir da quantidade de registros filhos em C141.
  • Campo 03-VL_INV do registro H005. Ele será calculado automaticamente a partir do campo VL_ITEM dos registros H010.

Funções auxiliares de preenchimento

Existem registros que precisarão ser preenchidos à medida que forem sendo preenchidos outros registros. Um exemplo é o registro 0190 do Sped Fiscal. O registro 0190 contém a identificação das unidades de medida. Você não deverá informar todas as unidades usadas no sistema, mas somente aquelas que tiverem sido usadas em outros registros gerado para o período. Por exemplo, cada unidade informada no campo 06 do registro C170 deve estar especificada no campo 02 de um registro 0190.

Sendo assim, muito provavelmente você não irá preencher os registros 0190 de uma única vez (a não ser que você já tenha uma tabela preparada com as unidades que foram usadas no período). O caminho normal será: à medida que os registros C170 forem sendo preenchidos, você irá incluir um registro 0190 para cada unidade usada.

Para facilitar esta adição, existe uma função auxiliar, a função SpedFiscalPisCofinsAdicionaRegistro0190. Esta função recebe uma variável simples contendo os dados de um registro 0190 que se deseja adicionar e um vetor contendo todos os registros 0190 já adicionados. A função irá então verificar se já existe um registro 0190 no array com o mesmo valor do campo UNID. Se já existir, não fará nada. Se não existir, irá acrescentar este registro no vetor de registros 0190. Abaixo um exemplo:

tSpedFiscalPisCofinsRegistro0190 vtRegistro0190
:
:
Move ItNota.sUnidade to vtRegistro0190.sUNID
Move (PegaDescricaoUnidade(ItNota.sUnidade)) to vtRegistro0190.sDESCR
If (not(SpedFiscalPisCofinsAdicionaRegistro0190(vtRegistro0190,(&vtArquivo.vtBloco0.vtRegistros0190), ;
False,True,(&sMsgErro)))) Function_Return False

Existem várias funções criadas na SpedLibrary para auxiliar no preenchimento de vetores. Algumas fazem mais do que adicionar. Verifique a documentação de cada uma delas em Funções.