Utilizando Classe no Access - As Classes Venda e DatalheVenda
Nota importante: para ter acesso aos vídeos e arquivos exemplos deste site, adquira um dos planos apresentados abaixo. Você pode comprar em até 5x no Cartão de Crédito, através do Paypal.
Veja como comprar e saiba mais sobre o material oferecido, clicando aqui.
Por: Plinio Mabesi
Estamos chegando na reta final da criação do sistema de vendas orientado a objetos.
Nesta etapa veremos a codificação das classes Venda e DetalheVenda, além da implementação de um PDV que será a nossa interface gráfica para realização das vendas.
No desenrolar da criação dos códigos com certeza você encontrará rotinas repetitivas, funções não tão necessárias e instâncias em excesso. Além disso, poderá sentir falta de funcionalidades importantes em um PDV ou mesmo na totalidade do sistema de vendas. E digo mais, agora que já é praticamente um “expert” em VBA e orientação a objetos perceberá que um ou outro método poderia ser mais bem implementado de outra maneira. Mas antes de queimar neurônios tentando entender por que o trabalho está assim tão “mal projetado”, lembre-se que tudo foi feito com fins didáticos, e a repetitividade foi proposital, somente para que você fique cansado de tanto manipular objetos e realmente aprenda como utilizá-los.
Não estaremos totalmente livres do aparecimento de “bugs”, pois apenas nos locais estritamente necessários foram inseridas rotinas de tratamento de erros. Estes algoritmos são cansativos de se preparar, então me dei ao luxo de não incluir em todos os procedimentos, funções e métodos do sistema. Porém nada impede que você, que já pesquisou o assunto e agora “saca” muito de programação, implemente ao seu bel prazer, corrigindo erros que porventura se apresentem.
Então chega de papo e mãos à obra...
A Consulta CVenda
Deveremos criar uma consulta que será utilizada por um método da classe Venda. O nome da consulta será CVenda e seu código SQL será o seguinte:
SELECT Venda.codVenda, Venda.codCliente, Venda.dataVenda, DetalheVenda.codProduto,
DetalheVenda.qtdProduto, Produto.descricao, Produto.unidade, Produto.valorUnitario,
(Produto.valorUnitario*DetalheVenda.qtdProduto) AS SubTotal
FROM Venda LEFT JOIN
(Produto RIGHT JOIN DetalheVenda ON Produto.codProduto=DetalheVenda.codProduto)
ON Venda.codVenda=DetalheVenda.codVenda;
A Classe Venda
O objetivo desta classe, conforme dito anteriormente, é oferecer funcionalidades de inclusão, consulta, atualização e exclusão dos objetos do tipo venda.
Este é o código da classe, com todos os seus atributos e métodos, que se utilizam de funções próprias, bem como dos métodos dos objetos da classe ConexaoBD e da classe Cliente.
Código da classe:
Option Compare Database Option Explicit 'Atributos da Classe 'Atributo de backup e atributo identificador da Classe 'PK - Código que identifica a venda. Private bkpCodVenda As Variant Private lngCodVenda As Variant 'FK - Código que indica o cliente relativo à venda. Private lngCodCliente As Variant 'Objeto da classe de Descrição de Tipo Private objetoCliente As New clsCliente 'Data de realização da venda. Private dtmDataVenda As Variant 'Métodos Get, Set e Let da Classe Property Get codVenda() As Variant codVenda = lngCodVenda End Property Property Let codVenda(argCodVenda As Variant) lngCodVenda = argCodVenda If IsEmpty(bkpCodVenda) Then bkpCodVenda = lngCodVenda End If End Property Property Get codCliente() As Variant codCliente = lngCodCliente End Property Property Let codCliente(argCodCliente As Variant) lngCodCliente = argCodCliente End Property Property Get dataVenda() As Variant dataVenda = dtmDataVenda End Property Property Let dataVenda(argDataVenda As Variant) dtmDataVenda = argDataVenda End Property Property Get objCliente() As Variant If Not IsEmpty(codCliente) Then If objetoCliente.obter(codCliente) Then Set objCliente = objetoCliente End If End If End Property 'Método Existe [Com conhecimento de SQL] 'Verifica a existência do objeto Venda na tabela 'correspondente no Banco de Dados Function existe(argCodVenda As Variant) As Boolean On Error GoTo Err_existe Dim objCon As New aclConexaoBD Dim rstExiste As Recordset Dim strSql As String existe = False strSql = "Select * " & _ "From Venda " & _ "Where codVenda = " & objCon.valorSql(argCodVenda) Set rstExiste = objCon.consulta(strSql) If rstExiste.RecordCount > 0 Then existe = True End If 'Fecha o Recordset existe rstExiste.close Exit_existe: Set rstExiste = Nothing Exit Function Err_existe: existe = False GoTo Exit_existe End Function 'Método Incluir [Com conhecimento de SQL] 'Inclui um novo objeto na tabela correspondente 'dentro do Banco de dados Function incluir() As Boolean On Error GoTo Err_incluir Dim objCon As New aclConexaoBD Dim strSql As String strSql = "Insert Into " & _ "Venda(codVenda,codCliente,dataVenda) " & _ "Values(" & objCon.valorSql(codVenda) & "," & _ objCon.valorSql(codCliente) & "," & _ objCon.valorSql(dataVenda) & ")" incluir = (objCon.executa(strSql) > 0) If incluir Then 'Atualiza os campos de backup bkpCodVenda = codVenda End If Exit_incluir: Exit Function Err_incluir: incluir = False GoTo Exit_incluir End Function 'Método Excluir [Com conhecimento de SQL] 'Exclui o objeto atual na tabela correspondente 'dentro do Banco de dados Function excluir() As Boolean On Error GoTo Err_excluir Dim objCon As New aclConexaoBD Dim strSql As String strSql = "Delete From Venda " & _ "Where codVenda = " & objCon.valorSql(codVenda) excluir = (objCon.executa(strSql) > 0) Exit_excluir: Exit Function Err_excluir: excluir = False GoTo Exit_excluir End Function 'Método Obter [Com conhecimento de SQL] 'Recupera o objeto Venda através dos argumentos informados Function obter(argCodVenda As Variant) As Boolean On Error GoTo Err_obter Dim objCon As New aclConexaoBD Dim rstObter As Recordset Dim strSql As String strSql = "Select * " & _ "From Venda " & _ "Where codVenda = " & objCon.valorSql(argCodVenda) Set rstObter = objCon.consulta(strSql) If rstObter.RecordCount = 0 Then obter = False Exit Function End If 'Atualiza os campos de backup 'e os identificadores codVenda = argCodVenda bkpCodVenda = argCodVenda 'Atualiza os campos restantes codCliente = rstObter.Fields("codCliente") dataVenda = rstObter.Fields("dataVenda") obter = True 'Fecha o Recordset obter rstObter.close Exit_obter: Set rstObter = Nothing Exit Function Err_obter: obter = False GoTo Exit_obter End Function 'Método Salvar [Com conhecimento de SQL] 'Salva o objeto atual na tabela correspondente 'dentro do Banco de dados Function salvar() As Boolean On Error GoTo Err_salvar Dim objCon As New aclConexaoBD Dim strSql As String If existe(bkpCodVenda) Then strSql = "Update Venda " & _ "Set codVenda = " & objCon.valorSql(codVenda) & _ ", codCliente = " & objCon.valorSql(codCliente) & _ ", dataVenda = " & objCon.valorSql(dataVenda) & _ " Where codVenda = " & objCon.valorSql(bkpCodVenda) salvar = (objCon.executa(strSql) > 0) Else salvar = incluir End If If salvar Then 'Atualiza as variáveis de backup 'com o novo valor da chave bkpCodVenda = codVenda End If Exit_salvar: Exit Function Err_salvar: salvar = False GoTo Exit_salvar End Function 'Método getValorTotal 'Calcula a valor total da venda atual e 'devolve o valor como resultado. Function getValorTotal() As Currency getValorTotal = Nz(DSum("SubTotal", "CVenda", _ "codVenda=" & Nz(lngCodVenda, -1))) End Function 'Fim da classe...
A Classe DetalheVenda
O objetivo desta classe, conforme dito anteriormente, é oferecer funcionalidades de consulta e atualização dos objetos de ligação entre a venda e os produtos componentes.
Este é o código da classe, com todos os seus atributos e métodos, que se utilizam de funções próprias, bem como dos métodos dos objetos da classe ConexaoBD e da classe Produto.
Código da classe:
Option Compare Database Option Explicit 'Atributos da Classe 'Atributo de backup e atributo identificador da Classe 'PK - FK - Código que indica o produto relacionado. Private bkpCodProduto As Variant Private lngCodProduto As Variant 'Atributo de backup e atributo identificador da Classe 'PK - FK - Código que indica a venda relacionada. Private bkpCodVenda As Variant Private lngCodVenda As Variant 'Quantidade do poduto relacionado. Private dblQtdProduto As Variant 'Métodos Get, Set e Let da Classe Property Get codProduto() As Variant codProduto = lngCodProduto End Property Property Let codProduto(argCodProduto As Variant) lngCodProduto = argCodProduto If IsEmpty(bkpCodProduto) Then bkpCodProduto = lngCodProduto End If End Property Property Get codVenda() As Variant codVenda = lngCodVenda End Property Property Let codVenda(argCodVenda As Variant) lngCodVenda = argCodVenda If IsEmpty(bkpCodVenda) Then bkpCodVenda = lngCodVenda End If End Property Property Get qtdProduto() As Variant qtdProduto = dblQtdProduto End Property Property Let qtdProduto(argQtdProduto As Variant) dblQtdProduto = argQtdProduto End Property 'Método Existe [Com conhecimento de SQL] 'Verifica a existência do objeto DetalheVenda na 'tabela correspondente no Banco de Dados Function existe(argCodProduto As Variant, _ argCodVenda As Variant) As Boolean On Error GoTo Err_existe Dim objCon As New aclConexaoBD Dim rstExiste As Recordset Dim strSql As String existe = False strSql = "Select * " & _ "From DetalheVenda " & _ "Where codProduto = " & objCon.valorSql(argCodProduto) _ And codVenda = " & objCon.valorSql(argCodVenda)" Set rstExiste = objCon.consulta(strSql) If rstExiste.RecordCount > 0 Then existe = True End If 'Fecha o Recordset existe rstExiste.close Exit_existe: Set rstExiste = Nothing Exit Function Err_existe: existe = False GoTo Exit_existe End Function 'Método Incluir [Com conhecimento de SQL] 'Inclui um novo objeto na tabela correspondente 'dentro do Banco de dados Function incluir() As Boolean On Error GoTo Err_incluir Dim objCon As New aclConexaoBD Dim strSql As String strSql = "Insert Into " & _ "DetalheVenda(codProduto,codVenda,qtdProduto) " & _ "Values(" & objCon.valorSql(codProduto) & "," & _ objCon.valorSql(codVenda) & "," & _ objCon.valorSql(qtdProduto) & ")" incluir = (objCon.executa(strSql) > 0) If incluir Then 'Atualiza os campos de backup bkpCodProduto = codProduto bkpCodVenda = codVenda End If Exit_incluir: Exit Function Err_incluir: incluir = False GoTo Exit_incluir End Function 'Método Excluir [Com conhecimento de SQL] 'Exclui o objeto atual na tabela correspondente 'dentro do Banco de dados Function excluir() As Boolean On Error GoTo Err_excluir Dim objCon As New aclConexaoBD Dim strSql As String strSql = "Delete From DetalheVenda " & _ "Where codProduto = " & objCon.valorSql(codProduto) & _ " and codVenda = " & objCon.valorSql(codVenda) excluir = (objCon.executa(strSql) > 0) Exit_excluir: Exit Function Err_excluir: excluir = False GoTo Exit_excluir End Function 'Método Obter [Com conhecimento de SQL] 'Recupera o objeto DetalheVenda através dos argumentos informados Function obter(argCodProduto As Variant, _ argCodVenda As Variant) As Boolean On Error GoTo Err_obter Dim objCon As New aclConexaoBD Dim rstObter As Recordset Dim strSql As String strSql = "Select * " & _ "From DetalheVenda " & _ "Where codProduto = " & objCon.valorSql(argCodProduto) & _ " And codVenda = " & objCon.valorSql(argCodVenda) Set rstObter = objCon.consulta(strSql) If rstObter.RecordCount = 0 Then obter = False Exit Function End If 'Atualiza os campos de backup 'e os identificadores codProduto = argCodProduto bkpCodProduto = argCodProduto codVenda = argCodVenda bkpCodVenda = argCodVenda 'Atualiza os campos restantes qtdProduto = rstObter.Fields("qtdProduto") obter = True 'Fecha o Recordset obter rstObter.close Exit_obter: Set rstObter = Nothing Exit Function Err_obter: obter = False GoTo Exit_obter End Function 'Método Salvar [Com conhecimento de SQL] 'Salva o objeto atual na tabela correspondente 'dentro do Banco de dados Function salvar() As Boolean On Error GoTo Err_salvar Dim objCon As New aclConexaoBD Dim strSql As String If existe(bkpCodProduto, bkpCodVenda) Then strSql = "Update DetalheVenda " & _ "Set codProduto = " & objCon.valorSql(codProduto) & _ ", codVenda = " & objCon.valorSql(codVenda) & _ ", qtdProduto = " & objCon.valorSql(qtdProduto) & _ " Where codProduto = " & objCon.valorSql(bkpCodProduto) & _ " and codVenda = " & objCon.valorSql(bkpCodVenda) salvar = (objCon.executa(strSql) > 0) Else salvar = incluir End If If salvar Then 'Atualiza as variáveis de backup 'com o novo valor da chave bkpCodProduto = codProduto bkpCodVenda = codVenda End If Exit_salvar: Exit Function Err_salvar: salvar = False GoTo Exit_salvar End Function 'Método getSubTotal 'Calcula o subtotal do produto vendido para a 'venda atual e devolve o valor como resultado Function getSubTotal() As Currency Dim objProduto As New clsProduto If Not IsNull(lngCodProduto) And Not IsNull(dblQtdProduto) Then If objProduto.obter(lngCodProduto) Then getSubTotal = objProduto.valorUnitario * dblQtdProduto End If End If End Function 'Método getListaProduto(argCodVenda As Long) 'Recebe como parâmetro um código de uma venda e devolve 'um conjunto de registros contendo os códigos dos produtos, 'além de outros dados, relativos à venda informada. Function getListaProduto(argCodVenda As Long) As Recordset Dim strSql As String Dim objCon As New aclConexaoBD strSql = "SELECT Venda.codVenda, Venda.codCliente, Venda.dataVenda, " & _ "DetalheVenda.codProduto, DetalheVenda.qtdProduto, " & _ "Produto.descricao, Produto.unidade, Produto.valorUnitario, " & _ "(Produto.valorUnitario * DetalheVenda.qtdProduto) AS SubTotal " & _ "FROM Venda LEFT JOIN (Produto RIGHT JOIN DetalheVenda ON " & _ "Produto.codProduto=DetalheVenda.codProduto) " & _ "ON Venda.codVenda=DetalheVenda.codVenda " & _ "Where Venda.codVenda=" & argCodVenda Set getListaProduto = objCon.consulta(strSql) End Function 'Fim da classe...
Mais Funções Auxiliares
Para exibir os itens vendidos na tela, no formato de um cupom fiscal, necessitaremos de uma função que busque os últimos itens registrados na venda para que possamos apresentá-los ao usuário.
Assim sendo deveremos incluir mais uma função no módulo Funcoes criado anteriormente, que será chamada de listaProdutos() e receberá como parâmetros o código da venda e o número de itens, a partir do último, que devem ser retornados.
Function listaProdutos(argCodVenda As Long, _ Optional ultimos As Integer = 0) As String Dim rstLista As Recordset Dim objDetalheVenda As New clsDetalheVenda Dim contador Set rstLista = objDetalheVenda.getListaProduto(argCodVenda) If Not rstLista.EOF Then rstLista.MoveLast Else listaProdutos = "" Exit Function End If While Not rstLista.BOF And (contador < ultimos Or ultimos = 0) listaProdutos = " " & FormatNumber(rstLista("qtdProduto"), 3) & _ " x " & FormatCurrency(rstLista("valorUnitario")) & _ " " & FormatCurrency(rstLista("SubTotal")) & _ vbCrLf & listaProdutos listaProdutos = Format(rstLista("codProduto"), "000000") & " " & _ rstLista("unidade") & " " & rstLista("descricao") & _ vbCrLf & listaProdutos rstLista.MovePrevious contador = contador + 1 Wend End Function
Repare que a função busca os itens começando do final do conjunto de registros, inclusive montando as linhas também do final para o início, até que seja completada a quantidade de itens solicitada, ou todos os itens caso a opção não seja informada.
Ponto Importante
Desta vez, relembrando e aprimorando os conceitos da orientação a objetos, vamos destacar o ponto a seguir.
1. Atributo do tipo objeto: Nos atributos da classe Venda podemos encontrar um que faz referência à classe Cliente, ou seja, o atributo é um objeto da classe Cliente. Por que isto? Apenas adiantando um pouco o assunto do próximo artigo criaremos um modelo de cupom fiscal (bem simples, lógico!!!) no qual esta opção será muito útil. Ao invés de criarmos uma variável de objeto da classe Cliente e a instanciarmos bastará utilizarmos o atributo incluído na classe Venda depois que a mesma for obtida através do código da venda. Neste caso o objeto cliente será instanciado dentro da classe Venda automaticamente assim que o atributo for referenciado. Com isso será possível também acessar todos os atributos e métodos do objeto cliente. Veja os trechos de código, primeiro o método de acesso ao atributo e depois a montagem do cupom fiscal:
Declaração da variável e método de acesso ao atributo:
'Objeto da classe de Descrição de Tipo Private objetoCliente As New clsCliente ... Property Get objCliente() As Variant If Not IsEmpty(codCliente) Then If objetoCliente.obter(codCliente) Then Set objCliente = objetoCliente End If End If End Property ...
Montagem do Cupom Fiscal:
cupomVenda = cupomVenda & "CPF: " & objVenda.objCliente.cpf & vbCrLf cupomVenda = cupomVenda & "Nome: " & objVenda.objCliente.nomeCliente & vbCrLf
A Interface Gráfica
Desta vez para que possamos visualizar e manipular os dados das vendas resolvi criar um interessante sistema de PDV para ser a nossa interface gráfica, já que há tanta curiosidade acerca dos mesmos.
A tela contará com exibição dos itens registrados em formato de cupom, além do cálculo e registro de produtos utilizando o esquema de digitação do código multiplicado pela quantidade em um único campo. Também faremos a verificação da descrição do produto a partir do código e o cálculo de troco a partir do valor pago, além de outras pequenas funções.
Como não poderia faltar faremos o controle de estoque, tanto baixando itens vendidos como subindo produtos devolvidos, além de verificação de estoque baixo.
A seguir serão definidas as características para o PDV. Assim como nos anteriores as especificações de design também são apenas sugestões, porém as informações de origem de dados, bloqueio de campos e formatos de dados são obrigatórias, sob pena de, novamente, não obter a funcionalidade desejada. Como de praxe as especificações estarão marcadas com um asterisco vermelho ao lado.
No formulário teremos um rótulo que exibirá os produtos vendidos, alguns campos de texto para digitação das informações e os botões de comando para criar e cancelar vendas, excluir produtos e fechar a tela do PDV.
Obs: Somente as propriedades que foram alteradas para um valor diferente do padrão serão apresentadas. Para as demais utilize o padrão do sistema, ou o valor que desejar, desde que não comprometa a funcionalidade do sistema.
Formulário
Nome: FVenda Largura: 22cm Altura: Cabeçalho (1,6cm) / Detalhe (15cm) Cor do fundo: Cabeçalho (Vermelho) / Detalhe (Azul escuro) Legenda: Sistema de Vendas Estilo da borda: Fino Seletores de registro: Não Botões de navegação: Não Linhas divisórias: Não Barras de rolagem: Nenhuma Caixa de controle: Não Botão Fechar: Não Botões Min Max: Nenhum Popup: Sim Janela Restrita: Não
Controle Label
Nome: lblProdutos * Largura: 9,7cm * Altura: 10,4cm *
Controles Textbox
Nome: txtCodigoVenda * Formato: 000000 Ativado: Não * Bloqueado: Sim * Cor do fundo: Cinza Alinhamento: Centro Nome: txtData * Formato: dd/mm/yyyy * Cor do fundo: Branco Máscara de entrada: 00/00/00;;_ Alinhamento: Centro Nome: txtCodCliente * Formato: 000 Cor do fundo: Branco Alinhamento: Centro Nome: txtNomeCliente * Cor do fundo: Cinza Alinhamento: Esquerda Ativado: Não * Bloqueado: Sim * Nome: txtProdutoQtd * Cor do fundo: Amarelo Alinhamento: Esquerda Tamanho da Fonte: 28 Nome: txtDescricao * Cor do fundo: Cinza Alinhamento: Esquerda Tamanho da Fonte: 14 Ativado: Não * Bloqueado: Sim * Nome: txtUnidade * Cor do fundo: Cinza Alinhamento: Centro Tamanho da Fonte: 14 Ativado: Não * Bloqueado: Sim * Nome: txtQtd * Cor do fundo: Cinza Formato: Padrão Casas decimais: 3 Alinhamento: Centro Tamanho da Fonte: 14 Ativado: Não * Bloqueado: Sim * Nome: txtValorUnt * Cor do fundo: Cinza Formato: Unidade Monetária Alinhamento: Direita Tamanho da Fonte: 14 Ativado: Não * Bloqueado: Sim * Nome: txtSubTotal * Cor do fundo: Cinza Formato: Unidade Monetária Alinhamento: Direita Tamanho da Fonte: 14 Ativado: Não * Bloqueado: Sim * Nome: txtTotal * Cor do fundo: Laranja Formato: Unidade Monetária Alinhamento: Direita Tamanho da Fonte: 24 Ativado: Não * Bloqueado: Sim * Nome: txtPago * Cor do fundo: Cinza Formato: Unidade Monetária Alinhamento: Direita Tamanho da Fonte: 16 Nome: txtTroco * Cor do fundo: Cinza Formato: Unidade Monetária Alinhamento: Direita Tamanho da Fonte: 16 Ativado: Não * Bloqueado: Sim *
Botões de Comando
Nome: btnExcluirProduto * Legenda: Excluir Produto Cor da Fonte: Vermelho escuro Nome: btnCancelar * Legenda: Cancelar Venda Cor da Fonte: Vermelho escuro Nome: btnNovo * Legenda: Nova Venda Cor da Fonte: Azul escuro Nome: btnFechar * Legenda: Fechar Cor da Fonte: Vermelho escuro
Como sugestão de design os campos deverão estar posicionados conforme demonstrado na figura a seguir, assim como devem ser adicionadas as legendas dos campos, as quais não foram descritas anteriormente:
Códigos do Formulário
Para que nosso formulário seja capaz de manipular os dados dos objetos produto deveremos implementar as funcionalidades necessárias que permitam criar uma nova venda, calcular os valores dos produtos vendidos, registrar as vendas, controlar estoque, devolver produtos e cancelar a venda.
Sendo assim criaremos algumas funções genéricas que sejam úteis para quantos procedimentos delas necessitem, reaplicando o conceito da reutilização de código. Além disso, incluiremos os códigos dos campos que possuem eventos, assim como dos botões de comando.
Todos os códigos a seguir deverão ser colocados no módulo do formulário FVenda.
1. Limpeza dos campos: Para efetuar a limpeza dos campos de preenchimento utilizaremos dois procedimentos genérico cuja função será a de atribuir o valor nulo a todos os campos de texto, deixar o rótulo de exibição do cupom em branco e enviar o foco ao campo txtProdutoQtd. Repare que um procedimento reutiliza o código do outro, já que os dois são independentes e o primeiro é utilizado apenas na descrição do produto e o segundo limpa todos os campos da tela.
Private Sub limpaExibicaoProduto() txtDescricao = Null txtUnidade = Null txtQtd = Null txtValorUnt = Null txtSubtotal = Null End Sub Private Sub limpaCampos() txtCodigoVenda = Null txtData = Null txtCodCliente = Null txtNomeCliente = Null txtProdutoQtd = Null txtTotal = Null txtPago = Null txtTroco = Null Call limpaExibicaoProduto lblProdutos.Caption = "" txtProdutoQtd.SetFocus End Sub
2. Nova venda: Para a criação de uma nova venda em branco simplesmente iremos chamar o procedimento limpaCampos() no evento Ao Clicar do botão btnNovo.
Private Sub btnNovo_Click() Call limpaCampos End Sub
3. Cálculo do troco: Com a finalidade de informar o valor do troco a partir do valor da venda e do valor pago utilizaremos um procedimento que será acionado no evento Após Atualizar do campo txtPago.
Private Sub txtPago_AfterUpdate() If Not IsNull(txtPago) Then txtTroco = CDbl(txtPago) - CDbl(Nz(txtTotal)) Else txtTroco = Null End If End Sub
4. Exibição da lista de produtos registrados: Para exibir a lista de produtos registrados sempre que for incluído ou retirado algum produto utilizaremos um procedimento genérico cuja função será a de buscar o lista utilizando a função listaProdutos(), atualizar a exibição do valor total da venda no campo txtTotal e acionar o procedimento que calcula e exibe o valor do troco.
Sub atualizaLista() Dim objVenda As New clsVenda If Not IsNull(txtCodigoVenda) Then If objVenda.obter(txtCodigoVenda) Then lblProdutos.Caption = listaProdutos(Nz(txtCodigoVenda, -1), 10) txtTotal = objVenda.getValorTotal Call txtPago_AfterUpdate End If End If End Sub
5. Carregamento do formulário: No evento Ao Carregar do formulário implementaremos uma pequena rotina que simplesmente atualizará a lista de produtos e moverá o foco para o campo txtProdutoQtd. A atualização da lista só fará sentido quando abrirmos novamente um formulário já carregado anteriormente (por exemplo, quando abrimos o formulário, mudamos para o modo Design e voltamos para o modo Formulário).
Private Sub Form_Load() Call atualizaLista txtProdutoQtd.SetFocus End Sub
6. Criação da venda: Ao iniciarmos uma venda deveremos gerar o código da mesma. Assim criaremos um procedimento genérico que lança mão da função proximoCodigo() para gerar o código da venda, além de atribuir a data atual caso o usuário não tenha informado alguma data específica.
Sub preencheVenda() If IsNull(txtCodigoVenda) Then txtCodigoVenda = proximoCodigo("codVenda", "Venda") End If If IsNull(txtData) Then txtData = Date End If End Sub
7. Busca do cliente: Para que seja exibido o nome do cliente assim que o código for informado utilizaremos esta rotina no evento Após Atualizar do campo txtCodCliente. Assim será preenchido o campo txtNomeCliente a partir dos dados retornados pelo objeto cliente, ou então um aviso ao usuário informando que o código do cliente é inválido. Ao ser informado um cliente válido a venda também será gerada através de uma chamada ao procedimento preencheVenda().
Private Sub txtCodCliente_AfterUpdate() Dim objCliente As New clsCliente If Not IsNull(txtCodCliente) Then If objCliente.obter(CLng(txtCodCliente)) Then Call preencheVenda txtNomeCliente = objCliente.nomeCliente Else txtNomeCliente = Null txtCodCliente = Null MsgBox "Código do cliente inválido!", _ vbExclamation, "Erro!" End If End If End Sub
8. Busca do produto em tempo real: O procedimento a seguir permitirá que seja exibida a descrição e a unidade do produto no exato momento em que o código for digitado. Ele será incluído no evento Ao Alterar do campo txtProdutoQtd. Caso o código não corresponda a nenhum produto será exibida uma mensagem no campo txtDescricao informando que o código não está cadastrado. Veja que ele somente será executado completamente caso o valor digitado possa ser interpretado como valor numérico, ou seja, um valor compatível com o código do produto.
Private Sub txtProdutoQtd_Change() Dim objProduto As New clsProduto If txtProdutoQtd.Text <> "" Then If InStr(txtProdutoQtd.Text, "*") = 0 Then If IsNumeric(txtProdutoQtd.Text) Then If objProduto.obter(CLng(txtProdutoQtd.Text)) Then txtDescricao = objProduto.descricao txtUnidade = objProduto.unidade Else txtDescricao = "Código não cadastrado..." txtUnidade = Null End If txtQtd = Null txtValorUnt = Null txtSubtotal = Null End If End If Else txtDescricao = Null txtUnidade = Null End If End Sub
9. Exibição de produto: Para exibirmos todos os dados do produto a ser registrado em uma venda criaremos um procedimento genérico que recebe como argumento um objeto detalhe de venda cujas propriedades e métodos fornecerão as informações para o preenchimento dos campos. Repare que o valor do subtotal é calculado pelo método getSubTotal() do objeto.
Sub exibeProduto(argDetalhe As clsDetalheVenda) Dim objProduto As New clsProduto If objProduto.obter(argDetalhe.codProduto) Then txtDescricao = objProduto.descricao txtUnidade = objProduto.unidade txtQtd = argDetalhe.qtdProduto txtValorUnt = objProduto.valorUnitario txtSubtotal = argDetalhe.getSubTotal End If End Sub
10. Registro do produto: Cada produto constante da venda deverá ser registrado antes do fechamento da venda. Para isto deveremos realizar várias verificações, utilizando vários objetos neste procedimento que será incluído no evento Antes de Atualizar do campo txtProdutoQtd para que seja capaz de cancelar o registro caso alguma informação não esteja correta. Primeiro será verificada a validade do código do produto e a correta informação da quantidade, caso seja incluída, atribuindo uma unidade em caso contrário. Em seguida será checada a validade do código da venda e também da data, além do código do cliente, caso seja informado (uma venda sem cliente será considerada venda ao consumidor). Para não ocorrerem conflitos o código da venda será sempre gerado automaticamente, não sendo permitida sua alteração posterior. O próximo passo será verificar se a quantidade solicitada existe em estoque. Caso não possa ser atendida integralmente o registro é cancelado. Existindo a quantidade o produto é registrado, assim como qualquer alteração nos dados da venda. Por último a exibição dos dados é atualizada e o estoque atualizado, informando o usuário se o estoque ficar abaixo do mínimo. Caso ocorram erros durante o processo o esquema de tratamento de erros avisa o usuário sobre qual passo gerou o problema.
Private Sub txtProdutoQtd_BeforeUpdate(Cancel As Integer) On Error GoTo Err_txtProdutoQtd_AfterUpdate Dim codigoProduto As Long Dim qtdProduto As Double Dim posicao As Integer Dim status As Integer Dim objVenda As New clsVenda Dim objDetalhe As New clsDetalheVenda Dim objProduto As New clsProduto status = 0 If Not IsNull(txtProdutoQtd) Then Call preencheVenda posicao = InStr(txtProdutoQtd, "*") If posicao > 0 Then status = 1 codigoProduto = CLng(Left(txtProdutoQtd, posicao - 1)) status = 2 qtdProduto = CDbl(Right(txtProdutoQtd, Len(txtProdutoQtd) - posicao)) Else status = 3 codigoProduto = CLng(txtProdutoQtd) qtdProduto = 1 End If status = 4 objVenda.codVenda = CLng(txtCodigoVenda) status = 5 objVenda.dataVenda = CDate(txtData) status = 6 If Not IsNull(txtCodCliente) Then objVenda.codCliente = CLng(txtCodCliente) End If If objProduto.obter(codigoProduto) Then If objProduto.qtdEstoque < qtdProduto Then MsgBox "O estoque existente não é suficiente!" & vbCrLf & vbLf & _ "Estoque atual: " & FormatNumber(objProduto.qtdEstoque, 3) & _ " " & objProduto.unidade, _ vbExclamation, "Estoque Baixo" Cancel = True Exit Sub End If End If status = 7 If objVenda.salvar Then objDetalhe.codVenda = objVenda.codVenda objDetalhe.codProduto = codigoProduto objDetalhe.qtdProduto = qtdProduto status = 8 If objDetalhe.salvar Then Call exibeProduto(objDetalhe) Call atualizaLista If objProduto.obter(codigoProduto) Then status = 9 If objProduto.baixarEstoque(qtdProduto) Then If objProduto.estoqueBaixo Then MsgBox "O estoque ficou abaixo da quantidade mínima!" & _ vbCrLf & vbLf & _ "Estoque atual: " & FormatNumber(objProduto.qtdEstoque, 3) & _ " " & objProduto.unidade, vbExclamation, "Estoque Baixo" End If
Else MsgBox "Ocorreu um erro na atualização do estoque.", _ vbExclamation, "Erro!" End If End If Else MsgBox "Ocorreu um erro ao incluir o produto.", _ vbExclamation, "Erro!" Cancel = True End If Else MsgBox "Ocorreu um erro ao incluir a venda.", _ vbExclamation, "Erro!" Cancel = True End If End If Exit_txtProdutoQtd_AfterUpdate: Exit Sub Err_txtProdutoQtd_AfterUpdate: Select Case status Case 1, 3 MsgBox "Código do produto inválido!", vbExclamation, "Erro!" Case 2 MsgBox "Quantidade do produto inválida!", vbExclamation, "Erro!" Case 5 MsgBox "Data inválida!", vbExclamation, "Erro!" Case 6 MsgBox "Código do cliente inválido!", vbExclamation, "Erro!" Case 7 MsgBox "Ocorreu um erro ao incluir a venda!", vbExclamation, "Erro!" Case 8 MsgBox "Ocorreu um erro ao incluir o produto!", vbExclamation, "Erro!" Case 9 MsgBox "Ocorreu um erro ao atualizar o estoque!", vbExclamation, "Erro!" Case Else MsgBox "Ocorreu um erro. O sistema informou a seguinte mensagem:" & _ vbCrLf & vbLf & Err.Description, vbExclamation, "Erro!" End Select Cancel = True Resume Exit_txtProdutoQtd_AfterUpdate End Sub
Obs: Apenas para esclarecimento sobre o tratamento de erros lembre-se que este não é um sistema real. Sendo assim os códigos foram colocados de qualquer maneira para atingir o objetivo do curso: aprender a utilizar os objetos. Da maneira que foram implementados os registros um erro no meio do processo impediria que o final do registro de uma venda ou de um produto ocorresse, deixando os dados corrompidos. Em um sistema real deveriam ser utilizadas transações para garantir que todos os processos ocorressem integralmente, do início ao fim, ou então que todas as etapas fossem canceladas e os dados retornassem ao estado original. Não poderíamos, por exemplo, ter uma venda registrada sem que o estoque fosse atualizado. Mas isto é outra história...
11. Atualização das informações: Após o registro de um produto as informações exibidas devem ser atualizadas e o foco retornar para o campo de digitação de produto e quantidade, a fim de que ele esteja pronto para efetuar novo registro. Para executar esta tarefa criaremos um procedimento no evento Após Atualizar do campo txtProdutoQtd.
Private Sub txtProdutoQtd_AfterUpdate() btnNovo.SetFocus txtProdutoQtd.SetFocus txtProdutoQtd = Null Call preencheVenda Call atualizaLista End Sub
12. Exclusão de produto da venda: Caso tenhamos que excluir um único produto da venda, antes que ela seja fechada, deveremos desfazer os passos executados no registro do mesmo. Para isto devemos criar um procedimento no evento Ao Clicar do botão btnExcluirProduto. Ele será encarregado de solicitar ao usuário o código do produto que deverá ser excluído, checando sua validade, atualizando o estoque e finalmente excluindo o produto, sendo que os dados de exibição devem ser novamente atualizados através de uma chamada ao procedimento genérico atualizaLista(). Repare que não excluiremos o produto em si, mas apenas o seu registro na venda.
Private Sub btnExcluirProduto_Click() Dim objDetalheVenda As New clsDetalheVenda Dim objProduto As New clsProduto Dim codigoProduto As String If Not IsNull(txtCodigoVenda) Then codigoProduto = InputBox("Informe o código do produto a ser excluído:", _ "Exclusão de Produto") Else Exit Sub End If If codigoProduto <> "" Then If IsNumeric(codigoProduto) Then If objDetalheVenda.obter(CLng(codigoProduto), CLng(txtCodigoVenda)) Then If objProduto.obter(CLng(codigoProduto)) Then If objProduto.subirEstoque(objDetalheVenda.qtdProduto) Then If objDetalheVenda.excluir Then MsgBox "O produto foi excluído com sucesso!", _ vbInformation, "Exclusão de Produto" Call atualizaLista Else MsgBox "Ocorreu um erro durante a exclusão do produto!", _ vbExclamation, "Exclusão de Produto" End If End If End If End If Else MsgBox "Código de produto inválido!", _ vbExclamation, "Exclusão de Produto" End If End If End Sub
Obs: Veja que não incluí tratamento de erro neste procedimento. Também não incluí e nem incluirei em vários outros. Esta tarefa deverá ser executada no evento Ao Praticar do objeto LeitorAplicado.
13. Cancelamento da venda: Assim como podemos excluir um produto registrado também podemos cancelar uma venda inteira. Para executar esta tarefa deveremos desfazer todos os passos executados para registrar todos os produtos até o momento. O procedimento responsável será incluído no evento Ao Clicar do botão btnCancelar. O cancelamento deverá contemplar a busca do conjunto de registros dos produtos da venda, através do método getListaProduto() do objeto detalhe de venda, que serão verificados um a um para retorno ao estoque. Em seguida a venda será excluída, ocasionando a exclusão dos registros associados na tabela DetalheVenda pelo mecanismo do relacionamento em cascata. Após a exclusão os campos serão limpos para iniciar nova venda.
Private Sub btnCancelar_Click() Dim objVenda As New clsVenda Dim objDetalheVenda As New clsDetalheVenda Dim objProduto As New clsProduto Dim rstLista As Recordset If objVenda.obter(Nz(txtCodigoVenda, -1)) Then If MsgBox("Confirma o cancelamento da venda?", _ vbQuestion + vbYesNo, "Cancelar Venda") = vbYes Then Set rstLista = objDetalheVenda.getListaProduto(txtCodigoVenda) While Not rstLista.EOF Call objProduto.obter(rstLista("codProduto")) Call objProduto.subirEstoque(rstLista("qtdProduto")) rstLista.MoveNext Wend If objVenda.excluir Then MsgBox "A venda foi cancelada com sucesso.", _ vbInformation, "Cancelar Venda" Call limpaCampos Else MsgBox "Ocorreu um erro durante o cancelamento.", _ vbExclamation, "Cancelar Venda" End If End If End If End Sub
14. Fechamento do PDV: Para encerrarmos as atividades no PDV simplesmente iremos incluir um procedimento que fecha o formulário no evento Ao Clicar do botão btnFechar.
Private Sub btnFechar_Click() DoCmd.close End Sub
Sistema de Exemplo
Novamente será disponibilizado o link para download do sistema de vendas, já atualizado com todos os recursos do estado de desenvolvimento em que se encontrar o projeto.
Continua a recomendação de consultar o exemplo apenas para tirar dúvidas e verificar como foi feito, além de ver o sistema em funcionamento. Treinem bastante, digitem, testem, façam o cérebro pedir água. Esta é a única maneira de realmente assimilar o conhecimento.
Segue o link para download:
Caso ainda não esteja com uma tela principal abra o formulário FVenda diretamente na janela Banco de Dados (Access 2003-) ou no Painel de Navegação (Access 2007+).
Conclusão
Depois desta exaustiva etapa foi possível percebermos o quanto os objetos devem interagir para chegar ao fim de uma tarefa. Funções, procedimentos, eventos, métodos e atributos trabalham em conjunto para produzir resultados que atendam as necessidades do usuário.
Este artigo foi dedicado a demonstrar de maneira maçante a criação, utilização e troca de informações entre objetos em meio aos códigos do restante do sistema, por isso mesmo foi bem mais longo que os demais.
Além de toda a programação em si foi possível trabalharmos apresentando um sistema interessante, útil e muito comum no dia a dia, do qual muitos tinham a curiosidade de conhecer e compreender a forma de implementação: o PDV. Mas nosso sistema de vendas ultramoderno, com tecnologia orientada a objetos, não estaria completo sem ele, não é mesmo?
Aqui praticamente encerramos a parte mais trabalhosa. De agora em diante veremos apenas a finalização do sistema, algo mais estético do que funcional, além de um pouco mais teoria nos artigos seguintes, com a apresentação do Genesis, a ferramenta case para VBA que facilitará a sua vida, e a abordagem sobre utilização de objetos no MS-Access para aprimorar seus projetos.
Conto com a presença de todos na próxima etapa, pois esta é a razão de ser desta série de artigos.
Até lá...
Artigos Relacionados
Utilizando Classe no Access - Introdução
Utilizando Classe no Access - Parte 1 - Orientação a Objetos
Utilizando Classe no Access - Parte 2 - Programação OO no Access/Vba
Utilizando Classe no Access - Parte 4 - As Classes Auxiliares
Utilizando Classe no Access - Parte 5 - A Classe Cliente
Utilizando Classe no Access - Parte 6 - A Classe Produto
Utilizando Classe no Access - Parte 7 - As Classes Venda e Detalhe de Venda
Utilizando Classe no Access - Parte 8 - Finalização do Sistema
Utilizando Classe no Access - Parte 9 - Genesis: A Ferramenta Case
Utilizando Classe no Access - Parte 10 - Conclusão
Como estudar com o Pesquisador de Objetos
14 comentários MUITO BOM 11/08/2022 23:38:19 MUITO BOM Patrick 30/06/2022 23:46:04 Boa noite. Gostaria de dizer que este site é 10! Sempre nos ajudando a assimilar os vários usos do access e facilitando a vida de iniciantes. Estive analisando o conteúdo do tutorial e fiquei impressionado, porém, não estou conseguindo trabalhar encima do código do FVenda. Nos meus testes não consigo inserir novos campos e salvar os mesmos, gravando seus dados na tabela. Exemplo: Gostaria de criar um campo de seleção com códigos de vendedor que eu mesmo determinaria, até consegui criar a combobox porém não consigo determinar a variante que faça os dados selecionados serem gravados na tabela Venda. Sempre fica em branco. Como poderia proceder para incluir um novo campo de seleção e gravar os dados selecionados no momento em que estou gravando os da venda? Grato. Silveriovitaliano 28/03/2022 18:58:25 Baixei e fiz alguns testes. Posso garantir que ficou muito bom. As mudanças que se assim acharem necessários...serão meramente detalhes da OBRA FEITA. Guidemar 17/03/2022 10:42:32 Bom dia Plinio, parabéns ótimo sistema, queria ver se você pode me ajudar quero montar um sistema para lancheria, quero que o sistema desconte do estoque os acompanhamentos do lanche, exemplo vendo um bauru, além de descontar o pão do estoque, desconte também o presunto queijo e demais ingredientes, desde já agradeço. Luciclaudio 09/01/2022 04:21:48 Galera tenho uma duvida, quando tento converter um texto com mais de 11 dígitos no access tenho o seguinte erro CODIGO convertido CODIGO_normal 1,38055651547E+11 138055651547 8,71651000905E+11 871651000905 o que posso fazer para torna-lo numero? johann 28/05/2021 19:55:08 Boa noite plinio, meu nome é johann e eu gostei do seu resultado sobre o pdv mostrado no seu site e dei uma melhorada colocando senhas e logins, permissoes de usuarios e entre outros pra transformar em um verdadeiro sistema mais tenho uma duvida, se vc poder me ajudar eu serei muito grato. Eu queria colocar ao inves de codproduto*qtdproduto na tela de venda codigoDeBarra*qdeProduto ou se podesse digitar codigo do produto ou codigo de barra x a quantidade do produto e ele funcionasse. Porque aonde eu trabalho tanto eu digito o codigo do produto como o codigo de barra. Obrigado e fico no seu aguardo. Gil Kléber 16/04/2021 16:55:58 Olá Plínio! Parabens pela atitude de deixar seu sistema disponivel para download! Me ajudou muuito!!! No entanto, há algo (que para vc deve sem bem simples de fazer) que não consigo fazer: Coloquei uma caixa de combinação no formulário FVenda, para selecionar 2 vendedores. Consegui fazer com que aparecesse no cupom fiscal sem problemas, mas não consigo salvar na tabela Vendas. (preciso salvar esse campo e o campo TOTAL, pq faço calculos de comissão no final do mês). Pode me ajudar? Mais uma coisinha... não consigo adicionar 2 produtos do mesmo código e isso será recorrente aqui na minha lojinha... como posso fazer?? Grande abraço e mais uma vez, obrigado! Enoque Nunes 27/09/2020 13:52:30 E ai Plínio, beleza? Cara, me diz uma coisa, o problema todo estava sendo causado apenas pelo sinal de "=" na linha: If dblQtdEstoque - Abs(argQtd) <= 0 Then ? Foi a única coisa que alterei no exemplo anterior para poder funcionar normalmente, depois disso funcionou sem problemas, até arrisquei colocar de volta o sinal de "=" para ver o que ia acontecer e para minha surpresa funcionou sem erros. Não sei qual foi a jogada, mas funcionou. Abraços amigão e até a próxima. Enoque Nunes 24/09/2020 08:57:54 Valeu Grande Plínio. Testei e está tudo certinho dessa vez. Abraços e fica com Deus. Plinio Mabesi 22/09/2020 20:02:04 O erro está no método baixarEstoque() da classe Produto. O código deve ser alterado para este: Function baixarEstoque(argQtd As Double) As Boolean If dblQtdEstoque - Abs(argQtd) < 0 Then baixarEstoque = False Else dblQtdEstoque = dblQtdEstoque - Abs(argQtd) baixarEstoque = salvar End If End Function Como o método considerava como falso resultados menores ou iguais a zero o estoque nunca reduzia a quantidade para abaixo de 1 unidade. Deixando falso apenas os valores menores que zero tudo corre normalmente. Mas lembrem-sem que isto não quer dizer que não haja outros erros. Agradeço por novas informações. Valeu Enoque... Enoque Nunes 18/09/2020 09:02:47 Cara, como é que você faz uma coisa dessas, vir aqui e não me avisar? rsrsrsrs, iria lhe convidar para comer um delicioso churrasco de tambaqui. Mas, oportunidades acredito que não irão faltar. Abraços amigão e fica na paz do Senhor Jesus. Até mais. Plinio Mabesi 17/09/2020 18:01:55 Olá Grande Enoque... Estive aí na sua terrinha semana passada, mas foi uma passagem rápida. Vou checar o programa e assim que corrigir peço para o Avelino postar a correção. Valeu... Enoque Nunes 15/09/2020 13:25:27 E ai grande mestre Plínio, beleza meu amigo? Cara, você está de parabens mesmo pelo artigo, baixei seu segundo exemplo e pasme, achei fantástico, sempre quis fazer algo semelhante e não sabia nem por onde começar. rsrsrsrs Mas, vamos lá. Estive explorando ontem à noite seu PDV e acredito que encontrei um o erro. Veja só. Cadastrei um produto e dei entrada de 05 peças, fui dando baixa um a um, até ai tudo bem, o problema veio depois que as 05 peças tinham acabado e o PDV continuou aceitando baixa e sem zerar o estoque, ele fica parado sempre com uma peça no estoque e sem abrir mensagem de estoque zero. Assim, lancei várias saidas no PDV com uma única peça de saldo no estoque, eu tinha dado entrada apenas de 05 peças. Dá uma checada ai amigão. Abraços e fica com Deus. Wemerson Bernardo 31/01/2020 13:05:31 Olá, Sei que já contrinbuiu demais com o bd disponibilizado (VendaOO). Confesso que segui suas dicas, estou estudando bastante o Gênesis, e as dicas no site. Mas como não entendia muito de VBA, até assimilar tudo pode ser que demore um pouco, e ainda assim continuo com dúvida. Como vc me orientou, gerei os códigos com o gênesis e não consegui ainda. Primeiro acrescentei o campo codBarra como string, coloquei como chave primária tb, o acrescentei na tab DetalheVenda, fiz as relações e nada. Sem sucesso, tentei apenas mudar as propriedades do campo existente codProduto para texto. (Para caber a qtd de digitos) Gerei o código com o gêneses e comparei linha por linha com o do projeto original (e não vi nada diferente). Então. Acho que o erro está no evento AO ALTERAR do campo txtProdutoQtd sempre que vou digitar mostra o seguinte erro: Mesmo o produto estando cadastrado na tabela Produto informa: Código não Cadastrado e também "Erro SQL: Select*From Produto Where codProduto=1 (= a Um ou o 1º nº digitado) Outro caso é a linha if is numeric, acho que deveria alterar, já que o campo passou a ser texto, não? Enfim, gostaria muito dessa ajuda. ---------------- Private Sub txtProdutoQtd_Change() Dim objProduto As New clsProduto If txtProdutoQtd.Text <> "" Then If InStr(txtProdutoQtd.Text, "*") = 0 Then If IsNumeric(txtProdutoQtd.Text) Then If objProduto.obter(CLng(txtProdutoQtd.Text)) Then txtDescricao = objProduto.descricao txtUnidade = objProduto.unidade Else txtDescricao = "Código não cadastrado..." txtUnidade = Null End If txtQtd = Null txtValorUnt = Null txtSubtotal = Null End If End If Else txtDescricao = Null txtUnidade = Null End If End Sub No evento antes de atualizar Private Sub txtProdutoQtd_BeforeUpdate(Cancel As Integer) On Error GoTo Err_txtProdutoQtd_AfterUpdate Dim codigoProduto As Long não sei se continua como long ou Dim codigoProduto As String Mais uma vez, Desculpas e Grato desde já. |