Olá

Nesta série irei postar, sempre que possível, alguns desafios que enfrento no meu dia-a-dia, atuando como desenvolvedor de software.

Código Legado

Em 1995, ainda funcionário de uma empresa, iniciei a construção de um software de gestão com controle de estoques, emissão de Nota Fiscal (formulário), financeiro, etc. Levou cerca de 14 meses para colocá-lo em operação (importante saber que a empresa utilizava 14 terminais + servidor, ou seja, desde o início eu tinha que me cercar de todos os cuidados com transações concorrentes, carga de dados na rede, levando em conta tecnologia de redes da época, que não eram tão rápidas e estáveis quanto as de hoje). Após isso, ainda foram mais 3 anos de alterações e novas funcionalidades até que eu resolvesse montar meu próprio negócio e disponibilizar o software às demais empresas da minha região.

À época, meu status quo (conhecimento, preferências, hábitos…) me fez acreditar que aquilo era o melhor que eu podia fazer. Que entregara ao cliente o que havia de mais moderno em termos de programação.

Hoje, porém, quando revisito códigos desta época (acredite, ainda tenho alguns clientes com muito código legado… 8-O ), apesar de funcional, eu vejo como viajei por diversas vezes na maionese. :oops:

Recentemente me deparei com um caso assim: fatura de um pedido venda. Pois bem, este então será objeto de análise neste post.

Faturamento de uma Venda

AVISO: antes de continuar, quero ressaltar que este código data do início dos anos 2000. Portanto, não me julguem. ;)

Segue tela da fatura:
telafatura

Nela podemos:

  • Gerar parcela a vista, a prazo, gerar parcelamento em 2 ou mais parcelas;
  • Ver uma lista de parcelas;
  • Alterar uma ou todas as parcelas;
  • Ver os dados do pedido (data, valor, cliente)
  • Ver o status do pedido (pendente, faturado, cancelado);
  • Imprimir o pedido.

Sequência de operação do usuário do sistema:

  1. Gerar uma ou mais parcelas
  2. Conferir os dados
  3. Clicar no botão faturar
  4. Se faturado, clicar em imprimir (se a tela de impressão já não estiver sido aberta).

Não irei mexer nesta tela, apesar de ser um formulário criado há mais de uma década. Nosso foco será estritamente em cima do botão faturar (F5):
(que os deuses do Olimpo me ajudem):

procedure TfrmPedFaturaCaixa.actFaturaPedExecute(Sender: TObject);
var
  sele, historico, nomeuser: string;
  codrec, codlanc, codcont: integer;
  Data: TDateTime;
  boletos: string;
  loBanco: string;
  loAgente: TAgente;
  Erros : TStringList;
