Pragiva — Tecnologia na prática

PARAMIXB o array que às vezes não é seu

PARAMIXB: nem todo ponto de entrada recebe, e quando não recebe pode existir mesmo assim com o conteúdo de outro. Como descobrir o layout sem se enganar.

Desenvolvimento Referência técnica 5 min de leitura

Assista o vídeo

PARAMIXB em resumo

O que é
o quarto argumento do ExecBlock ou ExecTemplate que chamou o ponto de entrada.
Sempre chega
não. Só quando o padrão passa esse quarto argumento.
Escopo
Private — continua visível em tudo que a rotina padrão chamar depois.
Layout
fixo por ponto de entrada — a linha que chama define o conteúdo — mas não documentado.
Risco maior
pode carregar o retorno de validações anteriores; sobrescrever com .T. apaga esse retorno em silêncio.
Consulta rápida6 seções

PARAMIXB é a variável que o engine cria quando executa um ponto de entrada, contendo exatamente o que a rotina padrão passou no quarto argumento de ExecBlock. É o que acontece em A410VldTOk(), ao chamar o MT410TOK:

ADVPL / TLPP
lRet := Execblock("MT410TOK", .F., .F., {nOpc, aRecnoSE1RA})
//                                      └─────────┬────────┘
//                                          vira PARAMIXB

Ela vale para todos os PEs e carrega três comportamentos raramente escritos, cada um capaz de gerar um bug que não dá erro.

Se você ainda não viu como o padrão chama um PE, comece por Ponto de entrada no Protheus.

1. Nem todo PE recebe

Se a rotina padrão chama sem o quarto argumento, não há array:

ADVPL / TLPP
Execblock("MT410INC", .F., .F.) // sem 4º argumento

Não existe PARAMIXB[1], não existe PARAMIXB[2]. Escrever PARAMIXB[1] num PE desses é erro de execução — na melhor das hipóteses.

Qual é a pior? A próxima seção.

2. Ele pode existir com o conteúdo de outro

PARAMIXB é Private. Em AdvPL, Private é visível na função que declara e em tudo que ela chamar abaixo na pilha.

Consequência: se um ExecBlock mais acima na pilha criou PARAMIXB, ele pode continuar visível dentro do seu PE — com o conteúdo de outro completamente diferente.

O resultado é o pior tipo de bug: Type("PARAMIXB") == "A" devolve verdadeiro, PARAMIXB[1] devolve um valor plausível, o código roda, ninguém vê erro nenhum, e a decisão foi tomada com o dado errado.

Regra prática: em ponto de entrada que o padrão chama sem parâmetro, não leia PARAMIXB. Pegue o que precisa das áreas de trabalho e das variáveis Private da rotina padrão.

3. Onde ele existe, o layout é fixo — e não documentado

Cada ponto de entrada tem a sua própria assinatura, definida pela linha que o chama dentro da rotina padrão. Não há um layout comum entre pontos de entrada diferentes, nem uma tabela oficial que resuma todos eles.

O MT410TOK, por exemplo — a mesma chamada de A410VldTOk() de cima:

PosiçãoTipoConteúdo
PARAMIXB[1]NuméricoOperação: 3 inclusão, 4 alteração, 5 exclusão
PARAMIXB[2]Array de arraysAdiantamentos: cada entrada é {pedido, recno do SE1, valor}

A segunda linha é a lição inteira do artigo num exemplo: o nome mente. aRecnoSE1RA sugere uma lista de recnos e é uma lista de arrays de três posições. O recno está em PARAMIXB[2][n][2].

Quem descobre isso lendo o nome da variável escreve SE1->(DbGoTo(PARAMIXB[2][n])) e leva erro de tipo. Quem descobre lendo a rotina padrão vê o padrão consumindo o próprio array dentro de A410CkClRA():

ADVPL / TLPP
SE1->(DbGoTo(aRecnoSE1RA[01][02]))

É a diferença entre deduzir e conferir. O GMCVndVLD, adiante, recebe na primeira posição o retorno de validações anteriores — não um código de operação nem um objeto de grid.

Como descobrir o layout de um PE que você não conhece

Dois caminhos, nesta ordem:

1. Ler a linha que chama, dentro da rotina padrão. É a fonte da verdade. Localize a chamada pelo nome do PE e olhe o quarto argumento do ExecBlock. O que estiver ali é o seu PARAMIXB.

2. Imprimir. Quando o fonte não está à mão:

ADVPL / TLPP
ConOut("[MEU_PE] PARAMIXB = " + cValToChar(PARAMIXB))

Rode a operação e leia o log: em dois minutos você tem o layout real da sua versão, não o de outra release.

O caso que muda o jeito de escrever: PARAMIXB como retorno anterior

Alguns pontos de entrada de validação passam, numa das posições, o valor que as validações anteriores do padrão já decidiram. O layout aparece no GMCVndVLD, chamado dentro de A410VldTOk():

ADVPL / TLPP
If lRet
    If ExistBlock("GMCVndVLD")
 lRet := ExecBlock("GMCVndVLD", .F., .F.,{ lRet,aHeader,aCols,aGEMCVnd })
    ElseIf ExistTemplate("GMCVndVLD",, .T.)
        //
        // executa a validacao da condicao de venda
        //
 lRet := ExecTemplate("GMCVndVLD", .F., .F.,{ lRet,aHeader,aCols,aGEMCVnd })
    EndIf
