Resposta rápida
- O que é salvo
- alias, ordem do índice e número do registro (
Alias(),IndexOrd(),RecNo()). - Área corrente
GetArea().- Área de uma tabela
SA1->(GetArea()).- Restaurar
RestArea(aArea), na ordem inversa em que as áreas foram salvas.- Onde é obrigatório
- ponto de entrada e qualquer função chamada no meio de uma rotina padrão.
- Alternativa do framework
FWGetArea()/FWRestArea(), com a mesma forma de chamada.
Nesta página7 seções
Quase todo erro de “o pedido gravou com o cliente errado” sem rastro no log começa aqui: uma customização mexeu no posicionamento de uma tabela e não devolveu. O código compila, roda, e o padrão continua trabalhando em cima do registro que você deixou.
Área de trabalho, em uma frase
Cada tabela aberta ocupa uma área de trabalho, identificada pelo alias
(SA1, SC5), com três coisas próprias: o índice ativo, o registro
posicionado e os campos desse registro. Existe sempre uma área corrente —
a última selecionada — e é ela que responde a todo comando escrito sem alias:
EoF(), DbSkip(), RecNo() e um campo solto como A1_NOME perguntam para
quem estiver selecionado naquele instante, não para a tabela que você tinha em
mente.
Qualifique sempre: ALIAS->
Dois laços aninhados mostram o problema. O de fora percorre pedidos; o de
dentro, os itens. Sem alias, o DbSkip() do laço externo roda depois do
interno — e a área corrente, a essa altura, é a dos itens:
// Frágil: EoF() e DbSkip() dependem de quem está selecionado
DbSelectArea("SC5")
While !EoF()
DbSelectArea("SC6")
//... percorre os itens...
DbSkip() // ainda está na SC6
EndDoA correção que resiste a manutenção é dizer a tabela em cada acesso:
While SC5->(!EoF())
While SC6->(!EoF()) .And. SC6->C6_NUM == SC5->C5_NUM
//...
SC6->(DbSkip())
EndDo
SC5->(DbSkip())
EndDoSC5->(expressão) avalia a expressão com a SC5 como área corrente e devolve a
seleção anterior no fim. Espalhar DbSelectArea() antes de cada comando também
funciona — até alguém inserir uma linha no meio. SA1->A1_NOME, com alias, lê
o campo da SA1 independente de quem esteja selecionado.
O laço em si está em Condições e laços em AdvPL.
Posicionar: ordem, filial, chave
Posicionar é sempre a mesma sequência: escolher o índice e buscar pela chave completa, começando pela filial.
SA1->(DbSetOrder(1)) // A1_FILIAL+A1_COD+A1_LOJA
If SA1->(DbSeek(xFilial("SA1") + cCodCli + cLojaCli))
// SA1 posicionada no cliente
EndIfNo dicionário, a ordem 1 da SA1 é A1_FILIAL+A1_COD+A1_LOJA e a 2 é
A1_FILIAL+A1_NOME+A1_LOJA; na SB1, a 1 é B1_FILIAL+B1_COD. Confira no seu
ambiente antes de fixar um número no código:
SELECT INDICE, ORDEM, CHAVE, DESCRICAO
FROM SIX010
WHERE INDICE IN ('SA1', 'SB1') AND D_E_L_E_T_ = ' '
ORDER BY INDICE, ORDEMxFilial("SA1") devolve a filial no formato que a tabela espera —
compartilhada ou exclusiva. Montar a chave com cFilAnt direto funciona até
mudar o compartilhamento da tabela. A assinatura da função nativa é
xFilial( cAlias, cFil ), e é ela quem decide esse formato por você.
DbSeek que não encontra deixa a tabela em fim de arquivo. Por isso o teste do
retorno não é opcional: sem ele, o código seguinte lê campos vazios sem erro.
O que GetArea() guarda
GetArea() é enxuta por definição: devolve um
array { Alias(), IndexOrd(), RecNo() }. Três posições, nada mais — não guarda
filtro, não guarda campos, não guarda a seleção de outras tabelas.
RestArea(aArea) faz o caminho de volta:
DbSelectArea no alias, DbSetOrder se a ordem mudou e DbGoTo se o registro
mudou. Dois comportamentos que a documentação oficial não deixa claros, e que
só aparecem na prática:
- se a área já foi fechada (
Select()igual a zero), ele não faz nada — não dá erro, também não restaura; GetArea(cSqlAlias), com o alias igual ao corrente, gravaNilno lugar do registro, e oRestAreacorrespondente não reposiciona. É um caso de tabela em inclusão, não o uso comum.
A diferença entre as duas formas de chamar:
| Chamada | Salva |
|---|---|
GetArea() | a área corrente, seja qual for |
SA1->(GetArea()) | a área da SA1, e só ela |
GetArea() sozinho não protege a SA1 se a corrente era a SC5. Para cada tabela
em que você vai mexer, um ALIAS->(GetArea()).
Por que em ponto de entrada é obrigatório
O ponto de entrada roda no meio da rotina padrão. Antes de chamar você, a
rotina padrão já posicionou as tabelas de que precisa; depois que você
devolve o controle, ela continua lendo essas tabelas sem reposicionar. Um
DbSeek seu na SA1, e na linha seguinte do padrão SA1->A1_... é o cliente
que você buscou. O mecanismo da chamada está em
Ponto de entrada no Protheus.
Exemplo
Um ponto de entrada fictício de validação do pedido, PGV001, que confere se
algum produto do grid está bloqueado e se o cliente tem risco E.
#Include "Protheus.ch"
/*/{Protheus.doc} PGV001
Validação fictícia do pedido: recusa cliente de risco E e produto bloqueado.
Devolve as áreas exatamente como recebeu.
@type user function
@author Pragiva
@return logical, .T. permite continuar
/*/
User Function PGV001()
Local aArea := GetArea() // corrente: salva primeiro
Local aAreaSA1 := SA1->(GetArea())
Local aAreaSB1 := SB1->(GetArea())
Local lOk := .T.
Local nPosPrd := 0
Local nX := 0
SA1->(DbSetOrder(1))
If SA1->(DbSeek(xFilial("SA1") + M->C5_CLIENTE + M->C5_LOJACLI))
lOk := SA1->A1_RISCO <> "E"
EndIf
If lOk .And. Type("aCols") == "A" .And. Type("aHeader") == "A"
nPosPrd := GDFieldPos("C6_PRODUTO", aHeader)
SB1->(DbSetOrder(1))
For nX := 1 To Len(aCols)
If SB1->(DbSeek(xFilial("SB1") + aCols[nX, nPosPrd])) ;
.And. SB1->B1_MSBLQL == "1"
lOk := .F.
Exit
EndIf
Next nX
EndIf
// Ordem inversa: a corrente volta por último
RestArea(aAreaSB1)
RestArea(aAreaSA1)
RestArea(aArea)
Return lOkA ordem inversa não é estética: cada RestArea termina com um
DbSelectArea, e quem é restaurado por último define a área corrente.
Restaurar aArea antes da SB1 deixaria a SB1 selecionada — o padrão não
esperava isso.
Type("aCols") guarda contra variável Private ausente em alguma chamada —
veja
Escopo de variáveis em AdvPL. Dado
vindo do PARAMIXB tem cuidado próprio, em
PARAMIXB, o array que às vezes não é seu.
E FWGetArea / FWRestArea?
Existem, e o próprio padrão as usa com a mesma forma de chamada —
salvando RD0->(FwGetArea()) e FW5->(FwGetArea()), por exemplo, e
restaurando com FWRestArea, na ordem inversa. A definição não é pública,
então não há como detalhar o que guardam além do uso observado. Para
código novo, são a opção do framework; GetArea/RestArea continua
funcionando e é o que você vai encontrar em quase todo legado.
Quando a consulta é só leitura e não precisa do registro posicionado, a saída mais limpa é nem tocar na área: ler por SQL — Consultas SQL em AdvPL.
Se uma customização sua está trocando registro em silêncio e você quer uma segunda leitura, fale com a gente.
Referências
TDNtdn.totvs.com
TDN — DBSelectAreaTDNtdn.totvs.com
TDN — DBSeekTDNtdn.totvs.com
TDN — DBGoTo
Continue por aqui
- Vídeo
DesenvolvimentoTutorial
Operadores em AdvPL: atribuição, comparação, lógica e precedência
Operadores em AdvPL: a diferença entre = e == em texto, o curto-circuito do .And. e do .Or., o Default para parâmetro opcional e a ordem de precedência.