Postagens Tagged ‘Nota Fiscal Paulista’
Nota Fiscal Paulista Mod 1 e 1A – O Caminho das Pedras

Considerações iniciais
Neste texto vou discorrer sobre como desenvolver uma classe em codigo delphi para adaptar sistemas empresariais para que possam atender à Portaria CAT – 102, de 9-11-2007 (DOE 10/11/2007), A abordagem apresentada aqui muito provavelmente não é a melhor, mas é a que foi possível em função do fato de que não houve tempo para um planejamento da forma que eu gostaria, devo dizer também que estou escrevendo este texto enquanto desenvolvo e que a medida que vou me envolvendo vão surgindo novas idéias e, como acontece com a maioria dos meus projetos, vai surgindo a vontade de começar tudo novamente, com uma outra abordagem, mas decidi levar esta até o fim, e depois do resultado ok, iniciar a nova, se esta ultima se mostrar melhor, sem dúvida escreverei outro texto aqui.
Nota Fiscal Paulista, Definição:
Olhando do ponto de vista do usuário final a NFP pode ser definida como um programa do governo estadual de São Paulo que devolve uma parte do ICMS aos contribuintes consumidores, do ponto de vista do comerciante é o programa que o obriga a enviar arquivos eletrônicos ao fisco com o movimento de suas transações comerciais e, do ponto de vista do fisco é o programa que visa reduzir a sonegação e aumentar a arrecadação, claro. E do ponto de vista do desenvolvedor? pode-se resumir em 3 palavras: trabalho, trabalho e trabalho.
Antes de começar a codificar:
É interessante que antes de começar a codificar, se “estude” (e estude muito) a Portaria CAT 102, recomendo que imprima este documento e o leia, mas leia mesmo, sem o conhecimento dos detalhes deste texto é impossível entender o código apresentado aqui.
Começando pela base de dados
A primeira coisa a ser feita é checar sua base de dados e verificar se as tabelas que guardam as informações referentes as notas fiscais possuem todos os campos exigidos pela portaria de acordo com o leiaute do anexo único apresentado aqui. Por precaução adotei como norma criar todos os campos e grava-los diretamente nas tabelas, por exemplo, o nome de um cliente está gravado na tabela de clientes, grava-lo na tabela de NFs seria redundancia pois bastaria a chave estrangeira, mas se alguem alterar este dado no futuro, os dados enviados ao fisco ficarão inconsistentes, por isto a opção de registrar tudo diretamente.
Tecnologia/Ferramentas utilizadas:
O Codigo apresentado aqui foi desenvolvido com a ferramenta Delphi versão 7, embora utilizamos a dupla FB/Zeos, as tabelas são representados por objetos do tipo Datasets, desta forma pode-se adaptar esta abordagem para quaisquer contextos.
Descrição da abordagem:
Vou apresentar a interface de uma classe com algumas propriedades e alguns métodos publicos, esta classe recebera 4 datasets como parametros em sua criação, o primeiro deverá conter os dados “fixos” das NFs, ou seja, o dataset mestre enquanto o segundo deverá ser o datset detalhe, o que conterá os itens de produtos e/ou serviços das NFs, os outros 2 são apenas para controle das nfs e arquivos enviados e foram inseridos depois da primeira publicação deste texto, apresentarei também o “corpo” do método principal e os métodos de obtenção dos registros 10 e 20, O bjetivo aqui é apresentar um caminho (embora eu já tenha outro em mente, ehehe!) e não fornecer a solução completa.
unit NFPaulista; interface uses Forms, Classes, DBGrids, ExtCtrls, ComCtrls, Controls, Dialogs, DBCtrls, StdCtrls, Buttons, SysUtils, DB; type TNFP = class private FNumRegs10,FNumRegs20,FNumRegs30,FNumRegs40,FNumRegs50, FNumRegs60 : Word; FCNPJ, FIEstadual, FIMunicipal, FNomeArquivo, FDiretorio : string; FLeiaute, sFilter : String; bFiltered : Boolean; protected FData1, FData2, FDataArquivo, FDataEnvio : TDate; FDataNF1, FDataNF2 : String; FCabecNF, FDetNF, FArquivos, FExportadas : TZQuery; function GetReg10 : String; function GetReg20 : String; function GetReg30 : String; function GetReg40 : String; function GetReg50 : String; function GetReg60 : String; function GetReg90 : String; function GetNumRegs(const sReg : string) : Word; procedure SetDiretorio(sString : String = ''); procedure SetCabec; procedure SetDetalhe; procedure SetLeiaute( sLeiaute : String='1.00'); procedure FiltraPeriodo; public constructor Create(dsCabec, dsDetalhe, dsArquivos, dsExportadas : TDataSet); procedure SetNomeArquivo(dData1, dData2 : TDate); procedure SetCNPJ( Value : String); procedure SetIEstadual( Value : String); procedure SetIMunicipal( Value : String); function SalvaArquivo : Boolean; destructor Destroy; property DataInicial : TDate read FData1 write FData1; property DataFinal : TDate read FData2 write FData2; property CabecalhoNF : TZQuery read FCabecNF write FCabecNF; property DetalheNF : TZQuery read FDetNF write FDetNF; property NomeArquivo : String read FNomeArquivo; property Diretorio : String read FDiretorio write SetDiretorio; property Leiaute : string read FLeiaute write SetLeiaute; end; const Delimitador = '|'; // B.O.M. p/ UTF-8 const BOM = (char($EF)+char($BB)+char($BF)) ;
Como eu disse antes, a classe receberá, em seu construtor, os datasets que contém as informações das notas fiscais, não há um metodo para configurar os campos master/source nas tabelas (embora possa ser desenvolvido) por isto é necessario enviar os datasets com esta configuração pronta, embora haja um método para filtrar o periodo, pode-se optar por ja enviar a tabela mestre filtrada.
O Construtor:
Veja que FCabecNF é a tabela master, que contém as informações referentes aos registros: 10, 20, 40, 50 e 60 enquanto FDetNF é a tabela detalhe, que contem os itens de serviços e/ou produtos da NF, ou seja o registro 30.
constructor TNFP.Create (dsCabec, dsDetalhe, dsArquivos, dsExportadas : TZQuery); begin inherited Create; // Inicialização dos campos FCabecNF := dsCabec; FDetNF := dsDetalhe; FArquivos := dsArquivos; FExportadas := dsExportadas; FIEstadual := ''; FIMunicipal := ''; FCNPJ := ''; SetDiretorio(); SetLeiaute(); FNumRegs10 := 0; FNumRegs20 := 0; FNumRegs30 := 0; FNumRegs40 := 0; FNumRegs50 := 0; FNumRegs60 := 0; sFilter := FCabecNF.Filter; bFiltered := FCabecNF.Filtered; end;
O Método Principal:
Obtém os registros e salva o arquivo, O nome do arquivo é criado com base nas datas informadas, por exemplo para o periodo de 01/07/2009 a 31/07/2009 o nome do arquivo seria 01072009-30072009.txt
function TNFP.SalvaArquivo : Boolean;
var
Stringlist : TStrings;
const
TipoReg = 'RIC';
begin
// Cria o nome do arquivo
if FNomeArquivo='' then SetNomeArquivo(FData1,FData2);
// Cria registro para informações sobre o arquivo gerado
FArquivos.Append;
FArquivos.FieldByName('NOME_ARQUIVO').AsString := FNomeArquivo;
FArquivos.FieldByName('DIRETORIO').AsString := FDiretorio;
FArquivos.FieldByName('DATA1').AsDateTime := FData1;
FArquivos.FieldByName('DATA2').AsDateTime := FData2;
FArquivos.FieldByName('DATA_EXPORTACAO').Value := Now;
FArquivos.Post;
// Criando stringlist p/ armazenamento das linhas
Stringlist := TStringList.Create;
FCabecNF.First;
FDetNF.First;
try
// Cabeçalho, somente 1 por arquivo
Stringlist.Add(BOM+GetReg10);
FNumRegs10 := 1;
while not FCabecNF.Eof do
begin
if Pos(UpperCase(FCabecNF.FieldByName('REG_NFP').AsString),TipoReg)>0
then begin
//Cabeçalho da NF, Destinatario, etc
Stringlist.Add( UTF8Encode(GetReg20));
FNumRegs20 := FNumRegs20+1;
// Itens (Produtos e serviços) da Nota Fiscal
while not FDetNF.Eof do
begin
Stringlist.Add(UTF8Encode(GetReg30));
FNumRegs30 := FNumRegs30+1;
FDetNF.Next;
end;
// Impostos, Despesas e Totais na NF
Stringlist.Add(UTF8Encode(GetReg40));
FNumRegs40 := FNumRegs40+1;
// Dados de Transporte
Stringlist.Add(UTF8Encode(GetReg50));
FNumRegs50 := FNumRegs50+1;
// Informações Adicionais e Complementares
Stringlist.Add(UTF8Encode(GetReg60));
FNumRegs60 := FNumRegs60+1;
end;
// Guardar informações sobre a exportação da NF
FExportadas.Append;
FExportadas.FieldByName('CODIGO_RESUMOVENDA').Value :=
FCabecNF.FieldByName('CODIGO_RESUMOVENDA').Value;
FExportadas.FieldByName('FUNCAO_REGISTRO').Value :=
FCabecNF.FieldByName('REG_NFP').Value;
FExportadas.FieldByName('CODIGO_ARQUIVO').Value :=
FArquivos.FieldByName('CODIGO_ARQUIVONFP').Value;
FExportadas.Post;
// Marcar como Exportada
if not (FCabecNF.State in [dsInsert, dsEdit]) then
FCabecNF.Edit;
FCabecNF.FieldByName('REG_NFP').Value := 'E';
// Proxima Nota Fiscal
FCabecNF.Next;
end;
// Rodapé, Totalizaçoes dos registros
Stringlist.Add(UTF8Encode(GetReg90));
<span style="text-decoration: line-through;"> // Salvar para o arquivo
Stringlist.SaveToFile( (FDiretorio+FNomeArquivo) );
finally
Stringlist.Free;
Result := True;
</span>
// Correção em 28/07/2009, só salvar se houver pelo menos uma NF
if FNumRegs20>0 then
begin
// Salvar para o arquivo
Stringlist.SaveToFile( (FDiretorio+FNomeArquivo) );
Result := True;
end;
finally
Stringlist.Free;
end;
end;
end;O Método para obtenção do Reg10:
function TNFP.GetReg10; begin // Registro + Versão + CNPJ Emitente + Data Inicial e Final Result := '10' + '|1,00|'+FCNPJ+'|'+FDataNF1+'|'+FDataNF2; end;
Veja que neste primeiro método estou usando o delimitador literalmente e não a constante que criei.
O Método para obtenção do Reg20
function TNFP.GetReg20 : String;
type
TReg20 = record
Registro,Justificativa,NatOp,SerieNF,NumNF,EmissaoNF,DataES,
TipoNF,CFOP,IEST,InscMun,CNPJDest,RazaoDest,Logradouro,
Numero,Complemento,Bairro,Cidade,UF,CEP,Pais,
Fone,IEDest : string
end;
var
Reg20 : TReg20;
begin
// Registro
Reg20.Registro :=
'20|'+FCabecNF.FieldByName('REG_NFP').AsString+Delimitador;
// Justificativa do cancelamento:
Reg20.Justificativa :=
FCabecNF.FieldByName('MOTIVO').AsString+Delimitador;
// Natureza da operacao
Reg20.NatOp :=
FCabecNF.FieldByName('NATOPERACAO').AsString+Delimitador;
// Série NF
Reg20.SerieNF :=
FCabecNF.FieldByName('SERIE_NF').AsString+Delimitador;
// Numero da NF
Reg20.NumNF :=
FCabecNF.FieldByName('NUMNF').AsString+Delimitador;
// Emissao da NF
Reg20.EmissaoNF :=
FCabecNF.FieldByName('TSNF').AsString+Delimitador;
// Data da saida/Entrada (Não Obrigatorio na versão 1)
if FLeiaute <> '1.00' then
Reg20.DataES :=
FCabecNF.FieldByName('DATA_SAIDA').AsString+Delimitador
else Reg20.DataES := Delimitador;
// Tipo de NF (Entrada ou Saida)
Reg20.TipoNF :=
FCabecNF.FieldByName('TPNFP').AsString+Delimitador;
// CFOP
Reg20.CFOP :=
FCabecNF.FieldByName('CFOP').AsString+Delimitador;
// Insc Estadual do Substituto Tributario
Reg20.IEST :=
FCabecNF.FieldByName('IEST').AsString+Delimitador;
// Inscrição Municipal do Emitente ** Metodo nesta classe
Reg20.InscMun := FIMunicipal+Delimitador;
// CNPJ Destino
Reg20.CNPJDest :=
FCabecNF.FieldByName('CNPJ').AsString+Delimitador;
// Razao Social do Destinatario
Reg20.RazaoDest :=
FCabecNF.FieldByName('RAZAO').AsString+Delimitador;
// Logradouro
Reg20.Logradouro :=
FCabecNF.FieldByName('ENDERECO').AsString+Delimitador;
// Numero no Logradouro
Reg20.Numero :=
FCabecNF.FieldByName('NUM_ENDERECO').AsString+Delimitador;
// COMPLEMENTO
Reg20.Complemento :=
FCabecNF.FieldByName('COMP_ENDERECO').AsString+Delimitador;
// Bairro
Reg20.Bairro :=
FCabecNF.FieldByName('BAIRRO').AsString+Delimitador;
// Cidade
Reg20.Cidade :=
FCabecNF.FieldByName('CIDADE').AsString+Delimitador;
// UF Destino
Reg20.UF :=
FCabecNF.FieldByName('UF_DEST').AsString+Delimitador;
// CEP
Reg20.CEP :=
FCabecNF.FieldByName('CEP').AsString+Delimitador;
// Pais **
Reg20.Pais :=
FCabecNF.FieldByName('PAIS_DEST').AsString+Delimitador;
// Telefone
Reg20.Fone :=
FCabecNF.FieldByName('FONE').AsString+Delimitador;
// IE Destinatario
Reg20.IEDest :=
FCabecNF.FieldByName('IE').AsString+Delimitador;
// Montagem do registro
result :=
Reg20.Registro + Reg20.Justificativa + Reg20.NatOp + Reg20.SerieNF+
Reg20.NumNF + Reg20.EmissaoNF + Reg20.DataES + Reg20.TipoNF +
Reg20.CFOP + Reg20.IEST + Reg20.InscMun + Reg20.CNPJDest +
Reg20.RazaoDest + Reg20.Logradouro + Reg20.Numero +
Reg20.Complemento + Reg20.Bairro + Reg20.Cidade + Reg20.UF +
Reg20.CEP + Reg20.Pais + Reg20.Fone + Reg20.IEDest;
end;Bem, é isto ai, acho que com isto já dá para apontar um caminho, o trabalho mais pesado mesmo é na base de dados, é o que deve ser feito primeiro e com mais cuidado, em seguida deve-se desenvolver uma inteface (uma tela) para apresentação ao usuário as notas fiscais emitidas e para que ele possa selecionar um periodo e incluir as nfs desejadas e então clicar no botão para gerar o arquivo que em seguida deverá ser validado pelo programa da Caixa fornecido gratuitamente aqui. Então, na minha opinião, o mais eficiente é fazer nesta ordem:
- Normalizar a base de dados
- Fazer a inteface com o usuario
- Desenvolver o codigo para gerar o arquivo (classe, procedure, etc)
- Testar, testar e testar
Abaixo apresento minha tela de iterface com o usuário:
Observação Final:
Enquanto escrevia este texto, varias idéias foram surgindo, como por exemplo implementar um método para guardar as informações sobre os arquivos gerados e as proprias NFs exportadas, bem como um outro para gerenciamento dos retornos dos arquivos enviados, o codigo continua em desenvolvimento, como tenho conversado como muitas pessoas que me pedem explicacoes e, neste caso, verbalmenteo custo é sempre maior para explicar, optei por publicar aqui este “ensaio”, provavelmente escreverei novos textos com novas abordagens no futuro, por hora espero que o expoxto aqui seja útil a alguém.
