Como criar um WebService REST para Notas Fiscais no Protheus usando ADVPL
Aprenda como estruturar um WebService RESTful em ADVPL para consultar notas fiscais no Protheus, utilizando boas práticas de performance, segurança e organização de código.
Introdução
Com a crescente demanda por integrações em tempo real com ERPs, criar WebServices RESTful dentro do TOTVS Protheus se tornou uma habilidade essencial. Neste artigo, vamos mostrar como funciona uma implementação prática para consulta de notas fiscais via REST, utilizando recursos da linguagem ADVPL e do framework do Protheus.
Definição da Função Inicial
#INCLUDE "PROTHEUS.CH" #INCLUDE "TBICONN.CH" ... User Function FBNOT01() Return
Essa é a função de inicialização que pode ser usada para testes ou registros no dicionário de dados.
Definindo o WebService: NOTASFISCAIS
O bloco abaixo cria a estrutura do WebService com os parâmetros que serão recebidos via URL:
WSRESTFUL NOTASFISCAIS DESCRIPTION "Serviço REST para manipulação de notas fiscais" WSDATA SEGKEY As String WSDATA VENDEDOR As String ... WSMETHOD GET DESCRIPTION "Retorna os cabeçalhos das notas fiscais"
Implementando o Método GET
Dentro do método GET, é feita a leitura dos parâmetros recebidos, montagem do filtro SQL e execução da query paginada. Veja um trecho do início da função:
WSMETHOD GET WSSERVICE NOTASFISCAIS
Local cNUMNOTADE := Self:NUMNOTADE
Local cNUMNOTAATE := Self:NUMNOTAATE
...
::SetContentType("application/json")
Exemplo de Montagem de Filtro
If !Empty(cNUMNOTADE) .AND. !Empty(cNUMNOTAATE) cFiltro += "AND SF2.F2_DOC BETWEEN '" + cNUMNOTADE + "' AND '" + cNUMNOTAATE + "' " + BREAKLINE EndIf
Isso permite consultas flexíveis, com filtros por número de nota, série, emissão, cliente, entre outros.
Consulta SQL Otimizada com Join
cQuery := "SELECT C5_FILIAL, F2_DOC, F2_SERIE, C5_NUM, ..."
cQuery += "FROM " + RetSQLName("SF2") + " SF2 " + ...
cQuery += "INNER JOIN " + RetSQLName("SC5") + " C5 ..."
O cQuery é montado dinamicamente, considerando banco Oracle ou MSSQL e utilizando o recurso de paginação.
Serialização e Retorno JSON
Ao final, os dados consultados são transformados em JSON para envio ao cliente.
oObjCom := Objetos():New(aDados) cJson := FWJsonSerialize(oObjCom) ::SetResponse(cJson)
Código Completo
#INCLUDE "PROTHEUS.CH"
#INCLUDE "TBICONN.CH"
#INCLUDE "COLORS.CH"
#INCLUDE "RPTDEF.CH"
#INCLUDE "FWPrintSetup.ch"
#Include 'parmtype.ch'
#Include "RestFul.CH"
#Include "TopConn.CH"
#Define BREAKLINE Chr(13)+Chr(10)
//5152
//6152
/*/{Protheus.doc} EREST_01
Ensinando Rest em ADVPL
@author Fernando Bueno
@since 13/04/2017
@version undefined
@type function
/*/
User Function FBNOT01()
Return
/*/{Protheus.doc} NOTASFISCAIS
Definição da estrutura do webservice
@author Fernando Bueno
@since 13/04/2017
@type class
/*/
WSRESTFUL NOTASFISCAIS DESCRIPTION "Serviço REST para manipulação de notas fiscais"
WSDATA SEGKEY As String //String que vamos receber via URL
WSDATA VENDEDOR As String //String que vamos receber via URL // Gabriel Soares 04/03/2019 - Colocado parametro para Vendedor
WSDATA CLIDE As String //String que vamos receber via URL // Gabriel Soares 04/03/2019 - Colocado parametro Cliende De
WSDATA CLIATE As String //String que vamos receber via URL // Gabriel Soares 04/03/2019 - Colocado parametro Cliende Até
WSDATA CLIENTE As String //String que vamos receber via URL
WSDATA LOJA As String //String que vamos receber via URL
WSDATA CLIENTES As String //String que vamos receber via URL
WSDATA NUMNOTADE As String //String que vamos receber via URL
WSDATA NUMNOTAATE As String //String que vamos receber via URL
WSDATA SERIEDE As String //String que vamos receber via URL
WSDATA SERIEATE As String //String que vamos receber via URL
WSDATA EMISSAODE As String //String que vamos receber via URL
WSDATA EMISSAOATE As String //String que vamos receber via URL
WSDATA ULTIMOS As String //String que vamos receber via URL
WSDATA PAGINA As String //String que vamos receber via URL
WSDATA ORDER As String //String que vamos receber via URL
WSDATA DIR As String //String que vamos receber via URL
WSDATA PAGINATION As String //String que vamos receber via URL
WSMETHOD GET DESCRIPTION "Retorna os cabeçalhos dos pedidos de venda informado na URL" WSSYNTAX "/NOTASFISCAIS || /NOTASFISCAIS/{NUMNOTADE}{NUMNOTAATE}{SERIEDE}{SERIEATE}{EMISSAODE}{EMISSAOATE}{ULTIMOS}{PAGINA}" //Disponibilizamos um método do tipo GET
END WSRESTFUL
/*/{Protheus.doc} GET
Processa as informações e retorna o json
@author Fernando Bueno
@since 13/04/2017
@version undefined
@param oSelf, object, Objeto contendo dados da requisição efetuada pelo cliente, tais como:
- Parâmetros querystring (parâmetros informado via URL)
- Objeto JSON caso o requisição seja efetuada via Request Post
- Header da requisição
- entre outras ...
@type Method
/*/
WSMETHOD GET WSSERVICE NOTASFISCAIS
//--> Recuperamos o produto informado via URL
//--> Podemos fazer dessa forma ou utilizando o atributo ::aUrlParms, que é um array com os parâmetros recebidos via URL (QueryString)
Local cNUMNOTADE := Self:NUMNOTADE
Local cNUMNOTAATE := Self:NUMNOTAATE
Local cSERIEDE := Self:SERIEDE
Local cSERIEATE := Self:SERIEATE
Local cEMISSAODE := Self:EMISSAODE
Local cEMISSAOATE := Self:EMISSAOATE
Local cULTIMOS := Self:ULTIMOS
Local cPAGINA := Self:PAGINA
Local cORDER := Self:ORDER
Local cDIR := Self:DIR
Local cPAGINATION := Self:PAGINATION
Local cPag := "10"
Local aArea := GetArea()
Local cJson := ""
Local cQuery := ""
Local aDados := {}
Local nTot := 0
Local cBD := AllTrim(GetMV("MV_FBWEB08"))
If !u_FBSegApp(Self:SEGKEY)
Return
EndIf
// define o tipo de retorno do método
::SetContentType("application/json")
cFiltro := ""
If !Empty(cNUMNOTADE) .AND. !Empty(cNUMNOTAATE)
cFiltro += "AND SF2.F2_DOC BETWEEN '" + cNUMNOTADE + "' AND '" + cNUMNOTAATE + "' " + BREAKLINE
EndIf
If !Empty(cSERIEDE) .AND. !Empty(cSERIEATE)
cFiltro += "AND SF2.F2_SERIE BETWEEN '" + cSERIEDE + "' AND '" + cSERIEATE + "' " + BREAKLINE
EndIf
If !Empty(cULTIMOS)
cFiltro += "AND SF2.F2_EMISSAO >= '" + DTOS((DATE() - Val(cULTIMOS))) + "' " + BREAKLINE
ElseIf !Empty(cEMISSAODE) .AND. !Empty(cEMISSAOATE)
cFiltro += "AND SF2.F2_EMISSAO BETWEEN '" + DTOS(CTOD(cEMISSAODE)) + "' AND '" + DTOS(CTOD(cEMISSAOATE)) + "' " + BREAKLINE
EndIf
if ! Empty(Self:VENDEDOR) // Gabriel Soares 04/03/2019 - Colocado teste para Vendedor
cFiltro += u_FBFILTER("NOTASFISCAIS", Self:VENDEDOR)
endif
if ! Empty(Self:CLIENTE) //.AND. ! Empty(Self:LOJA) // Gabriel Soares 04/03/2019 - Colocado teste para Cliente
cFiltro += u_FBFILTER("NOTASFISCAIS", Self:CLIENTE, "CC", Self:CLIENTES)
endif
if ! Empty(Self:CLIDE) // Gabriel Soares 04/03/2019 - Colocado teste para Cliente De
cFiltro += "AND SF2.F2_CLIENTE >= '" + Self:CLIDE + "' " + BREAKLINE
endif
if ! Empty(Self:CLIATE) // Gabriel Soares 04/03/2019 - Colocado teste para Cliente Até
cFiltro += "AND SF2.F2_CLIENTE <= '" + Self:CLIATE + "' " + BREAKLINE
endif
If cPAGINATION <> 'OFF'
//--NOTASFISCAIS
//--num_nota, ser_nota, num_carga, cliente, total_peso, total_merc, status, motivo, sequencia
cQuery := "SELECT COUNT(C5_FILIAL) TOTALREG " + BREAKLINE
cQuery += " FROM " + RetSQLName("SF2") + " SF2 " + IIF(cBD == 'MSSQL', "WITH (NOLOCK)", "") + " " + BREAKLINE
cQuery += " INNER JOIN " + RetSQLName("SC5") + " C5 " + IIF(cBD == 'MSSQL', "WITH (NOLOCK)", "") + " ON ( F2_FILIAL = C5_FILIAL AND F2_DOC = C5_NOTA AND F2_SERIE = C5_SERIE AND SF2.D_E_L_E_T_ = ' ' ) " + BREAKLINE
cQuery += " INNER JOIN " + RetSQLName("SC6") + " C6 " + IIF(cBD == 'MSSQL', "WITH (NOLOCK)", "") + " ON ( C5_FILIAL = C6_FILIAL AND C5_NUM = C6_NUM AND C6.D_E_L_E_T_ = ' ') " + BREAKLINE
cQuery += " INNER JOIN " + RetSQLName("SB1") + " B1 " + IIF(cBD == 'MSSQL', "WITH (NOLOCK)", "") + " ON ( C6_PRODUTO = B1_COD AND B1.D_E_L_E_T_ = ' ') " + BREAKLINE
cQuery += " INNER JOIN " + RetSQLName("SA1") + " A1 " + IIF(cBD == 'MSSQL', "WITH (NOLOCK)", "") + " ON ( C5_CLIENTE = A1_COD AND C5_LOJACLI = A1_LOJA AND A1.D_E_L_E_T_ = ' ') " + BREAKLINE
cQuery += "WHERE C5.D_E_L_E_T_ = ' ' " + BREAKLINE
cQuery += cFiltro
cQuery += "GROUP BY C5_FILIAL " + BREAKLINE
u_FBCONS("[FBWEB][NOTASFISCAIS] - " + cQuery)
//Executando consulta e setando o total da régua
TCQuery cQuery New Alias "QRY_CNT"
If !QRY_CNT->(Eof())
nTot := QRY_CNT->TOTALREG
u_FBCONS("NOTASFISCAIS nTot: " + cValToChar(nTot))
EndIf
QRY_CNT->(dbCloseArea())
EndIf
If !Empty(cPAGINA)
If cPAGINA == '1'
cPAGINA := ''
Else
cLim := cValToChar(Val(cPag)*(Val(cPAGINA)-1))
If Val(cLim) > nTot
cLim := cValToChar(Val(cLim) - Val(cPag))
EndIf
EndIf
Else
cLim := cPag
EndIf
//--NOTASFISCAIS
//--num_nota, ser_nota, num_carga, cliente, total_peso, total_merc, status, motivo, sequencia
cQuery := "SELECT " + IIF(cBD == 'MSSQL', "TOP " + cPag, "") + " C5_FILIAL, F2_DOC, F2_SERIE, C5_NUM, F2_EMISSAO, F2_FIMP, SUM(C6_VALOR) C6_VALOR, SUM(B1_PESO) B1_PESO, " + BREAKLINE
cQuery += " A1_COD, A1_LOJA, A1_NOME, A1_PESSOA, A1_CGC, A1_END, A1_BAIRRO, A1_MUN, A1_EST, A1_TEL, A1_EMAIL, F2_CHVNFE, C6_NUM " + BREAKLINE
cQuery += " FROM " + RetSQLName("SF2") + " SF2 " + IIF(cBD == 'MSSQL', "WITH (NOLOCK)", "") + " " + BREAKLINE
cQuery += " INNER JOIN " + RetSQLName("SC5") + " C5 " + IIF(cBD == 'MSSQL', "WITH (NOLOCK)", "") + " ON ( F2_FILIAL = C5_FILIAL AND F2_DOC = C5_NOTA AND F2_SERIE = C5_SERIE AND SF2.D_E_L_E_T_ = ' ' ) " + BREAKLINE
cQuery += " INNER JOIN " + RetSQLName("SC6") + " C6 " + IIF(cBD == 'MSSQL', "WITH (NOLOCK)", "") + " ON ( C5_FILIAL = C6_FILIAL AND C5_NUM = C6_NUM AND C6.D_E_L_E_T_ = ' ') " + BREAKLINE
cQuery += " INNER JOIN " + RetSQLName("SB1") + " B1 " + IIF(cBD == 'MSSQL', "WITH (NOLOCK)", "") + " ON ( C6_PRODUTO = B1_COD AND B1.D_E_L_E_T_ = ' ') " + BREAKLINE
cQuery += " INNER JOIN " + RetSQLName("SA1") + " A1 " + IIF(cBD == 'MSSQL', "WITH (NOLOCK)", "") + " ON ( C5_CLIENTE = A1_COD AND C5_LOJACLI = A1_LOJA AND A1.D_E_L_E_T_ = ' ') " + BREAKLINE
cQuery += "WHERE C5.D_E_L_E_T_ = ' ' " + BREAKLINE
If !Empty(cPAGINA) .AND. cBD == 'MSSQL'
cQuery += "AND SF2.R_E_C_N_O_ NOT IN (SELECT TOP " + cLim + " F2.R_E_C_N_O_ FROM " + RetSQLName("SF2") + " F2 WHERE F2.D_E_L_E_T_ = ' ' " + cFiltro + " " + u_FBORDERBY("NOTASFISCAIS", cORDER, cDIR) + ") " + BREAKLINE
EndIf
cQuery += cFiltro
cQuery += "GROUP BY C5_FILIAL, F2_DOC, F2_SERIE, C5_NUM, F2_EMISSAO, F2_FIMP, A1_COD, A1_LOJA, A1_NOME, A1_PESSOA, A1_CGC, A1_END, A1_BAIRRO, A1_MUN, A1_EST, A1_TEL, A1_EMAIL, F2_CHVNFE, C6_NUM " + BREAKLINE
cOrderBy := u_FBORDERBY("NOTASFISCAIS", cORDER, cDIR)
cQuery += cOrderBy
If cBD == 'ORACLE'
cQueryORA := " SELECT ROWNUM + (" + cPag + " * (PAGINA - 1)) AS LINNUM " + BREAKLINE
cQueryORA += " ,B.* " + BREAKLINE
cQueryORA += " FROM (SELECT COUNT(*) OVER() AS TOTALREG " + BREAKLINE
cQueryORA += " ,TRUNC((ROW_NUMBER() OVER(" + cOrderBy + ") - 1) / " + cPag + ") + 1 AS PAGINA " + BREAKLINE
cQueryORA += " ,TMP.* " + BREAKLINE
cQueryORA += " FROM ( " + BREAKLINE
cQueryORA += cQuery
cQueryORA += ") TMP " + BREAKLINE
cQueryORA += ") B " + BREAKLINE
If !Empty(cPAGINA)
cQueryORA += " WHERE PAGINA = " + cPAGINA + " " + BREAKLINE
EndIf
cQuery := cQueryORA
EndIf
u_FBCONS("[FBWEB][NOTASFISCAIS] - " + cQuery)
//Executando consulta e setando o total da régua
TCQuery cQuery New Alias "QRY_AUX"
Count to nTotal
QRY_AUX->(DbGoTop())
u_FBCONS("NOTASFISCAIS nTot2: " + cValToChar(nTot))
While ! QRY_AUX->(Eof())
aAdd(aDados, { QRY_AUX->C5_FILIAL,;
QRY_AUX->F2_DOC,;
QRY_AUX->F2_SERIE,;
QRY_AUX->C5_NUM,;
QRY_AUX->F2_EMISSAO,;
QRY_AUX->C6_VALOR,;
QRY_AUX->B1_PESO,;
QRY_AUX->A1_COD,;
QRY_AUX->A1_LOJA,;
QRY_AUX->A1_NOME,;
QRY_AUX->F2_CHVNFE,;
cValToChar(nTot),;
QRY_AUX->F2_FIMP,;
QRY_AUX->A1_PESSOA,;
QRY_AUX->A1_CGC,;
QRY_AUX->A1_END,;
QRY_AUX->A1_BAIRRO,;
QRY_AUX->A1_MUN,;
QRY_AUX->A1_EST,;
QRY_AUX->A1_TEL,;
QRY_AUX->A1_EMAIL;
})
QRY_AUX->(dbSkip())
EndDo
QRY_AUX->(dbCloseArea())
oObjCom := Objetos():New(aDados) //Cria um objeto da classe produtos para fazer a serialização na função FWJSONSerialize
//::SetContentType("application/json")
// --> Transforma o objeto de produtos em uma string json
//cJson := "receive([" + FWJsonSerialize(oObjCom) + "])"
cJson := FWJsonSerialize(oObjCom)
//u_FBCONS(cJson)
// --> Envia o JSON Gerado para a aplicação Client
::SetResponse(cJson)
RestArea(aArea)
Return(.T.)
Conclusão
Esse exemplo prático mostra como estruturar um serviço REST no Protheus para consulta de notas fiscais com segurança, performance e manutenção facilitada. Ele é ideal para integrações com ERPs, BI, ferramentas de automação ou portais de clientes.
