O Que Você Vai Aprender
- Como estruturar um WebService REST no Protheus com
WSRESTFUL - Como receber parâmetros via URL (GET)
- Como montar dinamicamente uma query SQL com filtros
- Como retornar os resultados em formato JSON
- Como aplicar paginação e ordenação no resultado
Parâmetros Recebidos via URL
O serviço aceita diversos parâmetros GET como filtros:
| Parâmetro | Descrição |
|---|---|
| PRODUTO | Código do produto |
| FORNECE | Código do fornecedor |
| CONTATO | Código do contato |
| EMISSAO | Data de emissão |
| TES | Código de TES |
| CLVL | Faixa de cliente inicial |
| QUANT | Faixa de cliente final |
| PAGINA | Página atual (paginação) |
| ORDER | Campo de ordenação |
| DIR | Direção da ordenação |
| LIMIT | Quantidade de registros |
| PAGINATION | ON/OFF para controle |
| SEGKEY | Chave de segurança |
Criação da Classe RESTful
WSRESTFUL PEDIDODECOMPRAS DESCRIPTION "Serviço REST para manipulação de pedidos de compras" WSDATA PRODUTO As String WSDATA FORNECE As String ... WSMETHOD GET DESCRIPTION "Retorna os cabeçalhos dos pedidos de compras com base nos filtros" WSSYNTAX "/PEDIDODECOMPRAS" END WSRESTFUL
Validação de Segurança
Logo no início do método GET, o código valida uma chave de segurança:
If !u_FBSegApp(Self:SEGKEY)
Return
EndIf
Montagem Dinâmica da Query
Os filtros recebidos via GET são utilizados para montar dinamicamente a cláusula WHERE da SQL:
If !Empty(Self:PRODUTO)
cFiltro += "AND SC7.C7_PRODUTO = '" + Self:PRODUTO + "' " + BREAKLINE
EndIf
Consulta SQL com Paginação
A consulta principal utiliza JOIN entre SC7, SA2, SD1 e SF1. A paginação é feita com TOP (MSSQL) ou ROWNUM (Oracle):
SELECT C7_NUM, C7_CONTATO, ... FROM SC7 INNER JOIN SA2 ON ... ... WHERE SC7.D_E_L_E_T_ = ' '
Resposta em JSON
Os resultados são serializados e enviados como JSON:
oObjCom := Objetos():New(aDados) cJson := FWJsonSerialize(oObjCom) ::SetResponse(cJson)
Exemplo de Requisição GET
GET /rest/PEDIDODECOMPRAS?FORNECE=000123&PAGINA=1&PAGINATION=ON&SEGKEY=XYZ123
Exemplo de Retorno JSON
[
{
"C7_NUM": "000123",
"A2_NOME": "Fornecedor XYZ",
"TOTAL_PRODUTO": 120.00,
...
}
]
Código Completo
#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"
#INCLUDE "PRTOPDEF.CH"
#Define BREAKLINE Chr(13)+Chr(10)
/*/{Protheus.doc} FBTIT01
Ensinando Rest em ADVPL
@author Fernando Bueno
@since 13/04/2017
@version undefined
@type function
/*/
User Function FBCOMP01()
Return
/*/{Protheus.doc} PEDIDODECOMPRAS
Definição da estrutura do webservice
@author Fernando Bueno
@since 13/04/2017
@type class
/*/
WSRESTFUL PEDIDODECOMPRAS DESCRIPTION "Serviço REST para manipulação de notas fiscais"
WSDATA PRODUTO As String //String que vamos receber via URL
WSDATA SEGKEY As String //String que vamos receber via URL
WSDATA TIPO As String //String que vamos receber via URL
WSDATA FORNECE As String //String que vamos receber via URL
WSDATA CONTATO As String //String que vamos receber via URL
WSDATA QUANT As String //String que vamos receber via URL
WSDATA EMISSAO As String //String que vamos receber via URL
WSDATA QUJE As String //String que vamos receber via URL
WSDATA TES As String //String que vamos receber via URL
WSDATA CLVL As String //String que vamos receber via URL
WSDATA PAGINA As String //String que vamos receber via URL
WSDATA DIR As String //String que vamos receber via URL
WSDATA ORDER As String //String que vamos receber via URL
WSDATA NUMLIQ As String //String que vamos receber via URL
WSDATA PAGINATION As String //String que vamos receber via URL
WSDATA LIMIT As String //String que vamos receber via URL
WSMETHOD GET DESCRIPTION "Retorna os cabeçalhos dos pedidos de venda informado na URL" WSSYNTAX "/PEDIDODECOMPRAS || /PEDIDODECOMPRAS/{TIPO}{FORNECE}{CONTATO}{QUANT}{EMISSAO}{QUJE}{TES}{CLVL}{PAGINA}{DIR}{NUMLIQ}" //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
@type Method
/*/
WSMETHOD GET WSSERVICE PEDIDODECOMPRAS
Local cPROD := Self:PRODUTO
Local cFORNECE := Self:FORNECE
Local cCONTATO := Self:CONTATO
Local cEMISSAO := Self:EMISSAO
Local cQUJE := Self:QUJE
Local cTES := Self:TES
Local cCLVL := Self:CLVL
Local cPAGINA := Self:PAGINA
Local cDIR := Self:DIR
Local cORDER := Self:ORDER
Local cPag := "10"
Local cPAGINATION := Self:PAGINATION
Local aArea := GetArea()
Local cJson := ""
Local cQuery := ""
Local aDados := {}
Local nTot := 0
Local cBD := AllTrim(GetMV("MV_FBWEB08"))
Private cLIMIT := Self:LIMIT
If !u_FBSegApp(Self:SEGKEY)
Return
EndIf
// define o tipo de retorno do método
::SetContentType("application/json")
cFiltro := ""
If !Empty(cPROD)
cFiltro += "AND SC7.C7_PRODUTO = '" + cPROD + "' " + BREAKLINE
EndIf
If !Empty(cFORNECE)
cFiltro += "AND SC7.C7_FORNECE = '" + cFORNECE + "' " + BREAKLINE
EndIf
If !Empty(cCONTATO)
cFiltro += "AND SC7.C7_CONTATO = '" + cCONTATO + "' " + BREAKLINE
EndIf
If !Empty(cEMISSAO)
cFiltro += "AND SC7.C7_EMISSAO = '" + cEMISSAO + "' " + BREAKLINE
EndIf
If !Empty(cQUJE)
cFiltro += "AND SC7.C7_QUJE = '" + cQUJE + "' " + BREAKLINE
EndIf
If !Empty(cTES)
cFiltro += "AND SC7.C7_TES = '" + cTES + "' " + BREAKLINE
EndIf
If !Empty(cCLVL)
cFiltro += "AND SC7.C7_CLVL = '" + cCLVL + "' " + BREAKLINE
EndIf
If Self:TIPO == "N"
//conout("hdsjkadhshadkjshdhkashkdshakdhsakhkdshaskdhkashdksahdkashdkashhdk")
if ! Empty(Self:FORNECE) // Gabriel Soares 04/03/2019 - Colocado teste para Fornecedor
cFiltro += u_FBFILTER("PEDIDODECOMPRAS", Self:FORNECE)
endif
conout("PEDIDODECOMPRAS:clientes1")
if ! Empty(Self:CONTATO) /* .AND. !Empty(Self:LOJA)*/ // Gabriel Soares 04/03/2019 - Colocado teste para Contato
conout("PEDIDODECOMPRAS:clientes2")
cFiltro += u_FBFILTER("PEDIDODECOMPRAS", Self:CONTATO, "CC", Self:CONTATOS)
endif
EndIf
if ! Empty(Self:CLVL) // Gabriel Soares 04/03/2019 - Colocado teste para Cliente Nivel
cFiltro += "AND SC7.C7_CLIENTE >= '" + Self:CLVL + "' " + BREAKLINE
endif
if ! Empty(Self:QUANT) // Gabriel Soares 04/03/2019 - Colocado teste para Quantidade
cFiltro += "AND SC7.C7_CLIENTE <= '" + Self:QUANT + "' " + BREAKLINE
endif
If !Empty(cEMISSAO)
cFiltro += "AND SC7.C7_TIPO = '" + cEMISSAO + "' " + BREAKLINE
If cEMISSAO == "NCC"
cFiltro += " AND SC7.C7_SALDO > 0 " + BREAKLINE
EndIf
EndIf
//--PEDIDODECOMPRAS
//--num_nota, ser_nota, num_carga, cliente, total_peso, total_merc, status, motivo, sequencia
cQuery := " SELECT COUNT(DISTINCT(C7_NUM)) TOTALREG " + BREAKLINE
cQuery += " FROM " + RetSQLName("SC7") + " SC7 " + IIF(cBD == 'MSSQL', "WITH (NOLOCK)", "") + " " + BREAKLINE
cQuery += " INNER JOIN " + RetSQLName("SA2") + " A2 " + IIF(cBD == 'MSSQL', "WITH (NOLOCK)", "") + " ON ( C7_FORNECE = A2_COD AND C7_LOJA = A2_LOJA AND A2.D_E_L_E_T_ = ' ') " + BREAKLINE
cQuery += " INNER JOIN " + RetSQLName("SD1") + " D1 " + IIF(cBD == 'MSSQL', "WITH (NOLOCK)", "") + " ON (C7_NUM = D1_PEDIDO AND D1.D_E_L_E_T_ = ' ') " + BREAKLINE
cQuery += " INNER JOIN " + RetSQLName("SF1") + " F1 " + IIF(cBD == 'MSSQL', "WITH (NOLOCK)", "") + " ON (D1_FILIAL = F1_FILIAL AND D1_DOC = F1_DOC AND D1_SERIE = F1_SERIE AND D1_FORNECE = F1_FORNECE AND F1.D_E_L_E_T_ = ' ') " + BREAKLINE
cQuery += " WHERE SC7.D_E_L_E_T_ = ' ' " + BREAKLINE
cQuery += " AND 1 = 1 "
cQuery += cFiltro
//cQuery += "GROUP BY C7_NUM"
//cQuery += "GROUP BY C7_FILIAL " + BREAKLINE
u_FBCONS("CONTADOR PEDIDO = " + cQuery)
//Executando consulta e setando o total da régua
TCQuery cQuery New Alias "QRY_CNT"
If !QRY_CNT->(Eof())
nTot := QRY_CNT->TOTALREG
EndIf
QRY_CNT->(dbCloseArea())
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
conout("cPAGINATION:" + cPAGINATION)
cLimitCond := IIF(Empty(cLIMIT), "", "TOP " + cLIMIT)
//--PEDIDODECOMPRAS
//--num_nota, ser_nota, num_carga, cliente, total_peso, total_merc, status, motivo, sequencia
cQuery := "SELECT C7_NUM, C7_CONTATO, C7_TIPO, MAX(C7_EMISSAO) C7_EMISSAO, A2_COD, A2_LOJA, A2_NOME, " + CRLF
cQuery += "A2_TPESSOA, A2_CGC, A2_END, A2_BAIRRO, A2_MUN, A2_EST, " + CRLF
cQuery += "A2_TEL, A2_EMAIL, SUM(D1_QUANT) TOTAL_PRODUTO, MAX(D1_EMISSAO) D1_EMISSAO, F1_STATUS " + CRLF
cQuery += "FROM " + RetSQLName("SC7") + " SC7 WITH (NOLOCK) " + CRLF
cQuery += "INNER JOIN " + RetSQLName("SA2") + " A2 WITH (NOLOCK) ON (C7_FORNECE = A2_COD AND C7_LOJA = A2_LOJA AND A2.D_E_L_E_T_ = ' ') " + CRLF
cQuery += "INNER JOIN " + RetSQLName("SD1") + " D1 WITH (NOLOCK) ON (C7_FILIAL = D1_FILIAL AND C7_NUM = D1_PEDIDO AND D1.D_E_L_E_T_ = ' ') " + CRLF
cQuery += "INNER JOIN " + RetSQLName("SF1") + " F1 WITH (NOLOCK) ON (D1_FILIAL = F1_FILIAL AND D1_DOC = F1_DOC AND D1_SERIE = F1_SERIE AND D1_FORNECE = F1_FORNECE AND F1.D_E_L_E_T_ = ' ') " + CRLF
cQuery += "WHERE SC7.D_E_L_E_T_ = ' ' " + CRLF
If cPAGINATION <> 'OFF' .AND. !Empty(cPAGINA) .AND. cBD == 'MSSQL'
cQuery += "AND SC7.R_E_C_N_O_ NOT IN (SELECT TOP " + cLim + " SC7.R_E_C_N_O_ FROM " + RetSQLName("SC7") + " SC7 " + BREAKLINE
cQuery += "WHERE SC7.D_E_L_E_T_ = ' ' " + cFiltro + " " + u_FBORDERBY("PEDIDODECOMPRAS", cORDER, cDIR) + ") " + BREAKLINE
EndIf
cQuery += cFiltro + BREAKLINE
cQuery += "GROUP BY C7_NUM, C7_CONTATO, C7_TIPO, " + CRLF
cQuery += "A2_COD, A2_LOJA, A2_NOME, A2_TPESSOA, A2_CGC, A2_END, A2_BAIRRO, A2_MUN, A2_EST, " + CRLF
cQuery += "A2_TEL, A2_EMAIL, F1_STATUS " + CRLF
cQuery += "ORDER BY C7_NUM DESC " + CRLF
cOrderBy := u_FBORDERBY("PEDIDODECOMPRAS", cORDER, cDIR)
cQuery += cOrderBy
If cBD == 'ORACLE' .AND. cPAGINATION <> 'OFF'
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("PEDIDODECOMPRAS cQuery principal: " + 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(aDados, { QRY_AUX->C7_NUM,;
QRY_AUX->C7_CONTATO,;
QRY_AUX->C7_TIPO,;
QRY_AUX->C7_EMISSAO,;
QRY_AUX->A2_COD,;
QRY_AUX->A2_LOJA,;
QRY_AUX->A2_NOME,;
QRY_AUX->A2_TPESSOA,;
QRY_AUX->A2_CGC,;
QRY_AUX->A2_END,;
QRY_AUX->A2_BAIRRO,;
QRY_AUX->A2_MUN,;
QRY_AUX->A2_EST,;
QRY_AUX->A2_TEL,;
QRY_AUX->A2_EMAIL,;
QRY_AUX->TOTAL_PRODUTO,;
QRY_AUX->D1_EMISSAO,;
QRY_AUX->F1_STATUS,;
cValToChar(nTot);
})
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
Criar um WebService REST em ADVPL para consulta de pedidos de compras no Protheus é totalmente viável e extremamente útil para integrações externas, dashboards, sistemas parceiros ou análise de dados.
Essa abordagem modular, segura e escalável pode ser reaproveitada para outros cadastros como pedidos de vendas, clientes, produtos e muito mais.
