Converse com a gente ali na caixa ao lado 👉
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
1 2 3 4 5 6 |
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:
1 2 3 |
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:
1 2 3 |
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):
1 2 3 4 5 |
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:
1 2 3 |
oObjCom := Objetos():New(aDados) cJson := FWJsonSerialize(oObjCom) ::SetResponse(cJson) |
Exemplo de Requisição GET
1 |
GET /rest/PEDIDODECOMPRAS?FORNECE=000123&PAGINA=1&PAGINATION=ON&SEGKEY=XYZ123 |
Exemplo de Retorno JSON
1 2 3 4 5 6 7 8 |
[ { "C7_NUM": "000123", "A2_NOME": "Fornecedor XYZ", "TOTAL_PRODUTO": 120.00, ... } ] |
Código Completo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 |
#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.
Fernando Bueno
Atuando desde 2005 no mercado de tecnologia, desenvolvendo e implantando e sistemas gerenciais, sistemas e sites web e ecommerce.
Siga-me no Linked In