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… 😯 ), apesar de funcional, eu vejo como viajei por diversas vezes na maionese. 😳
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. 😉
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:
- Gerar uma ou mais parcelas
- Conferir os dados
- Clicar no botão faturar
- 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):
[sourcecode language=”Delphi”]
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;
[/sourcecode]
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:
[sourcecode language=”Delphi”]
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;
[/sourcecode]
Esta classe possui o método que irá chamar todas as validações, o método ValidaFatura:
[sourcecode language=”Delphi”]
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;
[/sourcecode]
Classe de Fatura
Agora que foi resolvido a questão das validações, é necessário criar a classe responsável por faturar o pedido:
[sourcecode language=”Delphi”]
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;
[/sourcecode]
O método principal da classe é o FaturarPedido:
[sourcecode language=”Delphi”]
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;
[/sourcecode]
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:
[sourcecode language=”Delphi”]
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;
[/sourcecode]
Exemplo do método de inserção da parcela (linha 8):
[sourcecode language=”Delphi”]
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;
[/sourcecode]
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):
[sourcecode language=”Delphi”]
procedure TFatura.SalvaStatusPedido;
begin
FPedido.fatura := ‘S’;
FPedido.dtfat := datasis;
Menuprin.Dao.Salvar(FPedido);
end;
[/sourcecode]
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:
[sourcecode language=”Delphi”]
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;
[/sourcecode]
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.
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.
Grande Cleiton, é isso! Temos que sair da zona de conforto e partir pra luta.
Vlw!
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.
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:
[sourcecode language=”Delphi”]
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;
[/sourcecode]
É 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.