ADVPL / TLPP — Skill de Desenvolvimento Protheus
Documentação de referência: TDN TOTVS (tdn.totvs.com) | Central de Atendimento TOTVS | TOTVS Developers
1. FUNDAMENTOS DA LINGUAGEM
1.1 Tipos de Dados
| Tipo | Prefixo | Exemplos | Notas |
|---|---|---|---|
| Caracter | c | cNome, cCodigo | Strings com aspas duplas |
| Numérico | n | nTotal, nQtd | Int e float unificados |
| Data | d | dEmissao, dVenc | Criada via CToD(), Date() |
| Lógico | l | lAtivo, lOk | .T. / .F. (.Y./.N. também ok) |
| Array | a | aItens, aParam | Índice começa em 1; máx 100.000 elem. |
| Objeto | o | oModel, oGrid | Instâncias de classes |
| Bloco | b | bValid, bWhen | Código armazenado para execução futura |
| NIL | x (genérico) | xRet | Variável não inicializada |
⚠️ ADVPL não é strongly typed — variáveis mudam de tipo dinamicamente. TLPP introduz tipagem forte.
1.2 Notação Húngara (convenção obrigatória)
Local cNome := "João" // c = Caracter
Local nSalario := 2500.00 // n = Numérico
Local dAdmiss := CToD("01/03/25") // d = Data
Local lAtivo := .T. // l = Lógico
Local aItens := {} // a = Array
Local oObj := NIL // o = Objeto
1.3 Escopo de Variáveis
| Comando | Escopo | Boas Práticas |
|---|---|---|
LOCAL | Função atual apenas | ✅ Preferir sempre — não vaza |
STATIC | Arquivo fonte; persiste entre chamadas | ⚠️ Usar para contadores/caches locais |
PRIVATE | Função atual + filhas (herança) | ⚠️ Apenas quando necessário |
PUBLIC | Todo o ambiente (sessão) | 🚫 Evitar; causa efeitos colaterais |
Regra de ouro: declare sempre
LOCALno topo da função; nunca use variáveis não declaradas.
1.4 Limitação de 10 Caracteres (ADVPL clássico)
// ❌ ERRADO — os dois nomes colapsam (mesmos 10 primeiros chars)
Local nTotalGeralAnual := 300
Local nTotalGeralMensal := 100 // MESMO que a anterior!
// ✅ CORRETO — diferenciar nos primeiros caracteres
Local nAnualTotal := 300
Local nMensalTotal := 100
TLPP resolve isso com suporte a nomes longos nativamente.
2. ESTRUTURA DE PROGRAMAS
2.1 Cabeçalho Padrão com Protheus.doc
#INCLUDE "PROTHEUS.CH"
#INCLUDE "TOTVS.CH"
/*/{Protheus.doc} MinhaFuncao
Breve descrição do que a função faz.
@type Function
@author Seu Nome
@since 2025-01-01
@version 1.0
@param cCodigo, Caracter, Código do produto
@param nQtd, Numérico, Quantidade a processar
@return lOk, Lógico, .T. se processou com sucesso
@see MATA010, SB1
/*/
User Function MinhaFuncao( cCodigo, nQtd )
Local lOk := .T.
// ... implementação
Return lOk
2.2 Tipos de Função
// Ponto de entrada — chamada pelo ERP (nome curto, máx 10 chars)
User Function MT010INC() // PE após inclusão em MATA010
// PARAMIXB traz os parâmetros do sistema
Local aParam := PARAMIXB
Return .T.
// Função auxiliar local ao arquivo
Static Function ValidaDados( cCod, nVal )
Return ( !Empty(cCod) .AND. nVal > 0 )
// Função pública (acessível via ExecBlock/CallFunc)
Function U_HelperFn()
Return NIL
3. BANCO DE DADOS — PADRÕES E ARMADILHAS
3.1 Acesso via ISAM (modo ERP)
// ✅ PADRÃO ERP: usar RecLock + MsUnlock + BeginTran/EndTran
BeginTran()
If RecLock("SB1", .F.) // .F. = alteração, .T. = inclusão
SB1->B1_DESC := "Novo Produto"
MsUnlock()
EndIf
EndTran()
// Para inclusão de novo registro:
BeginTran()
RecLock("SB1", .T.) // .T. = append/novo
SB1->B1_COD := "000001"
SB1->B1_DESC := "Produto Teste"
MsUnlock()
EndTran()
🚫 NUNCA misturar funções de Framework (
RecLock,MsUnlock,BeginTran) com funções de baixo nível (DBAppend,DBRUnlock,TCCommit) no mesmo processo.
3.2 Acesso via SQL (TCQuery / Embedded SQL)
Local cQuery := ""
Local cAliasQ := GetNextAlias()
cQuery := "SELECT B1_COD, B1_DESC "
cQuery += " FROM " + RetSqlName("SB1") + " SB1 "
cQuery += " WHERE B1_FILIAL = '" + xFilial("SB1") + "' "
cQuery += " AND SB1.D_E_L_E_T_ = ' ' " // Filtro de exclusão lógica
cQuery += " ORDER BY B1_COD"
TCQuery( cQuery, .T., cAliasQ )
If !(cAliasQ)->(EOF())
While !(cAliasQ)->(EOF())
ConOut("Produto: " + (cAliasQ)->B1_COD + " - " + (cAliasQ)->B1_DESC)
(cAliasQ)->(DBSkip())
EndDo
EndIf
(cAliasQ)->(DBCloseArea())
✅ Sempre filtrar
D_E_L_E_T_ = ' '(registros não excluídos logicamente). ✅ Sempre usarxFilial("ALIAS")no filtro de filial em consultas SQL. ✅ UsarRetSqlName()para obter o nome real da tabela no banco.
3.3 Posicionamento em Tabelas
// Busca com índice (preferível para performance)
If MsSeek( xFilial("SA1") + cCodCli, "SA1", "1" )
// Registro encontrado no índice 1 da SA1
cNomeCli := SA1->A1_NOME
EndIf
// Busca sem índice (evitar em volumes grandes)
SA1->(DBSetOrder(1))
If SA1->(DbSeek( xFilial("SA1") + cCodCli ))
cNomeCli := SA1->A1_NOME
EndIf
4. PONTOS DE ENTRADA (PE)
4.1 PE Convencional
#INCLUDE "PROTHEUS.CH"
/*/{Protheus.doc} MT010INC
Ponto de entrada executado após inclusão de produto em MATA010.
@type User Function
@author Dev
@since 2025-01-01
@param Nenhum (usa PARAMIXB)
@return NIL
/*/
User Function MT010INC()
Local aParam := PARAMIXB
// aParam[1] = código do produto incluído (conforme doc TDN)
ConOut("Produto incluído: " + SB1->B1_COD)
Return NIL
4.2 PE em MVC (padrão moderno — único PE por Model)
#INCLUDE "PROTHEUS.CH"
#INCLUDE "FWMVCDEF.CH"
/*/{Protheus.doc} GPEA010
Ponto de entrada único para fonte MVC GPEA010.
O ID do PE deve ser igual ao ID do Model de dados.
@type User Function
@param aParam, Array, Parâmetros via PARAMIXB
@return xRet, Variado, Depende do IDPonto executado
/*/
User Function GPEA010()
Local aParam := PARAMIXB
Local xRet := NIL
Local cIDPonto := ""
If ValType(aParam) == "A" .AND. Len(aParam) >= 1
cIDPonto := aParam[1] // Identifica qual momento do MVC chamou o PE
EndIf
Do Case
Case cIDPonto == "MODELINIT"
// Inicialização do model
xRet := .T.
Case cIDPonto == "MODELCOMMITTTS"
// Após gravação — dentro da transação
xRet := .T.
Case cIDPonto == "MODELCANCEL"
// Usuário cancelou
xRet := .F.
Case cIDPonto == "VIEWINIT"
// View foi inicializada
EndCase
Return xRet
⚠️ Em MVC: o nome do
User Function(PE) deve ser igual ao ID do Model, mas nunca igual ao nome do arquivo fonte.
5. DESENVOLVIMENTO MVC
5.1 Estrutura de um Fonte MVC Completo
#INCLUDE "PROTHEUS.CH"
#INCLUDE "FWMVCDEF.CH"
#INCLUDE "TOTVS.CH"
// ---- CONTROLLER ----
User Function MYFONT01()
Local aArea := GetArea()
Local oBrowse
oBrowse := FwMBrowse():New()
oBrowse:SetAlias("SB1")
oBrowse:SetDescription("Cadastro de Produtos")
oBrowse:Activate()
RestArea(aArea)
Return NIL
// ---- MODEL ----
Static Function ModelDef()
Local oModel := MPFormModel():New("MYFONT01MDL", /*bPre*/, /*bPost*/, /*bCommit*/, /*bCancel*/)
Local oStruct := FWFormStruct(1, "SB1")
oModel:AddFields("SB1MASTER", /*parent*/, oStruct)
oModel:SetPrimaryKey({"B1_COD"})
Return oModel
// ---- VIEW ----
Static Function ViewDef()
Local oView := FWFormV