Baixe o app para aproveitar ainda mais
Prévia do material em texto
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, oarquivo 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 propriedadesorttype 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: 01.procedure TForm1.trv_PlanoCKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); 02.var 03.i, n, ponto, oldV, newV : Integer; 04.textoPos : String; 05.begin 06.if ((Key = VK_INSERT) or (key = VK_F2)) then 07.if (trv_PlanoC.Selected = nil) then 08.begin 09.if trv_PlanoC.Items.Count = 0 then 10.trv_PlanoC.Items.Add(nil, '1. ') 11.else begin 12.oldV := 0; 13.newV := 0; 14.for i := 0 to pred(trv_PlanoC.Items.Count) do 15.begin 16.n := 1; 17.while n <= Length(trv_PlanoC.Items.Item[i].Text) do 18.begin 19.if (Copy(trv_PlanoC.Items.Item[i].Text, n, 1) = '.') then 20.begin 21.ponto := n; 22.if ponto > 1 then 23.newV := StrToInt(Copy(trv_PlanoC.Items.Item[i].Text, 1, ponto - 1)); 24.if oldV < newV then 25.begin 26.oldV := newV; 27.newV := 0; 28.end; 29.Break; 30.end; 31.n := n + 1; 32.end; 33.end; 34.trv_PlanoC.Items.Add(nil, format('%d. ',[oldV + 1])); 35.end; 36.end else begin 37.//essa parte veremos mais adiante… 38.end; 39.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_PlanoCadicionamos o código listado abaixo: 1.procedure TForm1.trv_PlanoCKeyPress(Sender: TObject; var Key: Char); 2.begin 3.If CharInSet(key, ['.']) then 4.begin 5.MessageDlg('Não digite ponto (.), o sistema usa esse sinal para nivelar as contas.', mtInformation, [mbOK], 0); 6.key := #0; 7.end; 8.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 sistemaverificará 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. 01.function TForm1.calcularNivelDependente(textoOrigem: String; adiciona : String): String; 02.var 03.nivel : array [0..20] of integer; 04.i, nivelC : SmallInt; 05.niveisTxt : TStringList; 06.saida : String; 07.begin 08.i := 0; 09.niveisTxt := TStringList.Create; 10.for i := 0 to 20 do 11.nivel[i] := -1; 12.try 13.niveisTxt.Text := StringReplace(textoOrigem, '.', #13, [rfReplaceAll, rfIgnoreCase]); 14.for i := 0 to niveisTxt.Count - 2 do 15.begin 16.nivel[i] := StrToInt(niveisTxt.Strings[i]); 17.nivelC := i; 18.end; 19. 20.if adiciona = '=' then 21.if nivel[nivelC + 1] = -1 then 22.nivel[nivelC + 1] := 1; 23. 24.if adiciona = '+' then 25.if nivel[nivelC] = -1 then 26.nivel[nivelC] := 1 27.else 28.nivel[nivelC] := nivel[nivelC] + 1; 29. 30.if adiciona = '=' then 31.for i := 0 to nivelC + 1 do 32.saida := saida + IntToStr(nivel[i]) + '.'; 33. 34.if adiciona = '+' then 35.for i := 0 to nivelC do 36.saida := saida + IntToStr(nivel[i]) + '.'; 37. 38.Result := saida; 39.finally 40.niveisTxt.Free; 41.end; 42.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 propriedadetext 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 destinostringlist, 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...” 01.with trv_PlanoC do 02.begin 03.Items.BeginUpdate; 04.if (Selected.HasChildren = False) then 05.begin 06.with trv_PlanoC.Items.AddChild(trv_PlanoC.Selected, format('%s ',[calcularNivelDependente(trv_PlanoC.Selected.Text, '=')])) do 07.MakeVisible; 08.end else begin 09.textoPos := trv_PlanoC.Selected.GetLastChild.Text; 10.with trv_PlanoC.Items.AddChild(trv_PlanoC.Selected, format('%s ',[calcularNivelDependente(textoPos, '+')])) do 11.MakeVisible; 12.end; 13.Items.EndUpdate; 14.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: 1.if (Key = VK_ESCAPE) then 2.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: 01.if (Key = VK_F4) then 02.begin 03.if trv_PlanoC.Selected = nil then 04.Application.MessageBox('Para poder cancelar uma conta, você deve selecionar a conta primeiro !', 'Sem Seleção de Conta', MB_ICONWARNING) 05.else 06.if Application.MessageBox(Pchar(format('Você deseja cancelar o uso da conta "%s" ?',[ trv_PlanoC.Selected.Text])), 07.'Cancelar Conta', MB_ICONQUESTION + MB_YESNO) = mrYes then 08.trv_PlanoC.Selected.Enabled := False; 09.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: 01.procedure TForm1.trv_PlanoCEditing(Sender: TObject; Node: TTreeNode; 02.var AllowEdit: Boolean); 03.begin 04.if Node.Enabled = False then 05.Begin 06.Application.MessageBox(Pchar(Format('A Conta "%s" foi Cancelada e não pode mais ser reutilizada !',[trv_PlanoC.selected.Text])),'Conta Cancelada', MB_ICONINFORMATION); 07.AllowEdit := False; 08.end; 09.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.
Compartilhar