Pragiva — Tecnologia na prática

Validação de campo: ExistCpo, NaoVazio, Positivo e a função própria

Validação de campo com ExistCpo, NaoVazio, Positivo, Pertence e ExistChav: onde fica no dicionário (X3_VALID e X3_VLDUSER) e como escrever a própria.

Protheus Referência técnica 6 min de leitura

Assista o vídeo

Resposta rápida

Onde fica
duas colunas da SX3 — X3_VALID (validação do sistema) e X3_VLDUSER (validação do usuário).
Como funciona
uma expressão AdvPL que devolve lógico; .F. recusa o conteúdo do campo.
As duas se somam
a validação do usuário é acrescentada à do sistema; uma não substitui a outra.
ExistCpo × ExistChav
ExistCpo exige que o código exista em outra tabela; ExistChav exige que ele ainda não exista na própria tabela.
Armadilha
mexer no X3_VALID de campo padrão. Regra sua vai no X3_VLDUSER.
Consulta rápida6 seções

A validação de um campo é uma expressão AdvPL guardada no dicionário. Quando o conteúdo digitado é confirmado, o sistema avalia a expressão: se ela devolver .T., o conteúdo é aceito; se devolver .F., é recusado.

As duas colunas

ColunaNa tela do ConfiguradorPara quem
X3_VALIDVal. SistemaA regra que o padrão escreveu
X3_VLDUSERVal. UsuárioA regra que você acrescenta

A documentação do Configurador diz que a validação do usuário é somada à do sistema. Na prática, isso significa que você não precisa copiar a regra do padrão para acrescentar a sua — e não deve. O X3_VALID de , por exemplo, encadeia cinco verificações do padrão:

TEXT
A093Prod(.F.).and.existchav("SB1").and.IIF(Subs(M->B1_COD,1,3)=="MOD",A010MOD(),.T.) .And. A010GRADE() .And. Freeforuse("SB1")

Quem edita esse campo para “só acrescentar uma coisinha” passa a responder por tudo isso, misturado à regra própria. Regra sua, coluna sua: X3_VLDUSER. A documentação da SX3 diz o mesmo com outras palavras: o X3_VALID é enviado pelo sistema e não é alterável pelo cliente, salvo permissão; o X3_VLDUSER pode vir preenchido, mas o cliente pode customizá-lo.

Dentro da expressão, o conteúdo em edição é lido com M->campo (M->A1_EST, M->ZA1_NASC).

As funções que resolvem a maioria dos casos

FunçãoO que faz
NaoVazio()Não aceita branco em campo caractere nem zero em numérico
Vazio()Verdadeiro quando o campo está vazio; com .Or., torna a regra opcional
Positivo()Recusa valor negativo; o tratamento do zero não é garantido (veja abaixo)
Pertence()Restringe o conteúdo aos valores listados; sem o segundo parâmetro, confere o campo em digitação
ExistCpo()Aceita se a chave existe na tabela informada
ExistChav()Aceita se a chave não existe na tabela

As três últimas recebem parâmetros; os opcionais estão entre colchetes:

TEXT
Pertence(ExpC1 [, ExpC2])
ExistCpo(alias [, expressão] [, índice])
ExistChav(alias [, expressão] [, índice] [, help])

Na página de Validações do Configurador, a documentação avisa que o último parâmetro tem como default o campo que está sendo digitado. Por isso a expressão pode ficar de fora: ExistCpo("SBM") procura o próprio conteúdo do campo.

Onde o dicionário padrão usa cada uma:

  • : NaoVazio().And.ExistChav("SC5")
  • : Vazio() .Or. (A010Grupo() .And. ExistCpo("SBM"))
  • : Positivo()
  • (combo 1/2): pertence("12")
  • : ExistCpo("SX5","12"+M->A1_EST) .AND. IE(M->A1_INSCR,M->A1_EST)

O par Vazio() .Or. ... merece atenção: sem ele, uma regra como ExistCpo("SBM") recusaria o campo em branco, e o grupo do produto passaria a ser obrigatório sem ninguém ter decidido isso.

O zero no Positivo()

A documentação da TOTVS não fecha a questão. A página de Validações do Configurador diz que Positivo() “verifica se o valor digitado é maior que 0”. A página da própria função diz apenas que ela verifica se o conteúdo “é positivo” e que é o inverso de NEGATIVO() — o que não diz nada sobre o zero.

