Resposta rápida
- Tabela sua (prefixo Z)
RecLockeMsUnlock, com os campos de controle preenchidos por você.- Tabela do padrão
- rotina automática,
MsExecAuto, que passa por validação, gatilho e ponto de entrada. - Rotina em MVC
- a mesma chamada, ou o modelo direto com
FWLoadModel. - Cliente (SA1) hoje
CRMA980; oMATA030só repassa para ele quandoMV_MVCSA1está ligado.- Mais de uma gravação
- tudo dentro de
Begin Transaction, comDisarmTransactionno erro.
Nesta página6 seções
Toda customização que grava dado chega nessa escolha. Escolher errado não dá erro de compilação: dá um cliente sem os complementos que o cadastro criaria, um título sem o rateio da rotina ou uma integração nunca avisada. O erro aparece meses depois, em outro módulo.
É tentador ir direto com RecLock numa tabela do padrão:
DbSelectArea("SA1")
If RecLock("SA1", .T.)
SA1->A1_COD := cCodigo
SA1->A1_NOME := cNome
SA1->(MsUnlock())
EndIfCompila e grava, e não dispara mais nada: sem gatilho, sem validação do dicionário, sem o complemento que a rotina padrão geraria sozinha. É esse atalho que o resto deste texto evita.
O que cada caminho faz
RecLock trava um registro da área de trabalho, e você atribui campos
diretamente até o MsUnlock. Nada mais roda — nem validação do dicionário,
nem gatilho do SX7, nem ponto de entrada, nem integração: você está
escrevendo no banco com a mão.
MsExecAuto executa a rotina de tela sem tela. Ela guarda o contexto do
chamador (aHeader, aCols, Inclui, Altera), declara lMsHelpAuto como Private
se ele ainda não existir, marca __cInterNet como "AUTOMATICO" e avalia o
bloco que você passou. No fim, restaura o contexto e devolve .T. sempre.
E quem liga essa variável não é mágica: AutoGRLog(), no mesmo fonte, faz
lMSErroAuto := .T. quando a variável existe como lógica. Por isso ela
precisa ser Private no seu fonte, declarada antes da chamada — o conceito
está em escopo de variáveis.
A regra de decisão
| Situação | Caminho |
|---|---|
| Tabela customizada, regra toda sua | RecLock |
Campo customizado (_X) num registro padrão já gravado | RecLock em alteração |
| Incluir, alterar ou excluir documento/cadastro do padrão | MsExecAuto |
| Rotina padrão em MVC | MsExecAuto na função da rotina, ou o modelo direto |
A segunda linha é a exceção que confunde: marcar um campo seu num pedido que
já existe não precisa de rotina automática — é o que o
MT410INC
faz. Já criar um pedido com RecLock pula estoque, liberação e tudo que o
MATA410 faria — o erro caro.
RecLock numa tabela sua
Com RecLock, os campos que o padrão preencheria são responsabilidade sua,
a começar pela filial. O exemplo usa ZP1, uma tabela de log criada por
você:
Static Function GravaLog(cChave, cTexto)
Local aArea := GetArea()
DbSelectArea("ZP1")
//.T. = inclusão (registro novo)..F. = alteração do registro posicionado.
If RecLock("ZP1", .T.)
ZP1->ZP1_FILIAL := xFilial("ZP1")
ZP1->ZP1_CHAVE := cChave
ZP1->ZP1_TEXTO := cTexto
ZP1->ZP1_USER := RetCodUsr()
ZP1->ZP1_DATA := Date()
ZP1->ZP1_HORA := Time()
ZP1->(MsUnlock())
EndIf
RestArea(aArea)
Return NilPara alterar, posicione antes (DbSeek pelo índice) e chame
RecLock("ZP1", .F.). Guardar e devolver a área é assunto de
GetArea e RestArea.
MsExecAuto no cadastro de cliente
A dúvida sobre qual rotina chamar tem resposta no fonte. MATA030 ainda
existe, mas pergunta MA030IsMVC(), que lê MV_MVCSA1. Se o parâmetro
estiver ligado, ele simplesmente chama CRMA980(xRotAuto,nOpcAuto,,xRAutoFOJ).
O CRMA980 tem esta assinatura:
Function CRMA980(aSA1Auto,nOpcAuto,aAI0Auto,aFOJAuto)aFOJAuto é o quarto parâmetro — o mesmo xRAutoFOJ da chamada do
MATA030 acima. O fonte o descreve como “Tipo de retenção x Clientes”:
dados do submodelo FOJDETAIL, usado só com o motor de retenção ativo.
Com aSA1Auto preenchido, o CRMA980 carrega o modelo com
FWLoadModel("CRMA980") e chama FwMvcRotAuto — duas variantes. Com
FindFunction("FTemMotor") .and. FTemMotor(), a chamada inclui o terceiro
submodelo:
FwMvcRotAuto(oModel,"SA1",nOpcAuto,{{"SA1MASTER",aSA1Auto},{"AI0CHILD",aAI0Auto}, {"FOJDETAIL",aFOJAuto}},/*lSeek*/, .T.)Sem o motor, aFOJAuto fica de fora: só SA1MASTER e AI0CHILD entram.
Chamar o CRMA980 direto evita depender do parâmetro:
#Include "Protheus.ch"
#Include "FWMVCDef.ch"
User Function PGV001(cCodigo, cLoja, cNome)
Local aCampos := {}
Local aLog := {}
Local lOk := .F.
Private lMsErroAuto := .F.
Private lMsHelpAuto := .T.
Private lAutoErrNoFile := .T.
aAdd(aCampos, {"A1_COD", cCodigo, Nil})
aAdd(aCampos, {"A1_LOJA", cLoja, Nil})
aAdd(aCampos, {"A1_NOME", cNome, Nil})
aAdd(aCampos, {"A1_NREDUZ", Left(cNome,20), Nil})
aAdd(aCampos, {"A1_PESSOA", "J", Nil})
aAdd(aCampos, {"A1_TIPO", "F", Nil})
aAdd(aCampos, {"A1_END", "RUA EXEMPLO, 100", Nil})
aAdd(aCampos, {"A1_EST", "SP", Nil})
aAdd(aCampos, {"A1_COD_MUN", "50308", Nil})
Begin Transaction
MsExecAuto({|x, y| CRMA980(x, y)}, aCampos, MODEL_OPERATION_INSERT)
If lMsErroAuto
aLog := GetAutoGRLog()
DisarmTransaction()
Else
GravaLog(cCodigo + cLoja, "Cliente incluido por PGV001")
lOk := .T.
EndIf
End Transaction
If !lOk
aEval(aLog, {|cLinha| ConOut("[PGV001] " + cLinha)})
EndIf
Return lOkOs campos do exemplo existem no SX3 com esses nomes e tamanhos
(A1_NREDUZ tem 20 posições, daí o Left) — o conjunto obrigatório depende
do seu dicionário; confira antes. O log volta como array de linhas;
percorrê-lo é assunto de
arrays em AdvPL.
Três escolhas nesse código merecem explicação.
lAutoErrNoFile := .T. Em AutoGRLog(), essa
variável decide entre o array interno, lido por GetAutoGRLog(), e o
arquivo .log padrão, mostrado por MostraErro() (mesmo fonte) em
diálogo — que uma rotina em job não pode abrir. O mecanismo completo está em
MsExecAuto: como descobrir por que a rotina falhou.
GetAutoGRLog() uma vez só. Ela esvazia o buffer ao ler — chamada de
novo, o array vem vazio.
DisarmTransaction() e não Return. Ele marca a transação para ser
desfeita, e o fluxo segue até o End Transaction. Sair do bloco com Return
no meio deixa a transação aberta. Com isso, o log da ZP1 só existe se o
cliente existir: ou grava os dois, ou nenhum.
O modelo MVC direto
Quando você precisa só de um submodelo, ou quer a mensagem estruturada do MVC, carregue o modelo e faça o que a tela faria:
oModel := FWLoadModel("CRMA980")
oModel:SetOperation(MODEL_OPERATION_INSERT)
oModel:Activate()
oModel:SetValue("SA1MASTER", "A1_COD", cCodigo)
oModel:SetValue("SA1MASTER", "A1_LOJA", cLoja)
oModel:SetValue("SA1MASTER", "A1_NOME", cNome)
If oModel:VldData()
lOk := oModel:CommitData()
EndIf
If !lOk
aErro := oModel:GetErrorMessage()
EndIf
oModel:DeActivate()
oModel:Destroy()Esse caminho dispensa lMsErroAuto: o erro vem de GetErrorMessage(). A
contrapartida é que você passa a depender dos IDs do modelo (SA1MASTER),
que são da TOTVS e podem mudar numa atualização — um SetValue que hoje
grava calado passa a devolver .F. sem aviso na versão seguinte, se o ID do
submodelo mudar de nome. A definição de FwMvcRotAuto não vem nos fontes
distribuídos — só a chamada, presente em muitas outras rotinas do padrão
(CRMA980, FINA010, CTBA020, FINA171, PCOA010, entre outras) — por
isso a referência de uso aqui são as chamadas do próprio CRMA980.
O que fica
Tabela sua: RecLock, com filial e campos de controle na sua conta. Tabela
do padrão: a rotina, porque carrega a regra de negócio que você não quer
reescrever — e toda gravação dupla, dentro de uma transação só. Próximo
passo da série:
tabela temporária,
para quando o dado não deve ir para tabela nenhuma.
Se uma integração sua grava pelo caminho errado e você quer uma segunda leitura, fale com a gente.
Referências
TDNtdn.totvs.com
TDN — Cadastro de Clientes (CRMA980)Endereço citado pelo próprio MATA030.TDNtdn.totvs.com
TDN — Variáveis de controle do MsExecAutoTDNtdn.totvs.com
TDN — AdvPL, documentação da linguagem
Continue por aqui
- Vídeo
DesenvolvimentoTutorial
GetArea e RestArea em AdvPL: área de trabalho e posicionamento
GetArea e RestArea em AdvPL: o que é a área de trabalho, por que qualificar todo acesso com ALIAS-> e por que o PE tem que devolver a área como recebeu.