Buscar

Plano de contas - Parte 1 de 2

nome

Exio

Colunista ainda sem descrição. Caro colunista, por favor envie-nos sua descrição.

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 adiciona 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 !!!

Publicidade

Vote no artigo




Quantidade de votos: 2 votos
Aceitação: 20%


Detalhes do artigo

Categoria: Banco de dados
Adicionado dia: 20/08/12
Por: Exio
Visualizado: 28246 vezes

Planeta Delphi - Tudo sobre programação Delphi Planeta Delphi - www.planetadelphi.com.br - Todos os direitos reservados | Copyright 2001-2009