O dicionário padrão escreve as duas combinações:

  • : Positivo().And.NaoVazio() — o NaoVazio() só acrescenta alguma coisa se Positivo() deixar o zero passar;
  • : positivo() .or. vazio() — o vazio() só acrescenta alguma coisa se Positivo() recusar o zero.

A regra prática vale nos dois casos, seja qual for o comportamento no seu ambiente:

  • para recusar o zero com certeza, combine com NaoVazio(): Positivo() .And. NaoVazio();
  • para aceitar o zero com certeza, combine com Vazio() em .Or.: Positivo() .Or. Vazio().

ExistCpo ou ExistChav

As duas procuram uma chave num índice. A diferença é o que cada uma quer encontrar:

  • ExistCpo valida uma chave estrangeira: o código digitado precisa existir em outra tabela. O grupo do produto precisa existir na ; o estado do cliente precisa existir na tabela genérica 12 da SX5. Devolve .T. se encontrar; se não encontrar, devolve .F. e mostra um help.
  • ExistChav valida uma chave única: o código digitado não pode existir ainda na tabela que está sendo cadastrada. É o que impede dois pedidos com o mesmo C5_NUM. Devolve .T. se não encontrar; se encontrar, devolve .F. e mostra um help.

Em uma frase: ExistCpo no campo que aponta para outro cadastro; ExistChav no campo que é a chave do cadastro.

Nos dois casos, sem a expressão, a busca usa o conteúdo do próprio campo — é como o padrão escreve ExistCpo("SBM") e ExistChav("SC5"). O terceiro parâmetro escolhe o índice, como em ExistCpo("SN1",M->AA3_CHAPA,2). Sem ele, cada função segue um default diferente, segundo a documentação:

  • ExistCpo assume o último índice definido;
  • ExistChav assume o primeiro índice do arquivo de dados.

Quando a chave procurada não for a do índice 1, informe o terceiro parâmetro nas duas — no ExistCpo, não conte com o default.

O dicionário padrão nem sempre segue esse cuidado: no B1_GRUPO, o ExistCpo("SBM") omite o índice, embora a SBM tenha dois (1 pelo código do grupo, 2 pela descrição). Mas o padrão também escreve o índice 1 por extenso, como no : ExistCpo( "CTT", M->AAH_CCUSTO, 1 ). É essa a forma usada no exemplo abaixo.

As validações do cadastro de motoristas

No exemplo da série, a transportadora tem o cadastro de motoristas ZA1 e o campo C5_XMOTOR no pedido de venda. Numa tabela customizada como a ZA1, as duas colunas são do cliente: não há regra do padrão a preservar, e a série grava as regras no X3_VALID. O X3_VLDUSER é a coluna para acrescentar regra a um campo do padrão sem mexer no X3_VALID dele. O C5_XMOTOR está numa tabela do padrão, a , mas o campo foi criado pelo cliente: o X3_VALID dele também não tem regra do padrão a preservar. As regras do exemplo, no X3_VALID:

  • ZA1_COD: NaoVazio() .And. ExistChav("ZA1") — código não pode ficar vazio nem se repetir, como o C5_NUM.
  • ZA1_NOME: NaoVazio() — nome obrigatório.
  • ZA1_VINC: Pertence("123") — só aceita os três códigos do combo.
  • ZA1_IDADE: Positivo() .And. NaoVazio() — idade não pode ser zero nem negativa; o NaoVazio() garante a recusa do zero.
  • ZA1_NASC: U_ZA1VldIdade() — regra de negócio: idade mínima.
  • C5_XMOTOR: Vazio() .Or. ExistCpo("ZA1", M->C5_XMOTOR, 1) — motorista opcional, mas, se informado, precisa existir. O índice 1 vai explícito porque a ZA1 tem um segundo índice, por nome.

Função própria de validação

Quando a regra não cabe numa expressão legível, ela vira uma User Function chamada pelo dicionário — U_ZA1VldIdade() no X3_VALID do ZA1_NASC. O próprio dicionário padrão tem validações que chamam U_, então o formato é aceito.

A regra aqui: motorista precisa ter a idade mínima definida no parâmetro customizado MV_XIDMIN (numérico, conteúdo 21). A função vai no campo de nascimento, que é o que o usuário digita, e calcula a idade ali mesmo. Se ela fosse no ZA1_IDADE, dependeria de o gatilho que preenche a idade já ter rodado.

