Neste artigo técnico, você aprenderá como criar um Webservice RESTful em ADVPL utilizando recursos nativos do TOTVS Protheus. O objetivo é desenvolver um endpoint para consulta de saldo em estoque, com base em parâmetros como produto, filial e armazém, utilizando GET e integração JSON.

 

Visão geral: Criando um Webservice RESTful no Protheus

Para muitas empresas que utilizam o TOTVS Protheus, disponibilizar informações de forma integrada com outras aplicações é essencial. Este exemplo apresenta uma estrutura funcional de um serviço RESTful que retorna o estoque disponível de um determinado produto.A criação desse tipo de webservice permite o consumo por sistemas terceiros, como e-commerce, ERPs externos, WMS e aplicativos, tornando o ecossistema da empresa mais integrado e eficiente.

##

Estrutura básica do Webservice REST

O serviço é iniciado com uma função principal e a definição do Webservice usando a diretiva `WSRESTFUL`. Veja como é feita essa configuração básica:
#INCLUDE "PROTHEUS.CH"
#INCLUDE "RWMAKE.CH"   
#INCLUDE "TBICONN.CH" 
#INCLUDE "COLORS.CH"
#INCLUDE "RPTDEF.CH"
#INCLUDE "FWPrintSetup.ch"
#Include 'parmtype.ch'
#Include "RestFul.CH"
#Include "TopConn.CH"
#INCLUDE "FILEIO.ch"

User Function FBESTA01()	
Return

WSRESTFUL CONSESTQ DESCRIPTION "Serviço REST para consulta de estoque"
WSDATA SEGKEY  		As String
WSDATA PFILIAL 		As String
WSDATA PRODUTO  	As String
WSDATA ARMAZEM		As String
 
WSMETHOD GET DESCRIPTION "Retorna o estoque do produto informado na URL" 
WSSYNTAX "/CONSESTQ || /CONSESTQ/{PFILIAL}{PRODUTO}{ARMAZEM}" 
END WSRESTFUL

###

Principais pontos dessa estrutura

- WSRESTFUL: Define a classe do Webservice.
- WSDATA: Mapeia os parâmetros que serão recebidos via URL.
- WSMETHOD GET: Define o método HTTP e a rota que será exposta.


##

Tratamento da Requisição GET

O método `GET` implementa a lógica para extrair os parâmetros, consultar o banco de dados e devolver os dados em formato JSON:
WSMETHOD GET WSSERVICE CONSESTQ

Local cPFILIAL   := Self:PFILIAL
Local cPRODUTO   := Self:PRODUTO
Local cARMAZEM   := Self:ARMAZEM

Local aArea		:= GetArea()
Local cJson		:= ""
Local aDados    := {}

If !u_FBSegApp(Self:SEGKEY)
	Return
EndIf

::SetContentType("application/json")

aDados := u_FBESTA02(cPFILIAL, cPRODUTO, cARMAZEM, "SB2.B2_COD")

oObjCom := Objetos():New(aDados)
cJson := FWJsonSerialize(oObjCom)

::SetResponse(cJson)
RestArea(aArea)

Return(.T.)

###

O que está acontecendo aqui?

- Validação da chave de segurança com `u_FBSegApp`.
- Chamada da função `FBESTA02`, responsável pela consulta de dados.
- Serialização dos dados usando `FWJsonSerialize`.
- Resposta JSON formatada enviada ao cliente via `SetResponse`.


##

Consulta ao banco de dados: a função FBESTA02

Esta função é responsável por montar a SQL dinamicamente, respeitando os filtros recebidos via URL.
User Function FBESTA02(cPFILIAL, cPRODUTO, cARMAZEM, cOrderBy)

Local aRet := {}
Local cFiltro := ""

If !Empty(cPFILIAL) 
	cFiltro += " AND SB2.B2_FILIAL = '" + cPFILIAL + "' "
EndIf

If !Empty(cPRODUTO) 
	cFiltro += " AND SB2.B2_COD = '" + UPPER(cPRODUTO) + "' "
EndIf

If !Empty(cARMAZEM)
	cFiltro += " AND SB2.B2_LOCAL = '" + cARMAZEM + "' " 		
EndIf

// Aqui você pode continuar explicando e depois incluir o código completo original como solicitado

##

Implementação Completa

Veja abaixo o código completo implementado conforme descrito ao longo do artigo:
#INCLUDE "PROTHEUS.CH"
#INCLUDE "RWMAKE.CH"   
#INCLUDE "TBICONN.CH" 
#INCLUDE "COLORS.CH"
#INCLUDE "RPTDEF.CH"
#INCLUDE "FWPrintSetup.ch"
#Include 'parmtype.ch'
#Include "RestFul.CH"
#Include "TopConn.CH"
#INCLUDE "FILEIO.ch"

#Define BREAKLINE		Chr(13)+Chr(10)