begin
  if tbpar.RecordCount = 0 then
  begin
    screen.cursor := crdefault;
    Mensagem('PEDIDO ainda não tem parcelas!');
    Exit;
  end;

  if Comparadb(PegaCampos(pedido), pedido, 'pedvenda',
    pedido.fieldbyname('CODPED').AsString) then
  begin
    Exit;
  end;

  if pedido.fieldbyname('FATURA').AsSTRING = 'S' then
  begin
    screen.cursor := crdefault;
    Mensagem('PEDIDO já Faturado!');
    Exit;
  end;

  if pedido.fieldbyname('Situacao').AsSTRING = 'C' then
  begin
    screen.cursor := crdefault;
    Mensagem('PEDIDO está Cancelado!');
    exit;
  end;

  with pg1 do
  begin
    close;
    sql.Clear;
    sql.Add('select mec from pedserv where codped=:codped');
    sql.Add('and (mec is null or mec=0)');
    ParamByName('codped').AsString := pedido.fieldbyname('codped').AsString;
    open;

    if recordcount > 0 then
    begin
      Mensagem('É necessário informar mecânico que executou os serviços!');
      exit;
    end;

    close;
    sql.Clear;
    sql.Add('select * from pedpecas where codped=:codped');
    sql.Add('and qtdn>0');
    ParamByName('codped').AsString := pedido.fieldbyname('codped').AsString;
    open;

    if Recordcount > 0 then
    begin
      Mensagem('Existem produtos pendentes neste pedido! Faça a entrega antes de prosseguir.');
      exit;
    end;
  end;

  if verificaparcela(1) = false then
    exit;

  if ce1.value > 0 then
  begin
    Mensagem('O valor das parcelas não bate com o valor do pedido!');
    exit;
  end;

  tbpar.First;

  data := tbparVENCTO.AsDateTime;

  while not tbpar.Eof do
  begin
    if data< tbparVENCTO.AsDateTime then data := tbparVENCTO.AsDateTime;
    tbpar.next;
  end;

  if not ValidarData(data, True,True,'FATURA PEDIDO','DATA VENCIMENTO') then exit;

  tbpar.DisableControls;
  try
    tbpar.First;
    Erros := TStringList.Create;
    boletos := '';
    loBanco := '';

    Menuprin.aaa.StartTransaction;
    try
      if menuprin.empconf.fieldbyname('PED_FAT_MINIMA').AsCurrency>0 then
      begin
        sele := 'select autorizado from funcionario where codfunc=' +
        pedido.FieldByName('codfunc').asString + ' and Situacao=''A''';
        pg1.Close; pg1.sql.text:=sele; pg1.open;
        while not tbpar.Eof do
        begin
          if (tbparVALOR.Value<menuprin.empconf.fieldbyname('PED_FAT_MINIMA').AsCurrency) and
            (tbparDIAS.Value>0) then
          begin
            Erros.Add('Valor da parcela menor do que o mínimo permitido!');
          end;
          if pg1.FieldByName('autorizado').AsString='V' then
          if tbparDIAS.Value>0 then
          begin
            Erros.Add('Este vendedor somente é autorizado a fazer venda a vista!');
          end;
          if pg1.FieldByName('autorizado').AsString='P' then
          if tbparDIAS.Value=0 then
          begin
            Erros.Add('Este vendedor somente é autorizado a fazer venda a prazo!');
          end;
          if Erros.Text <> '' then
          begin
            if Pergunta('As seguintes restricoes foram encontradas:'+#13+Erros.text+#13#13+
            'Deseja solicitar liberação do gerente?') then
            begin
              if execGerente(nomeuser) then
              begin
                LogGeral(logLiberou,'GERENTE '+NOMEUSER+
                  ' LIBEROU VENDA A PRAZO C/FAT ABAIXO DO MINIMO. PED: '+
                  pedido.FieldByName('codped').asstring);
                Break;
              end
              else
              begin
                Menuprin.aaa.Rollback;
                exit;
              end;
            end
            else
            begin
              begin
                Menuprin.aaa.Rollback;
                Exit;
              end;
            end;
          end;
          tbpar.next;
        end;
      end;
      tbpar.First;
      codrec := maximo('CODREC', 'RECEBER');
      codlanc := maximo('registro', 'LancBanco');
      Cheque.zeraValores;
      Cheque.REGISTRO := Maximo('Registro', 'CHQRECEBIDO');
      codcont := 1;
      while not TBPAR.eof do
      begin
        //lança os cheques
        if cdsCheque.Locate('parcela', tbparPARCELA.asinteger,
          [loCaseInsensitive]) then
        begin
          Cheque.NUMCONTA := cdsChequeNumconta.AsString;
          Cheque.AGENCIA := cdsChequeAgencia.AsString;
          Cheque.NUMCHQ := cdsChequeNumchq.AsString;
          if cdsChequeBompara.AsDateTime > 0 then
          begin
            Cheque.PREDATADO := 'S';
            Cheque.BOMPARA := cdsChequeBompara.AsDateTime;
          end;
          Cheque.VALOR := cdsChequeValor.AsCurrency;
          Cheque.OBS := 'Chq. gerado na fatura do pedido';
          Cheque.TITULAR := cdsChequeTitular.AsString;
          Cheque.CODBANCO := cdsChequeCodbanco.AsInteger;
          Cheque.CODFUNC := pedido.fieldbyname('codfunc').asinteger;
          Cheque.CODCLIE := pedido.fieldbyname('CODCLIE').asinteger;
          Cheque.PEDIDO := pedido.fieldbyname('CODPED').AsString;
          Cheque.CUSTODIA := cdsChequeCustodia.AsString;
          Cheque.DTCUSTODIA := DataSis;
          Cheque.Incluir;
          with exec2 do
          begin
            close;
            sql.clear;
            sql.Add('update pedfat set idchq=' + inttostr(cheque.registro));
            sql.Add('where codped=' + pedido.fieldbyname('CODPED').AsString);
            sql.Add('and parcela=' + tbparParcela.AsString);
            ExecQuery;
          end;
          Cheque.REGISTRO := Cheque.REGISTRO + 1;
        end;

        if TBPAR.FieldByName('DIAS').AsINTEGER = 0 then
        begin
          if menuprin.empconf.fieldbyname('ContaCx').AsInteger = 0 then
            raise Exception.Create('Informe a conta caixa nas configurações da empresa!');

          if not SitClieConsumidor(TBPAR.FieldByName('DIAS').AsINTEGER) then
          begin
            menuprin.aaa.Rollback;
            exit;
          end;

          if codcont = 1 then
            if not verif_caixa(menuprin.empconf.fieldbyname('ContaCx').asstring, datasis, True) then
            begin
              menuprin.aaa.Rollback;
              exit;
            end;

          sele := 'insert into LancBanco (registro,data,codbanco,hora,debito,' +
            'credito,historico,sldant,documento,CodUser,CodOp,CodPed,CodAg,IdChq, CodEmp, tpMov)' +
            ' values (:0,:1,:2,:3,:4,:5,:6,:7,:8,:9,:10,:11,:12,:13,:14, 0)';
          historico := COPY('PED.N. ' + pedido.fieldbyname('codped').AsString +
            ' P/ ' + edCliente.Text, 1, 40);
          exec2.close;
          exec2.sql.text := sele;
          exec2.params[0].asinteger := codlanc;
          exec2.params[1].asdatetime := DataSis;
          exec2.params[2].asstring :=  menuprin.empconf.fieldbyname('ContaCx').AsString;
          exec2.params[3].asstring := HoraSis;
          exec2.params[4].ascurrency := 0;
          exec2.params[5].ascurrency := tbpar.FIELDBYNAME('VPAGO').ASCURRENCY;
          exec2.params[6].asstring := historico;
          exec2.params[7].asstring := 'N';
          exec2.params[8].asstring := tbpar.fieldbyname('DOC').asSTRING;
          exec2.params[9].asinteger := menuprin.usuario.fieldbyname('coduser').asinteger;

          if (Trim(menuprin.empconf.fieldbyname('centroAV').asstring)='') or
          (menuprin.empconf.fieldbyname('centroAV').AsInteger<=0) then
            raise Exception.Create('Configure o código do centro de custo a vista'+#10+
            'na configuração da empresa.');

          exec2.params[10].asstring := menuprin.empconf.fieldbyname('centroav').AsString;
          exec2.params[11].asstring := pedido.fieldbyname('codped').AsString;
          exec2.params[12].asstring := tbpar.fieldbyname('codag').asstring;
          exec2.params[13].asstring := tbpar.fieldbyname('idchq').asstring;
          exec2.params[14].asstring := MENUPRIN.Empresa;
          exec2.ExecQuery;
          inc(codlanc);
          if TBPAR.FieldByName('troco').ascurrency > 0 then
          begin
            sele := 'insert into LancBanco (registro,data,codbanco,hora,debito,' +
              'credito,historico,sldant,documento,CodUser,CodOp,Codped,CodAg, CodEmp, tpMov)' +
              ' values (:0,:1,:2,:3,:4,:5,:6,:7,:8,:9,:10,:11,:12, :13,0)';
            exec2.close;
            exec2.sql.text := sele;
            exec2.params[0].asinteger := codlanc;
            exec2.params[1].asdatetime := DataSis; //strtodate(label5.caption);
            exec2.params[2].asstring :=
              menuprin.empconf.fieldbyname('ContaCx').AsString;
            exec2.params[3].asstring := HoraSis;
            exec2.params[4].ascurrency := tbpar.FIELDBYNAME('troco').ASCURRENCY;
            exec2.params[5].ascurrency := 0;
            exec2.params[6].asstring   := COPY('TROCO ' + historico, 1, 40);
            exec2.params[7].asstring   := 'N';
            exec2.params[8].asstring   := tbpar.fieldbyname('DOC').asSTRING;
            exec2.params[9].asinteger  := menuprin.usuario.fieldbyname('coduser').asinteger;
            exec2.params[10].asstring  := menuprin.empconf.fieldbyname('centroav').AsString;
            exec2.params[11].asstring  := pedido.fieldbyname('codped').AsString;
            exec2.params[12].asstring  := tbpar.fieldbyname('codag').asstring;
            exec2.params[13].asstring  := MENUPRIN.Empresa;
            exec2.ExecQuery;
            inc(codlanc);
          end;
        end
        else
        begin
          sele := 'select * from receber where situacao<>2 and documento=''' +
            tbpar.fieldbyname('doc').asstring + '''';
          pg1.close;
          pg1.SQL.text := sele;
          pg1.open;
          if pg1.recordcount > 0 then
            raise exception.Create('Já existe documento ' +
              tbpar.fieldbyname('doc').asstring +
              ' lançado no contas a receber.'#10 +
              'Não será possivel completar operação!');

          loAgente := tAgente.Create;
          try
            loAgente.CODAG := tbparCODAG.AsInteger;
            MenuPrin.Dao.Buscar(loAgente);

            if loAgente.BOLETO = 'S' then
            begin
              if boletos='' then
                boletos := IntToStr(codrec)
              else
                boletos := boletos+','+IntToStr(codrec);
              loBanco   := loAgente.CODBANCO.ToString;
            end
            else
              loBanco   := '0';
          finally
            loAgente.Free;
          end;

          sele := 'insert into RECEBER (CODCLIE,codtipo,codemp,situacao,documento,nossonum,obs,' +
            'emissao,vencto,dtLanca,dtDesconto,dtPagto,CodAg,';
          sele := sele +
            'vDescAnt,valor,pjuros,pmulta,vpagto,vjuros,vmulta,vdesconto,' +
            'acrescimo,vtotal,CODREC,BolPrint,CodFunc,instr1,instr2,instr3,vsaldo,idchq, titger, codped, banco) values ' +
            '(:0,:1,:2,:3,:4,:5,:6,:7,:8,:9,:10,:11,:12,:13,:14,:15,:16,:17,:18,' +
            ':19,:20,:21,:22,:23,''N'',:24,:25,:26,:27,:28,:29, ''N'', :codped, :banco)';
          exec2.close;
          exec2.sql.clear;
          exec2.sql.add(sele);
          exec2.params[0].asstring := pedido.fieldbyname('codclie').AsString;

          if (Trim(menuprin.empconf.fieldbyname('centroAP').asstring)='') or
          (menuprin.empconf.fieldbyname('centroAP').AsInteger<=0) then
            raise Exception.Create('Configure o código do centro de custo a prazo'+#10+
            'na configuração da empresa.');

          exec2.params[1].asstring := menuprin.empconf.fieldbyname('centroap').AsString;
          exec2.params[2].asstring := MENUPRIN.Empresa;
          exec2.params[3].asstring := '0';
          exec2.params[4].asstring := tbpar.fieldbyname('doc').asstring;
          sele := tbpar.fieldbyname('doc').asstring;
          sele := tbpar.fieldbyname('valor').asstring;
          exec2.params[5].asstring := '';
          exec2.params[6].asstring := '';
          exec2.params[7].asdatetime := DataSis;
          exec2.params[8].asstring := tbpar.fieldbyname('vencto').asstring;
          exec2.params[9].asstring := DATETOSTR(DataSis);
          if menuprin.empconf.FieldByName('boleto_Desconto').AsCurrency>0 then
          begin
            exec2.params[10].AsDateTime := tbpar.fieldbyname('vencto').AsDateTime - menuprin.empconf.FieldByName('boleto_diasDesconto').AsInteger;
          end
          else
          exec2.params[10].CLEAR;
          exec2.params[11].CLEAR;
          exec2.params[12].asstring := tbpar.fieldbyname('codag').asstring;

          if menuprin.empconf.FieldByName('boleto_Desconto').AsCurrency>0 then
          exec2.params[13].ascurrency := tbpar.fieldbyname('valor').ascurrency * menuprin.empconf.FieldByName('boleto_Desconto').AsCurrency / 100
          else
          exec2.params[13].ascurrency := 0;

          exec2.params[14].ascurrency := tbpar.fieldbyname('valor').ascurrency;
          exec2.params[15].ascurrency := menuprin.empconf.fieldbyname('boleto_txjuros').ascurrency;
          exec2.params[16].ascurrency := menuprin.empconf.fieldbyname('boleto_multa').ascurrency;
          exec2.params[17].asstring := '0';
          exec2.params[18].asstring := '0';
          exec2.params[19].asstring := '0';
          exec2.params[20].ascurrency := 0;
          exec2.params[21].ascurrency := 0;
          exec2.params[22].ascurrency := tbpar.fieldbyname('valor').ascurrency;
          exec2.params[23].asinteger  := codrec;
          exec2.params[24].asstring   := pedido.fieldbyname('codfunc').AsString;
          exec2.params[25].asstring   := menuprin.empconf.fieldbyname('boleto_obs1').asstring;
          exec2.params[26].asstring   := menuprin.empconf.fieldbyname('boleto_obs2').asstring;
          exec2.params[27].asstring   := '';
          exec2.params[28].ascurrency := tbpar.fieldbyname('valor').ascurrency;
          exec2.params[29].AsInteger  := tbpar.fieldbyname('idchq').AsInteger;
          exec2.params[30].asstring   := pedido.fieldbyname('codped').AsString;
          exec2.params[31].asstring   := loBanco;
          exec2.ExecQuery;
          codrec := codrec + 1;
        end;
        Inc(codcont);
        tbpar.next;
      end;
      sele := 'update PEDVENDA set FATURA=:fat, DTFAT=:1 where codped=' +
        pedido.fieldbyname('codped').AsString;
      exec2.close;
      exec2.sql.clear;
      exec2.sql.add(sele);
      exec2.params[0].asSTRING   := 'S';
      exec2.params[1].asDATETIME := DataSis;
      exec2.ExecQuery;

      LogGeral(logFaturou, 'PEDIDO COD: ' +
        pedido.fieldbyname('codped').AsString);

      Menuprin.aaa.Commit;

      //imprime boletos
      if Menuprin.empconf.FieldByName('boleto_auto').AsInteger = 1 then
      begin
        if boletos<>'' then
        begin
          frmPedFatBoleto := TfrmPedFatBoleto.Create(self);
          try
            frmPedFatBoleto.selTitulos := boletos;
            frmPedFatBoleto.ShowModal;
          finally
            FreeAndNil(frmPedFatBoleto);
          end;
        end;
      end;

      if Menuprin.empconf.FieldByName('ImpAuto').AsString = 'S' then
        actImprimirExecute(nil);
    except
      on E: EDataBaseError do
      begin
        Menuprin.aaa.Rollback;
        Mensagem('Erro: ' + e.Message, MB_ICONERROR);
      end;
      on E: exception do
      begin
        menuprin.aaa.Rollback;
        Application.ShowException(E);
      end;
    end;
  finally
    tbpar.EnableControls;
    getPedido(pedido.fieldbyname('codped').AsString);
    screen.cursor := crdefault;
  end;
end;

Ok, Ok! É perfeitamente normal e humano que, ao olhar este código, você seja levado a soltar um “que p… é essa!”. Eu mesmo, criador deste monstro, assim o fiz (mentalmente, confesso)! Então fique tranquilo. Para os programadores antigos (Clipper, Delphi 1 e 2, por exemplo):

Quem nunca cometeu este pecado que atire a primeira pedra!

Com muita fé e coragem, vamos destrinchar esta joça:

  • Primeiro fato que salta aos olhos: um método gigantesco contendo múltiplas responsabilidades!
  • Num mesmo método (execute da action), temos validações, consultas, comandos de atualização de tabelas do banco de dados (inserts e updates)
  • Programação totalmente procedural, um emaranhado de código, tudo no formulário! E olha que este é apenas o método responsável pela fatura, sem contar os códigos para montagem da tela em si (status do form, busca de informações para atualizar os campos, etc.)
  • Falta de padrão e bom-senso na nomenclatura dos objetos e variáveis
  • Entre outros…

Urge a necessidade de refatorar este código. Refatorar-lo-ei com a ajuda de nosso projeto de ORM-Básico. Com ele, o trabalho será facilitado.

Validações

O primeiro passo será separar as validações numa classe que terá essa única responsabilidade, ou seja, a de validar as informações da fatura:

  TValidaFatura = class
  private
    FErros: TStringList;
    FPedido: TPedVenda;
    FCliente: TClientes;
    FParcela: TDataSet;

    function SomaParcelas: Currency;
    function SitClieConsumidor(Prazo: integer): Boolean;
    function ValidaFaturaMinima: Boolean;
    function ValidaParcelas: Boolean;
    function ValidaParcelaAVista: Boolean;
    function ValidaParcelaAPrazo: Boolean;
    function ValidaPrestadorServico: Boolean;
    function ValidaProdutosPendentesDeBaixa: Boolean;
  public
    constructor Create(AParcela: TDataSet; APedido: TPedVenda; ACliente: TClientes); reintroduce;
    destructor Destroy; override;

    function ValidaFatura: Boolean;
    function Erros: TStringList;
  end;

Esta classe possui o método que irá chamar todas as validações, o método ValidaFatura:

function TValidaFatura.ValidaFatura: Boolean;
begin
  Result := False;

  if FParcela.RecordCount = 0 then
  begin
    Mensagem('PEDIDO ainda não tem parcelas!');
    exit;
  end;

  if Menuprin.Dao.TabelaDifDB(FPedido) then
  begin
    Mensagem('Pedido alterado em outro terminal ou sistema! Consulte novamente para obter dados atualizados.');
    exit;
  end;

  if FPedido.FATURA = 'S' then
  begin
    Mensagem('PEDIDO já Faturado!');
    exit;
  end;

  if FPedido.Situacao = 'C' then
  begin
    Mensagem('PEDIDO está Cancelado!');
    exit;
  end;

  if not ValidaPrestadorServico then
    Exit;

  if not ValidaProdutosPendentesDeBaixa then
    Exit;

  if not ValidaParcelas then
    exit;

  if not ValidaFaturaMinima then
    exit;

  Result := True;
end;

Classe de Fatura

Agora que foi resolvido a questão das validações, é necessário criar a classe responsável por faturar o pedido:

 TFatura = class
  private
    FPedido: TPedVenda;
    FPedFat: TPedFat;
    FCheque: TChequeRecebido;
    FCliente: TClientes;
    FValidaFatura: TValidaFatura;

    FBoletos: TStringList;

    //contadores
    CodRec,
    Registro,
    CodBanco: Integer;

    FParcela: TDataSet;
    FCheques: TDataSet;

    procedure InsereParcela;
    procedure InsereRegistroCaixaBanco;
    procedure InsereContasAReceber;
    procedure InsereCheque;

    procedure AdicionaBoletoNaLista;
    procedure SalvaStatusPedido;
    procedure InicializaContadores;
    procedure ProcessaParcelas;
  public
    constructor Create(AParcela, ACheque: TDataSet; APedido: TPedVenda); reintroduce;
    destructor Destroy; override;

    function FaturarPedido: Boolean;
    function Erros: TStringList;
    function Boletos: TStringList;

    procedure ImprimeBoletos;
  end;

O método principal da classe é o FaturarPedido:

function TFatura.FaturarPedido: Boolean;
begin
  Result := False;

  if not FValidaFatura.ValidaFatura then
    exit;

  FBoletos.clear;

  Screen.Cursor := crHourGlass;
  try
    Menuprin.Dao.StartTransaction;
    try
      InicializaContadores;

      InsereCheque;

      ProcessaParcelas;

      SalvaStatusPedido;

      LogGeralIBx(logFaturou, 'PEDIDO COD: ' + FPedido.codped.ToString);

      Menuprin.Dao.Commit;

      Result := True;
    except
      on E: Exception do
      begin
        Menuprin.Dao.Rollback;
        Mensagem('Erro: ' + E.Message, MB_ICONERROR);
      end;
    end;
  finally
    Screen.Cursor := crDefault;
  end;
end;

Entendendo o seu funcionamento:

  • Antes de mais nada, valida a fatura (linha 5)
  • Abre a transação (linha 12)
  • Inicia os contadores (id’s das tabelas – este é um ponto que ainda irei voltar, visto que a tabela do banco de dados está sem os generators. Então, neste primeiro momento, preferi não mexer nisso, visto que os clientes antigos teriam que ter seus bancos de dados alterados. Ficou um TODO aberto aqui. ;)
  • Insere os cheques informados no ato do parcelamento, lá no formulário de fatura. É apenas um DataSet contendo lista de cheques informados (com número, banco, conta, bom-para, titular e valor do cheque recebido).
  • Na linha 18, ProcessaParcelas, cuja responsabilidade é a de correr todas as parcelas, inserindo-as na base de dados.
  • Nas linhas 20 a 23, salva o status do pedido (faturado Sim e Data da Fatura) e gera um log no banco de dados. Este log, conterá além da descrição informada, a data, hora, computador onde foi feito o procedimento e o usuário.
  • Por fim, finaliza com um commit na base de dados e retorna true.

Implementação do método ProcessaParcelas:

procedure TFatura.ProcessaParcelas;
begin
  FParcela.DisableControls;
  try
    FParcela.First;
    while not FParcela.Eof do
    begin
      InsereParcela;
      if FParcela.fieldbyname('DIAS').asinteger = 0 then
        InsereRegistroCaixaBanco
      else
      begin
        AdicionaBoletoNaLista;
        InsereContasAReceber;
      end;
      FParcela.next;
    end;
  finally
    FParcela.first;
    FParcela.EnableControls;
  end;
end;

Exemplo do método de inserção da parcela (linha 8):

procedure TFatura.InsereParcela;
begin
  with FPedFat do
  begin
    Limpar;
    CODPED   := FParcela.FieldbyName('CODPED').AsInteger;
    DOC      := FParcela.FieldbyName('DOC').AsString;
    PARCELA  := FParcela.FieldbyName('PARCELA').asInteger;
    CODAG    := FParcela.FieldbyName('CODAG').AsInteger;
    ENTRADA  := FParcela.FieldbyName('ENTRADA').AsString;
    DIAS     := FParcela.FieldbyName('DIAS').AsInteger;
    VENCTO   := FParcela.FieldbyName('VENCTO').AsDateTime;
    VALOR    := FParcela.FieldbyName('VALOR').AsCurrency;
    NUMTP    := FParcela.FieldbyName('NUMTP').AsString;
    VPAGO    := FParcela.FieldbyName('VPAGO').AsCurrency;
    TROCO    := FParcela.FieldbyName('TROCO').AsCurrency;
    IDCHQ    := FParcela.FieldbyName('IDCHQ').AsInteger;
  end;
  Menuprin.Dao.Inserir(FPedFat);
end;

Para quem acompanhou a série ORM-Básico neste blog não estranha o código acima. Aqui, simplesmente passo os dados do dataset vindo do form. de fatura para o objeto FPedFat, e então, executo o comando de inserção na base de dados (linha 19).

Implementação do Método SalvaStatusPedido (linha 20 FaturarPedido):

procedure TFatura.SalvaStatusPedido;
begin
  FPedido.fatura := 'S';
  FPedido.dtfat := datasis;
  Menuprin.Dao.Salvar(FPedido);
end;

Perceba como facilita o entendimento quando os nomes dos objetos são auto descritivos, ao contrário do método no início deste post, onde tínhamos objetos com nomes como “pg1” (what’s that mean?!?).

Eis que enfim, temos algo apresentável!

Bom, agora vamos voltar ao método inicial, a action actFaturaPedExecute, e implementar as alterações:

procedure TfrmFaturaPedido.actFaturaPedExecute(Sender: TObject);
var
  Fatura: TFatura;
begin
  Fatura := TFatura.Create(cdsParcela, cdsCheque, FPedido);
  try
    if not Fatura.FaturarPedido then
      Exit;

    AtualizaForm;

    Mensagem('Pedido Faturado com Sucesso!');

    if Fatura.Boletos.count > 0 then
      Fatura.ImprimeBoletos;

    if Menuprin.empconf.fieldbyname('ImpAuto').AsString = 'S' then
      actImprimirExecute(nil);
  finally
    Fatura.Free;
  end;
end;

Voilà! Compare e verá que foi obtido um salto e tanto de qualidade! O que antes ocupava mais de 400(!) linhas de código no formulário de fatura conta agora com apenas 22 linhas. Ainda não está 100% e com certeza, puristas OO me passarão por um crivo intenso e muitas críticas surgirão. Mas é fato que melhorou e é isso que importa.

Espero que gostem, visto que, além do constrangimento (oh código feio, meu!), o tempo aqui, como sempre, anda escasso! Abraços.

Desenvolvedor de software desde 1995. Em 1998, abriu sua própria empresa, a Lukas Sistemas, desde então passou a atender diversas empresas, principalmente autopeças. Apaixonado por Delphi, porém não o impede de flertar com outras linguagens sempre que possível. Mora na cidade de Balsas/MA com sua esposa e dois filhos.

4 thoughts on “Dia-a-dia do Desenvolvedor de Software: Código Legado”

  1. Grande Luiz, impressionando aqui como de um turbilhão de código com mais de 400 linhas pra apenas 22 linhas,
    e isso me deixa assombrado, rss

    Tenho um sistema com mais de 15 anos, e esta assim um grande Frankiestain!

    Onde agora eu tenho conseguir separar as responsabilidades, quebrar os paradigmas do desenvolvimento
    procedural, arregaçar as mangas e partir pra luta.

    Mas, em fim, muito bacana seu artigo, a forma que você mostra isso, e que parece ser algo tão simples de fazer.

    grande abraço.

  2. Bom dia Luiz estou com um problema com uma procedure pra limpar objetos o Sr poderia me ajudar pois vejo que tem muito conhecimento sobre RTTI.

    1. Bom dia Erasmo

      Na série ORM-Básico, no projeto desenvolvido na mesma, foi implementado recentemente um método responsável por limpar objetos:

      procedure TAtributos.LimparCampos(ATabela: TTabela);
      var
        Contexto: TRttiContext;
        TipoRtti: TRttiType;
        PropRtti: TRttiProperty;
      begin
        Contexto := TRttiContext.Create;
        try
          TipoRtti := Contexto.GetType(ATabela.ClassType);
          for PropRtti in TipoRtti.GetProperties do
          begin
             case PropRtti.PropertyType.TypeKind of
               tkFloat,
               tkInteger: PropRtti.SetValue(ATabela, 0);
             else
               PropRtti.SetValue(ATabela, '');
             end;
          end;
        finally
          Contexto.free;
        end;
      end;
      

      É bastante simples, ele apenas verifica o tipo da propriedade e seta 0 para campos numéricos (inteiros, floats) e para os demais seta string vazia.

      Não sei se isto resolve o seu problema, mas já pode lhe dar um início.

      Abraços.

Deixe um comentário para Erasmo Cancelar resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.