PLANO DE CONTAS - "EU PRECISAVA DE UM DESSES NESSA FORMA E NÃO SEI COMO FAZER!" – PARTE 1 DE 2
I - INTRODUÇÃO
Estimados amigos, estou mais uma vez aqui e espero estar ajudando a alguém. Volto a mencionar que não sou nenhum programador de alto nível e nem detentor da verdade, porém, o mais importante é tentar. Depois de muito tempo, trabalhando e também viajando muito consegui sentar para escrever esse material. Nesses dias que se passaram um grande colega meu entrou em contato e pediu ajuda para desenvolver um Plano de Contas. A minha primeira recomendação caiu sobre os componentes da DevExpress. O grande problema como sabemos, é que cada carga de componente do DevExpress, o arquivo aumenta e a possibilidade de problemas também aumenta. Ao mesmo tempo ascendeu uma ideia de fazer o plano exclusivamente sobre o componente TreeView. E assim, se dá a sequência desse material.
OBSERVAÇÃO
Esse material é destinado exclusivamente ao site
PlanetaDelphi. Não pode ser copiado parcialmente ou na sua totalidade sem autorização do autor. Links podem ser realizados desde que atentem e respeitem as regras dos Direitos Autorais.
II - Do que precisamos?
Nada além do básico, Delphi e boa vontade. Os conhecimentos de programação não serão abordados. Usuários de nível iniciante conseguirão entender e fazer o trabalho tranquilamente.
III - Versão do Delphi
Para esse artigo foi usado o Delphi 2010.
IV - Explicando melhor o ocorrido
Como lhes falei anteriormente, esse amigo não sabia mais o que fazer, queria porque queria um plano de contas que fosse gerado automaticamente e com a respectiva hierarquia de códigos. Entendendo o problema, veio a sugestão do DevExpress, porém, os motivos foram comentados acima e a decisão foi por água abaixo. Num segundo momento, surge os componentes VirtualTreeView. Bem, levava mais tempo aprendendo o componente e as suas restrições e os “esqueminhas”, e no final, o problema se mantinha. Mas qual problema? Muitos planos de contas exigem uma janela secundária contendo um ou mais campos de edição, forçando o usuário a pressionar sobre o "ramo" desejado e em seguida, preencher os campos, e depois mais um botão, para finalmente surgir o "ramo". Existem alguns componentes no Delphi que nunca damos muita atenção, o TreeView é um deles; justamente por não darmos muita atenção, temos que aprender a utilizá-los sobre pressão. Pensando que: componentes de terceiros sempre aumentam consideravelmente o código-fonte; geram algumas exceções que resultam na criação de blocos de proteção sem necessidade; exigem algumas programações que aparentam ser desnecessárias, e por ai vai... Então, mantive a ideia do TreeView in natura do Delphi. Vamos aos problemas aparentemente existentes para o meu estimado amigo:
- 1- A inserção de itens tinha que ser feita de maneira natural, isto é, com poucos procedimentos "burocráticos";
- 2- Os códigos da hierarquia tinham que ser gerados automaticamente, isto é, na hora da inserção;
- 3- A recriação da árvore tem que ser rápida e sem muita programação;
- 4- contas excluídas tinham que ficar em um tom diferente e também ficar impossível de alterar;
- 5- Salvar o plano alterado ou criado rapidamente;
V - Fazendo acontecer
Execute o Delphi, crie um novo projeto e salve os arquivos da melhor forma que lhe convier. Adicione um componente
TreeView e renomeie a sua propriedade name para
trv_PlanoC. Mude a propriedade
sorttype para
stText.
Imagem 01 – adicionando o componente treeview
Existem dois problemas diretos no treeview que devemos nos preocupar, o primeiro é como o usuário está procedendo com a criação dos itens. Eles podem ser criados numa raiz – “root” sequencialmente, ou podem ser criados em um dos ramos existentes na raiz ou em outro sub-ramo. Além disso, você está querendo ordenar os elementos inseridos no treeview, essa ordenação pode ser dada por eventos no momento da inserção dos itens, ou pode ser pela forma definida na propriedade sorttype + método de inserção. Então, aí estão os dois problemas com que deve se preocupar. Vamos iniciar a codificação que visualizaremos melhor como será resolvido o problema.
Clique no componente
trv_PlanoC, e na aba Events localize o evento onkeyDown, dê dois cliques para acionar o editor e vamos a codificação como apresentada abaixo:
procedure TForm1.trv_PlanoCKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
var
i, n, ponto, oldV, newV : Integer;
textoPos : String;
begin
if ((Key = VK_INSERT) or (key = VK_F2)) then
if (trv_PlanoC.Selected = nil) then
begin
if trv_PlanoC.Items.Count = 0 then
trv_PlanoC.Items.Add(nil, '1. ')
else begin
oldV := 0;
newV := 0;
for i := 0 to pred(trv_PlanoC.Items.Count) do
begin
n := 1;
while n <= Length(trv_PlanoC.Items.Item[i].Text) do
begin
if (Copy(trv_PlanoC.Items.Item[i].Text, n, 1) = '.') then
begin
ponto := n;
if ponto > 1 then
newV := StrToInt(Copy(trv_PlanoC.Items.Item[i].Text, 1, ponto - 1));
if oldV < newV then
begin
oldV := newV;
newV := 0;
end;
Break;
end;
n := n + 1;
end;
end;
trv_PlanoC.Items.Add(nil, format('%d. ',[oldV + 1]));
end;
end else begin
//essa parte veremos mais adiante…
end;
end;
O que faz esse fragmento de código? Quando o usuário pressiona a tecla INSERT ou a tecla de função F2, o sistema fará uma verificação procurando por algum item dentro do
trv_PlanoC que esteja selecionado, se não houver uma seleção ativa (resultando em
nil) o próximo passo e a primeira condição
IF. Esse teste é o mais importante, já que ele verificará se não existem itens registrados na raiz através de
trv_PlanoC.items.count. Se nenhum item tiver sido registrado anteriormente, o primeiro registro ocorrerá através de
trv_PlanoC.Items.Add(nil, ‘1. ’).
Muitos tutoriais recomendam o uso de
AddFirst(), essa função registra o item no topo do lista, e isso particularmente não nos interessa.Atente que o valor do tipo string um (1) no parâmetro, será o primeiro código da estrutura; o ponto que acompanha o valor 1 será nossa referência para sabermos em que nível o código está sendo registrado.
Ele será o ponto de partida. Continuando a leitura do código, vemos a condição
False do
IF. As duas primeiras variáveis
oldV e
newV (oldvalue e newvalue) são variáveis de troca, a variável
ponto é de posição do ponto (sinal ponto “.”). No primeiro
For o sistema verificou que existem diversas raízes (ou uma pelo menos) e que não há seleção de nenhum dos itens no TreeView.
Pega o primeiro item e extrai o tamanho dele através da função Length, em seguida, procura o sinal ponto “.”. Como consequência, o tamanho do número é copiado e convertido para a variável
oldV , e assim sucessivamente será executado com os demais itens. À medida que os valores copiados são maiores que os anteriores, o valor da variável
oldV será substituído e a ordenação vai ocorrendo naturalmente.
Imagem 02 – Executando a criação de itens na raiz
É interessante que não precisa ser escrito nenhum código para editar esses itens que foram criados, o usuário só precisa clicar sobre os itens como se fosse executar um procedimento de renomear arquivos.
A parte mais simples está feita, como geramos uma formatação em nosso código, temos que nos preocupar com o lado do cliente. Para termos certeza que não ocorrerão saltos nos códigos porque nossos usuários resolveram adicionar pontos nas contas, no evento
onKeyPress do
trv_PlanoC adicionamos o código listado abaixo:
procedure TForm1.trv_PlanoCKeyPress(Sender: TObject; var Key: Char);
begin
If CharInSet(key, ['.']) then
begin
MessageDlg('Não digite ponto (.), o sistema usa esse sinal para nivelar as contas.', mtInformation, [mbOK], 0);
key := #0;
end;
end;
Como pode ser observado, o nosso primeiro código digitado não tem uma parte final que é justamente a mais importante de todas. Através dessa continuação é que o sistema verificará as ocorrências para itens selecionados em nossa árvore. Exemplo: Vamos supor que foram criadas as duas ou quatro contas principais de nosso plano de contas, algo como:
- 1. RECEITAS
- 2. DESPESAS
- 3. RECEITAS NÃO OPERACIONAIS
- 4. DESPESAS NÃO OPERACIONAIS
Se desejar continuar a hierarquia através do pressionamento de INSERT ou F2, nada ocorrerá. Vamos então a parte complementar desse código. Para que a codificação possa ser processada, temos duas novas condições que poderão ocorrer, sendo: a primeira é que o usuário pode estar no topo de uma raiz e quer adicionar itens; a segunda: ele pode estar no meio da raiz. Graças à função seguinte, ela nos criará todas as sequências necessárias para executar a tarefa com sucesso.
function TForm1.calcularNivelDependente(textoOrigem: String; adiciona : String): String;
var
nivel : array [0..20] of integer;
i, nivelC : SmallInt;
niveisTxt : TStringList;
saida : String;
begin
i := 0;
niveisTxt := TStringList.Create;
for i := 0 to 20 do
nivel[i] := -1;
try
niveisTxt.Text := StringReplace(textoOrigem, '.', #13, [rfReplaceAll, rfIgnoreCase]);
for i := 0 to niveisTxt.Count - 2 do
begin
nivel[i] := StrToInt(niveisTxt.Strings[i]);
nivelC := i;
end;
if adiciona = '=' then
if nivel[nivelC + 1] = -1 then
nivel[nivelC + 1] := 1;
if adiciona = '+' then
if nivel[nivelC] = -1 then
nivel[nivelC] := 1
else
nivel[nivelC] := nivel[nivelC] + 1;
if adiciona = '=' then
for i := 0 to nivelC + 1 do
saida := saida + IntToStr(nivel[i]) + '.';
if adiciona = '+' then
for i := 0 to nivelC do
saida := saida + IntToStr(nivel[i]) + '.';
Result := saida;
finally
niveisTxt.Free;
end;
end;
Explicando. Essa função é importante porque é através dela, que será gerado o código para o próximo nível de nosso plano de contas ou para o nível atual. O parâmetro
textoOrigem recebe a propriedade
text da posição atual ou da última posição, algo como
1.2.1 Receitas com Vendas na Internet. O parâmetro a
diciona recebe dois possíveis valores, que são: “=” (igual) ou “+” (mais).
Esse é um padrão que eu adotei, poderia ser feito de outra forma. Quem sabe outros programadores poderão recomendar melhores formas programáticas para essa função. No bloco de variáveis existe uma em especial que gostaria de chamar atenção, é a variável
nível do tipo array de inteiros. Essa variável é usada para medir o nível em que a sua seleção está posicionada.
Eu usei um array de 20 elementos, isso é um absurdo! O problema é que existem usuários e usuários; outro detalhe importante é que os próprios Conselhos Regionais de Contabilidade recomendam um padrão para Planos de Contas, veja, recomendam.
Na maioria dos livros que tenho, os melhores ou mais recomendados planos de contas possuem no máximo cinco (5) níveis, isso quer dizer: U.X.Y.W.Z. Mas coloquei 20 para ser bem exagerado e garantir que nenhum mortal da empresa do meu amigo vai deixar de fazer um plano de contas da forma que mais gostar. Criei um
StringList que vai desmembrar o texto separado pelos pontos.
Na sequência, defini um valor padrão para todos os componentes do array, o valor
-1. Após desmembrar o texto usando a função
StringReplace, que eu considero o biotônico Fontoura (risos) da programação, aplico um
For subtraindo de 2. Explico o por que. Veja: Vamos dizer que estamos dentro de uma conta assim nomeada:
1.2. Vendas a Vista. Ao desmembrar esse texto com
stringReplace para um destino
stringlist, teremos a seguinte forma de ocupação:
- [0] 1
- [1] 2
- [2] Vendas à Vista
Perceba que todas as propriedades Count de um listbox, memo, combobox é sempre um valor a mais. Então uso menos 2, logo: 3 – 2 = 1, capturando somente os valores dos vetores nas casas 0 e 1. Agora, atente para o resto da função. Estamos em 1. RECEITAS (selecionada), queremos criar duas contas dependentes de RECEITAS. Vamos então pegar a posição atual e adicionar um nível, ocupando uma nova casa do vetor. O resto da função é destinado somente à soma de Strings para retorno de função.
Adicione o seguinte fragmento de código no evento onKeyDown onde você encontra o texto “
essa parte veremos mais adiante...”
with trv_PlanoC do
begin
Items.BeginUpdate;
if (Selected.HasChildren = False) then
begin
with trv_PlanoC.Items.AddChild(trv_PlanoC.Selected, format('%s ',[calcularNivelDependente(trv_PlanoC.Selected.Text, '=')])) do
MakeVisible;
end else begin
textoPos := trv_PlanoC.Selected.GetLastChild.Text;
with trv_PlanoC.Items.AddChild(trv_PlanoC.Selected, format('%s ',[calcularNivelDependente(textoPos, '+')])) do
MakeVisible;
end;
Items.EndUpdate;
end;
Porque essa parte é importante? Porque devemos entender o seguinte: se estamos no topo da raiz, em uma conta qualquer, é necessário saber se temos dependentes dessa conta (“Child”). Veja:
Ex.1)
1. RECEITAS
Ex.2)
1. RECEITAS
1.1 Vendas à Vista
No primeiro exemplo não temos dependentes (“Child”), no exemplo 2 temos um dependente. No exemplo um, adicionamos um nível inferior porque testamos a existência de dependentes através da função
HasChildren. Então usamos a opção “=” para identificar que queremos criar níveis abaixo.
Caso estivéssemos no exemplo 2, o procedimento verifica a existência de um “ramo” da Conta RECEITAS, então desloca o cursor para o ramo “1.1 Vendas à Vista”, ocupa as duas primeiras casas do vetor e adicionar um nova casa. Veja o resultado na figura abaixo de nosso sistema em execução.
imagem 03 – Nosso plano de contas funcionando
Na verdade, depois de todo o código estar digitado e funcionando, não parece ser um código que faz muita coisa, mas “ele” quebra um galho danado. Continuando a nossa expedição pelo Plano de Contas, vamos adicionar um novo código no evento onKeyDown de nosso trv_PlanoC:
if (Key = VK_ESCAPE) then
trv_PlanoC.Selected := nil;
Esse pequeno fragmento de código permite remover a seleção de qualquer item que esteja selecionado atualmente no nosso Plano de Contas, é útil porque podemos voltar a criar novas contas na raiz.
É incrível, mas depois que a parte nervosa fica pronta, parece que os tratamentos de determinados tipos de situações de controle ficam mais fáceis de serem programados. Vamos supor agora, que desejamos cancelar uma conta em nosso plano. A conta ao invés de ser excluída, ficará desabilitada. No mesmo evento onKeyDown entre com o seguinte código:
if (Key = VK_F4) then
begin
if trv_PlanoC.Selected = nil then
Application.MessageBox('Para poder cancelar uma conta, você deve selecionar a conta primeiro !', 'Sem Seleção de Conta', MB_ICONWARNING)
else
if Application.MessageBox(Pchar(format('Você deseja cancelar o uso da conta "%s" ?',[ trv_PlanoC.Selected.Text])),
'Cancelar Conta', MB_ICONQUESTION + MB_YESNO) = mrYes then
trv_PlanoC.Selected.Enabled := False;
end;
Quando o usuário pressionar a tecla de função F4, será feita uma verificação para ver se uma conta foi selecionada antes de ser desabilitada. Existindo um item selecionado, o sistema o desabilita através da propriedade Enabled, com o seguinte código: trv_PlanoC.Selected.Enabled := False;
Imagem 04 – Ao pressionar F4 para desabilitar uma conta
imagem 05 – Itens desabilitados no plano de contas
Ao desabilitar uma conta, o sistema não trava o item do TreeView, ele simplesmente fica numa cor diferente, se você quiser renomear, nada o impedirá. Mas para que exista esse impedimento e que o usuário não possa alterar uma conta desabilitada, adicione no evento
onEditing do
trv_PlanoC:
procedure TForm1.trv_PlanoCEditing(Sender: TObject; Node: TTreeNode;
var AllowEdit: Boolean);
begin
if Node.Enabled = False then
Begin
Application.MessageBox(Pchar(Format('A Conta "%s" foi Cancelada e não pode mais ser reutilizada !',[trv_PlanoC.selected.Text])), 'Conta Cancelada', MB_ICONINFORMATION);
AllowEdit := False;
end;
end;
Quando o usuário clicar sobre a conta tentando renomear, a advertência será lançada. Quem trava a edição desse item é a propriedade
AllowEdit que fica definida para
false.
Por hoje é só! Mas na sequência faremos o resto ao que foi proposto. A recriação do Plano de Contas e alguns pequenos detalhes que na verdade não foram colocados nesse texto e que foram solicitados por ele (meu estimado amigo) virão na segunda parte – a continuação. Se outros tiverem uma forma melhor de fazer isso, e com certeza tem, gostaria de receber as contribuições para melhorar esse exemplo.
Download desta aula
plano-de-contas-fontes.zip
Abraços.
Exio – bitzero_000
Até a próxima aula !!!