/*/{Protheus.doc} FBESTA01
Rest em ADVPL
@author FBS Consultoria
@since 11/03/2020
@version 1.0
@type function
/*/
User Function FBESTA01()	

Return

/*/{Protheus.doc} CONSESTQ
Definição da estrutura do webservice
@author FBS Consultoria
@since 04/06/2019
@type class
/*/ 
WSRESTFUL CONSESTQ DESCRIPTION "Serviço REST para consulta de estoque"
WSDATA SEGKEY  		As String //String que vamos receber via URL
WSDATA PFILIAL 		As String //String que vamos receber via URL
WSDATA PRODUTO  	As String //String que vamos receber via URL
WSDATA ARMAZEM		As String //String que vamos receber via URL
 
WSMETHOD GET DESCRIPTION "Retorna o estoque do produto informado na URL" WSSYNTAX "/CONSESTQ || /CONSESTQ/{PFILIAL}{PRODUTO}{ARMAZEM}" //Disponibilizamos um método do tipo GET
 
END WSRESTFUL

WSMETHOD GET WSSERVICE CONSESTQ
//--> 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 cPFILIAL   := Self:PFILIAL
Local cPRODUTO   := Self:PRODUTO
Local cARMAZEM   := Self:ARMAZEM

Local aArea		:= GetArea()
Local cJson		:= ""
Local cQuery    := ""
Local cQueryORA := ""
Local aDados    := {}
Local cRet      := ""
Local cOrderBy  := "SB2.B2_COD"
Local cBD       := AllTrim(GetMV("MV_FBWEB08")) //AQUI PODE DEFINIR MSSSQL OU ORACLE
Local cPag      := "10"
Local cPAGINA   := ""
Local cFiltro   := ""  

	If !u_FBSegApp(Self:SEGKEY)
		Return
	EndIf

	Conout("WS CONSESTQ")
	
	// define o tipo de retorno do método
	::SetContentType("application/json")
	
	aDados := u_FBESTA02(cPFILIAL, cPRODUTO, cARMAZEM, cOrderBy)
	
	oObjCom := Objetos():New(aDados) //Cria um objeto da classe produtos para fazer a serialização na função FWJSONSerialize
	cJson := FWJsonSerialize(oObjCom)
	
	Conout(cJson)
	
	// --> Envia o JSON Gerado para a aplicação Client
	::SetResponse(cJson)
	RestArea(aArea)

	// --> Envia o JSON Gerado para a aplicação Client
	::SetResponse(cRet)
	
	//RpcClearEnv()
	
	RestArea(aArea)
	
Return(.T.)


User Function FBESTA02(cPFILIAL, cPRODUTO, cARMAZEM, cOrderBy)
Local aRet 			:= {}
Local cBD 			:= 'MSSQL'
Default cOrderBy 	:= "SB2.B2_COD"

Conout("FILIAL: " + cPFILIAL + " PRODUTO: " + cPRODUTO + " ARMAZEM: " + cARMAZEM)

cFiltro := ""

If ! Empty(cPFILIAL) 
	cFiltro += " AND SB2.B2_FILIAL = '" + cPFILIAL + "' " + BREAKLINE
EndIf

If ! Empty(cPRODUTO) 
	cFiltro += " AND SB2.B2_COD = '" + UPPER(cPRODUTO) + "'  " + BREAKLINE
EndIf

If ! Empty(cARMAZEM)
	cFiltro += " AND SB2.B2_LOCAL = '" + cARMAZEM + "' " + BREAKLINE  		
EndIf

cQuery := "SELECT B2_QATU " + BREAKLINE
cQuery += "FROM " + RetSQLName("SB2") + " as SB2 WITH(NOLOCK) " + BREAKLINE
cQuery += "WHERE SB2.D_E_L_E_T_ = '' " + BREAKLINE			
cQuery += cFiltro									
cQuery += "ORDER BY " + 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

Conout("CONSESTQ cQuery: " + cQuery)

If Select("QRY_AUX") > 0
	QRY_AUX->(dbCloseArea())
EndIf

//Executando consulta e setando o total da régua
TCQuery cQuery New Alias "QRY_AUX"
Count to nTotal

QRY_AUX->(DbGoTop())

While ! QRY_AUX->(Eof())

	aAdd(aRet, {	QRY_AUX->B2_QATU,; 
					})
					
	QRY_AUX->(dbSkip())	

EndDo

QRY_AUX->(dbCloseArea())
	
Return aRet




##

Conclusão

Ao estruturar um Webservice RESTful em ADVPL, como demonstrado neste artigo, é possível oferecer serviços modernos e seguros para integração de sistemas. Essa abordagem é ideal para conectar o Protheus a aplicativos e ferramentas externas, automatizando rotinas e garantindo agilidade operacional.

Precisa de ajuda com este assunto no Protheus? Fale comigo.