Resposta rápida
- Onde fica
- duas colunas da
SX3—X3_VALID(validação do sistema) eX3_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
ExistCpoexige que o código exista em outra tabela;ExistChavexige que ele ainda não exista na própria tabela.- Armadilha
- mexer no
X3_VALIDde campo padrão. Regra sua vai noX3_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
| Coluna | Na tela do Configurador | Para quem |
|---|---|---|
X3_VALID | Val. Sistema | A regra que o padrão escreveu |
X3_VLDUSER | Val. Usuário | A 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 B1_COD, por
exemplo, encadeia cinco verificações do padrão:
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ção | O 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:
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:
C5_NUM:NaoVazio().And.ExistChav("SC5")B1_GRUPO:Vazio() .Or. (A010Grupo() .And. ExistCpo("SBM"))AA1_VALOR:Positivo()B1_MSBLQL(combo1/2):pertence("12")A1_EST: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:
AF5_QUANT:Positivo().And.NaoVazio()— oNaoVazio()só acrescenta alguma coisa sePositivo()deixar o zero passar;C6_COMIS1:positivo() .or. vazio()— ovazio()só acrescenta alguma coisa sePositivo()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:
ExistCpovalida uma chave estrangeira: o código digitado precisa existir em outra tabela. O grupo do produto precisa existir naSBM; o estado do cliente precisa existir na tabela genérica12da SX5. Devolve.T.se encontrar; se não encontrar, devolve.F.e mostra um help.ExistChavvalida 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 mesmoC5_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:
ExistCpoassume o último índice definido;ExistChavassume 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 AAH_CCUSTO: 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 SC5, 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 oC5_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; oNaoVazio()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 aZA1tem 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.
#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 lRetTrê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.ouReturn .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 com21como 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.
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_ORDEME o parâmetro que a função lê — cadastro, tipo e conteúdo no seu ambiente:
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
TDNtdn.totvs.com
TDN — SX3, Campos das tabelasTDNtdn.totvs.com
TDN — Pasta Validações (CFGX034 - SIGACFG)TDNtdn.totvs.com
TDN — Função EXISTCPO()TDNtdn.totvs.com
TDN — Função EXISTCHAV()TDNtdn.totvs.com
TDN — Função POSITIVO()TDNtdn.totvs.com
TDN — VAZIO()
Continue por aqui
- Vídeo
ProtheusReferência técnica
Campo real ou virtual: o contexto decide se ele existe no banco
Campo real ou virtual no Protheus: X3_CONTEXT decide se ele vira coluna na tabela ou só existe na tela. Como montar um virtual que busca de outra tabela.