ADVPL / TLPP
#Include "Protheus.ch"

/*/{Protheus.doc} ZA1VldIdade
Validação (X3_VALID) do campo ZA1_NASC.
Recusa motorista abaixo da idade mínima definida em MV_XIDMIN.
@type user function
@return logical, .T. aceita o conteúdo; .F. recusa
/*/
User Function ZA1VldIdade()

    Local lRet   := .T.
    Local dNasc  := M->ZA1_NASC
    Local nIdMin := SuperGetMV("MV_XIDMIN", .F., 21)
    Local nIdade := 0

    // Campo em branco: quem cobra o preenchimento é a obrigatoriedade
    // do campo, não esta regra.
    If Empty(dNasc)
        Return .T.
    EndIf

    // Anos completos entre o nascimento e a data-base: a mesma função
    // do gatilho que preenche o ZA1_IDADE.
    nIdade := DateDiffYear(dDataBase, dNasc)

    If nIdade < nIdMin
        Help(" ", 1, "ZA1IDADE",, "Motorista com " + cValToChar(nIdade) + ;
            " anos. Idade mínima: " + cValToChar(nIdMin) + " (MV_XIDMIN).", 1, 0)
        lRet := .F.
    EndIf

Return lRet

Três decisões que valem para qualquer validação sua:

  • Todo caminho devolve lógico. Uma função de validação que termina sem Return .T. ou Return .F. claro deixa a regra do campo imprevisível.
  • Recusar sempre com mensagem. Um .F. calado prende o usuário no campo sem explicar por quê. Help(), no formato acima, é o que o padrão usa.
  • O limite vem de parâmetro, não do fonte. SuperGetMV("MV_XIDMIN", .F., 21) lê o parâmetro com 21 como valor padrão. A empresa muda a idade mínima sem recompilar. Criar e ler um parâmetro customizado está em Parâmetros (SX6).

A idade sai de DateDiffYear(), a mesma conta do gatilho de Gatilhos no Protheus, que já desconta o aniversário que ainda não chegou. A data de referência é dDataBase, a data-base do sistema, a mesma que o padrão usa como data de emissão do pedido. Por que variáveis como dDataBase e M->ZA1_NASC estão visíveis dentro da sua função, sem serem passadas como parâmetro, está em Escopo de variáveis em AdvPL.

Se o cadastro for uma rotina MVC e a validação precisar ir além do dicionário, Validar e bloquear campo no MVC mostra como a regra vive no modelo.

Conferindo no banco

As duas validações de cada campo da tabela, lado a lado. O sufixo 010 é o do exemplo; o seu depende da empresa.

Confira no seu ambienteSQL
SELECT X3_CAMPO, X3_VALID, X3_VLDUSER
  FROM SX3010
 WHERE X3_ARQUIVO = 'ZA1'
   AND (X3_VALID <> ' ' OR X3_VLDUSER <> ' ')
   AND D_E_L_E_T_ = ' '
 ORDER BY X3_ORDEM

E o parâmetro que a função lê — cadastro, tipo e conteúdo no seu ambiente:

Confira no seu ambienteSQL
SELECT X6_FIL, X6_VAR, X6_TIPO, X6_DESCRIC, X6_CONTEUD
  FROM SX6010
 WHERE X6_VAR = 'MV_XIDMIN'
   AND D_E_L_E_T_ = ' '

Nenhuma linha significa que o parâmetro não foi criado — e a função vai usar o 21 do fonte. X6_TIPO precisa ser N: com o parâmetro criado como caractere, a comparação nIdade < nIdMin passa a comparar número com texto.

Se uma validação está recusando o que não devia e ninguém sabe de onde ela vem, fale com a gente.

Referências

  1. TDNtdn.totvs.com

    TDN — SX3, Campos das tabelas
  2. TDNtdn.totvs.com

    TDN — Pasta Validações (CFGX034 - SIGACFG)
  3. TDNtdn.totvs.com

    TDN — Função EXISTCPO()
  4. TDNtdn.totvs.com

    TDN — Função EXISTCHAV()
  5. TDNtdn.totvs.com

    TDN — Função POSITIVO()
  6. TDNtdn.totvs.com

    TDN — VAZIO()

Continue por aqui