Você precisa importar 3 mil produtos de uma planilha do fornecedor pra dentro do Protheus. Ou lançar 500 pedidos de venda vindos de um marketplace. Ou rodar baixa de mil títulos de uma vez. Abrir a interface e digitar manualmente está fora de cogitação — e RecLock direto na tabela é tiro no pé porque você pula gatilhos, validações, integrações fiscais, contábeis, MVs.
A resposta certa nesse cenário é MSExecAuto: a forma oficial de executar uma rotina padrão (MATA010, FINA070, MATA410…) como se um usuário tivesse digitado tudo na tela, mas sem abrir tela nenhuma. O Protheus roda toda a regra de negócio no fundo, valida, grava, contabiliza — e você só passa um array de campos.
Como funciona por baixo
Toda rotina padrão do Protheus tem uma função principal (MATA010, MATA030, FINA070, etc.) que sabe se comportar de dois jeitos: interativo (chamado pelo menu, abre tela) ou automático (chamado por MSExecAuto, lê os dados de variáveis e arrays). A função detecta o modo pelo contexto e pula a parte de tela quando estiver em ExecAuto.
Sintaxe básica:
MSExecAuto({|x, y| MATA010(x, y)}, aDados, nOpcao)O primeiro argumento é um code block que recebe os parâmetros e chama a rotina alvo. aDados é um array com os campos a preencher. nOpcao indica a operação:
3— inclusão4— alteração5— exclusão
As variáveis PRIVATE obrigatórias
Antes de qualquer chamada, você precisa declarar variáveis em escopo PRIVATE (não LOCAL) que o framework lê do contexto:
PRIVATE lMsErroAuto := .F. // vira .T. se rotina falhar
PRIVATE lMsHelpAuto := .T. // suprime Help interativo (essencial em batch)Isso não é opcional. Se você declarar LOCAL lMsErroAuto, a rotina interna não enxerga, vai escrever em outro escopo, e quando você testar depois sempre vai estar .F. mesmo que tenha falhado.
Exemplo simples: cadastrar um produto (MATA010)
User Function ATImpProd()
Local aArea := GetArea()
Local aProduto := {}
PRIVATE lMsErroAuto := .F.
PRIVATE lMsHelpAuto := .T.
aProduto := { ;
{"B1_COD", "PROD001", NIL}, ;
{"B1_DESC", "Caneta esferográfica azul", NIL}, ;
{"B1_TIPO", "MC", NIL}, ;
{"B1_UM", "UN", NIL}, ;
{"B1_LOCPAD", "01", NIL}, ;
{"B1_GRUPO", "0001", NIL}, ;
{"B1_PRV1", 3.50, NIL} ;
}
MSExecAuto({|x, y| MATA010(x, y)}, aProduto, 3)
If lMsErroAuto
ConOut("Erro ao incluir produto:")
ConOut(MostraErro()) // ou GetAutoGrLog() em batch puro
Else
ConOut("Produto incluído: " + SB1->B1_COD)
EndIf
RestArea(aArea)
ReturnCada linha do array tem 3 elementos: nome do campo, valor, e NIL (reservado pra modo M>F, raramente usado). Não precisa preencher todos os campos da SX3 — só os obrigatórios e os que você quer setar; o resto pega default.
Exemplo composto: pedido de venda (MATA410)
Rotinas com cabeçalho + itens recebem dois (ou mais) arrays separados. O bloco passa cada um como argumento:
User Function ATImpPedido()
Local aArea := GetArea()
PRIVATE lMsErroAuto := .F.
PRIVATE lMsHelpAuto := .T.
Local aCabec := { ;
{"C5_TIPO", "N", NIL}, ;
{"C5_CLIENTE", "000001", NIL}, ;
{"C5_LOJACLI", "01", NIL}, ;
{"C5_CONDPAG", "001", NIL}, ;
{"C5_EMISSAO", Date(), NIL} ;
}
Local aItens := { ;
{ ;
{"C6_ITEM", "01", NIL}, ;
{"C6_PRODUTO", "PROD001", NIL}, ;
{"C6_QTDVEN", 10, NIL}, ;
{"C6_PRCVEN", 25.00, NIL}, ;
{"C6_TES", "501", NIL} ;
} ;
}
MSExecAuto({|x, y, z| MATA410(x, y, z)}, aCabec, aItens, 3)
If lMsErroAuto
ConOut(GetAutoGrLog())
EndIf
RestArea(aArea)
ReturnO array de itens é um array de arrays — cada item do pedido é um array no mesmo formato do cabeçalho. Quer 5 itens no pedido? Cinco sub-arrays.
Tratamento de erro: MostraErro vs GetAutoGrLog
Quando lMsErroAuto = .T., você tem duas formas de pegar o motivo:
MostraErro()— abre janela com erro (use em rotina interativa).GetAutoGrLog()— retorna string com log completo (use em batch, jobs, web service).
Em job sem interface, MostraErro trava. Sempre GetAutoGrLog() em batch. Padrão comum pra logar:
If lMsErroAuto
cLog := GetAutoGrLog()
ConOut("=== ERRO ExecAuto ===")
ConOut(cLog)
// grave em arquivo, banco, ou retorne pra integração
EndIfTransação: rollback automático
Se a rotina falha no meio (cabeçalho gravado, item dá erro), você quer reverter tudo. Embrulhe num Begin Transaction:
Begin Transaction
MSExecAuto({|x, y, z| MATA410(x, y, z)}, aCabec, aItens, 3)
If lMsErroAuto
DisarmTransaction()
Break
EndIf
End TransactionDisarmTransaction() + Break faz rollback de tudo que foi gravado dentro do bloco. Sem isso, em alguns cenários o cabeçalho fica órfão.
Pegadinhas que travam projeto
1. PRIVATE não LOCAL
Já mencionado, mas vale repetir: variáveis de controle do ExecAuto precisam ser visíveis em todos os escopos chamados. LOCAL não funciona. Em rotinas customizadas (User Functions), declare PRIVATE no topo da função.
2. cFilAnt e usuário
O ExecAuto roda no contexto da filial corrente (cFilAnt) e usuário corrente. Se sua rotina batch precisa rodar em filial diferente, faça cFilAnt := "02" antes (e restaure depois). Pra job sem usuário logado, prepare ambiente com RpcSetType(3) + RpcSetEnv().
3. MV_PAR1, MV_PAR2…
Algumas rotinas (especialmente processos como FINA070 — baixa) usam MV_PAR1, MV_PAR2 etc. pra ler parâmetros da pergunta padrão (SX1). Em ExecAuto você precisa setar essas variáveis manualmente antes da chamada:
PRIVATE MV_PAR01 := "001"
PRIVATE MV_PAR02 := "999"
// ...e assim por diante4. Tabela bloqueada por outro processo
Se outra rotina interativa está com a SB1 em RecLock, seu ExecAuto vai falhar com erro de lock. Em integrações de alta concorrência, vale fazer retry com pequeno delay quando o erro é por lock.
5. Versões diferentes de rotinas
Algumas rotinas têm versão "N" (nova): MATA410 vs MATA410N, MATA103 vs MATA103N. A nova geralmente exige campos diferentes ou ordem diferente nos parâmetros. Sempre olhe a documentação da sua versão do Protheus, não copie de blog antigo cego.
Quando NÃO usar ExecAuto
respeita gatilhos+validacoes] Q1 -->|100-10mil em batch| Q2{Tem regra fiscal/contabil?} Q2 -->|Sim| EA2[ExecAuto
mesmo lento] Q2 -->|Nao, dado puro| ETL[ETL: SQL bulk insert
+ recalculo via job] Q1 -->|10mil+ ou import inicial| ETL2[ETL: bulk + reindex
ou DBAccess Tools] EA --> OK[Confiavel] EA2 --> OK ETL --> RISK[Pula validacoes:
assume responsabilidade] ETL2 --> RISK
ExecAuto é a opção certa pra centenas a alguns milhares de registros. Acima disso, o overhead de validações torna o processo lento (cada registro pode levar segundos). Pra carga inicial de massa (50k+ produtos numa migração), o caminho normalmente é: SQL bulk → recálculo de saldos via job batch → reconciliação.
Tabela das rotinas mais usadas
| Função | Módulo | O que faz | Estrutura aDados |
|---|---|---|---|
MATA010 | Estoque | Cadastro de produto (SB1) | 1 array de campos |
MATA020 | Compras | Cadastro de fornecedor (SA2) | 1 array |
MATA030 | Faturamento | Cadastro de cliente (SA1) | 1 array |
MATA103 | Compras | NF de entrada | cabec + itens |
MATA410 | Faturamento | Pedido de venda | cabec + itens |
MATA460 | Faturamento | Faturamento de pedido | cabec + itens |
FINA040 | Financeiro | Inclusão de título (SE1/SE2) | 1 array |
FINA070 | Financeiro | Baixa de título | cabec + array de baixa |
FINA340 | Financeiro | Compensação automática | 1 array |
MATA080 | Estoque | Movimento interno (SD3) | 1 array |
Boilerplate que vale colar no projeto
Em integrações com vários ExecAutos, vale ter uma função wrapper que padroniza tratamento de erro e log:
User Function ATExecAuto(bRotina, aDados, nOpc, cContexto)
Local lOk := .T.
Local cErro := ""
PRIVATE lMsErroAuto := .F.
PRIVATE lMsHelpAuto := .T.
Begin Transaction
Eval(bRotina, aDados, nOpc)
If lMsErroAuto
cErro := GetAutoGrLog()
DisarmTransaction()
lOk := .F.
Break
EndIf
End Transaction
If !lOk
ConOut("[" + cContexto + "] FALHOU: " + Left(cErro, 200))
// grava em log estruturado, manda alerta, etc.
EndIf
Return {lOk, cErro}Uso:
aRet := U_ATExecAuto({|x, y| MATA010(x, y)}, aProduto, 3, "IMP-PRODUTO-" + cCodFornec)Vale o overhead
Tem dev que prefere RecLock direto pra ganhar 10x de performance. Funciona — até o dia que o gatilho da SB1 que recalcula custo médio não roda, ou a integração fiscal não dispara, ou o módulo de estoque entra em desacordo com a contabilidade. ExecAuto existe pra isso não acontecer. Você abre mão de velocidade pra ganhar consistência com todo o resto do ERP.
Pra os casos onde performance é crítica e você sabe exatamente o que está pulando (carga inicial, importação histórica), aí sim vai de SQL ou ETL. Pra integração corrente do dia a dia, ExecAuto é o caminho. Doc oficial dispersa, mas o fundamento está no TDN do MSExecAuto e nas páginas de cada rotina (MATA010, FINA070, etc.).
Comentarios 0