EndIf

PARAMIXB[1] é literalmente o lRet de quem chamou. [2], [3] e [4] são aHeader, aCols e aGEMCVnd, como o próprio PE os lê, na Template Function GMCVndVLD() — um template de segmento da TOTVS, não uma customização de cliente:

ADVPL / TLPP
Local lRetorno := iIf( ParamIXB[1] == NIL, .T.,ParamIXB[1] )
Local aHeader := iIf( ParamIXB[2] == NIL,{},ParamIXB[2] )
Local aCols := iIf( ParamIXB[3] == NIL,{},ParamIXB[3] )
Local aCondVnd := iIf( ParamIXB[4] == NIL,{},ParamIXB[4] )

Mas a chamada está dentro de If lRet: nesse call-site, PARAMIXB[1] sempre chega .T.. O GMCVndVLD mostra o layout, não o risco de recebê-lo já reprovado — para isso, o call-site certo é outro.

Onde o veredito anterior pode mesmo chegar reprovado

Lj010EvaLi valida a linha digitada no LOJA010, a rotina de Vendas - Balcão do SigaLoja — não o PDV: o Front Loja é LOJA701 e não chama essa função. Ele é chamado sem essa guarda, dentro do LOJA010B:

ADVPL / TLPP
If ExistBlock("Lj010EvaLi")
    lRet := ExecBlock("Lj010EvaLi", .F., .F.,{lRet})
Endif

Entre o início da própria lj010EvaLi() do padrão e o ponto onde essa chamada dispara, ela atribui lRet := .F. onze vezes: produto vazio, TES inválido, série de outro produto, local, localização, lote ou sub-lote divergente e saldo insuficiente são oito delas — as outras três vivem num ramo de legislação de El Salvador (cPaisLoc == "SAL"), inalcançável num ambiente brasileiro. PARAMIXB[1], ali, pode chegar genuinamente reprovado:

ADVPL / TLPP
User Function Lj010EvaLi()

Local lRet     := PARAMIXB[1]     // o que a validação padrão já decidiu
Local cProduto := GDFieldGet("L2_PRODUTO", n)   // "n" é Private da rotina padrão

If lRet .And. Rastro(cProduto, "S") .And. Empty(GDFieldGet("L2_NLOTE", n))
    MsgAlert("Informe o lote antes de confirmar a linha.")
 lRet := .F.
EndIf

Return lRet

O MsgAlert antes de lRet := .F. não é estética: é a mesma função que a rotina padrão usa em seis dos oito reprovos alcançáveis num ambiente brasileiro — por exemplo MsgAlert(STR0100+SBF->BF_PRODUTO), quando o serial pertence a outro produto. Os outros dois (produto vazio e TES inválido), mais cedo na função, usam Help() em vez de MsgAlert. Sem uma mensagem, de um jeito ou de outro, o botão só não confirma, e o usuário abre chamado dizendo que “o sistema travou”.

A primeira linha da customização começa do que a validação anterior decidiu, em vez de começar de .T.:

  • Iniciando com PARAMIXB[1], a sua regra só consegue apertar — se algo antes reprovou a linha, ela continua reprovada.
  • Iniciando com .T., você derruba silenciosamente toda validação anterior, e o problema que existia antes some sem deixar rastro.

O segundo caso é fácil de escrever sem perceber, e o sintoma aparece semanas depois, num dado que ninguém liga à customização.

O M410LIOK é o contraste: recebe o objeto MsGetDados do grid, não um array com retorno anterior, e só é chamado quando o padrão já aprovou a linha, dentro de A410LinOk() (nas duas chamadas, Template e Block, ambas guardadas por If lRetorno .And.). Ele só aperta, nunca afrouxa.

A leitura defensiva

Onde PARAMIXB existe e você depende dele, conferir é barato:

ADVPL / TLPP
If Type("PARAMIXB") <> "A" .Or. Len(PARAMIXB) < 1 .Or. ValType(PARAMIXB[1]) <> "N"
    ConOut("[MEU_PE] PARAMIXB inesperado. Regra ignorada.")
 Return .T. // fail-safe: nunca bloquear por omissão
EndIf

Três coisas nesse trecho:

  • Confere tipo, tamanho e o tipo do elemento — não só se o array existe.
  • Registra no console. Se uma atualização de release mudar a assinatura, você descobre pelo log, e não por um chamado.
  • Sai com .T., não com .F.: o padrão seguro é deixar passar na dúvida — bloquear por omissão trava a operação inteira por causa de uma incompatibilidade sua.

Em uma linha

PARAMIXB é Private, chega só quando o padrão manda, e o layout é o que a linha que chama, dentro da rotina padrão, define. Se o seu PE não recebe, não leia. Se recebe e começa com o retorno anterior, respeite ele.

Para ver isso aplicado a um caso concreto: MT410INC, o ponto de entrada que não recebe PARAMIXB e cujo retorno é descartado.

Referências

  1. TDNtdn.totvs.com

    TDN — Pontos de Entrada

Continue por aqui