Nota Fiscal Paulista Mod 1 e 1A – O Caminho das Pedras

July 26th, 2009 16 Comments   Postado em Delphi

nfp

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&gt;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 &lt;&gt; '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:

cadnf12

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.