Hoje vou mostrar para vocês, como configurar um serviço de WebService em AdvPL, usando SOAP.
Primeiramente devemos entender como funciona um WebService, nesse sentido, basicamente existem 2 pontos principais, o que recebe informações e o que manda informações.
Por exemplo, o WebService do SEFAZ possui vários serviços, dentro deles, alguns métodos, como o de transmitir uma nota, ele RECEBE uma informação XML, e MANDA de volta a informação se deu certo ou errada a transmissão.
Portanto, iremos configurar o nosso Protheus para que ele também tenha isso, ou seja, receba requisições externas, trate e mande de volta informações tratadas. Para isso usaremos o protocolo SOAP para troca de mensagens, e nesse exemplo, utilizaremos JSON (você também pode usar XML ou outros padrões texto).
Antes de iniciar, baixe o pacote compactado com todos os arquivos necessários e de exemplos, Clicando Aqui.
Configurando um novo serviço (incluindo um novo appserver.ini)
01. Crie uma pasta appserver que será um novo serviço, por exemplo, “TOTVS | WebServices” que será responsável por receber as conexões
02. Crie um novo apo também, que irá conter um RPO exclusivo para esse serviço de WS. No nosso arquivo, nosso ambiente se chamará AMB_TESTE
03. No seu appserver.ini, você deve se atentar para a chave http, deixando ela ativa e setando uma porta que ficará liberada (nesse exemplo 8091)
;Liberando a porta 8091 para conexões com o WebService [HTTP] ENABLE=1 PORT=8091
04. No mesmo arquivo, no fim dele, configure o IP e a porta que serão disponíveis para conexão juntamente com um job, no exemplo abaixo, é configurado tanto o ip interno quanto externo
;Configurando IP Interno [192.168.XXX.XXX:8091/ws] ENABLE=1 PATH=D:\TOTVS\TESTE\Protheus_Data\web\ws ENVIRONMENT=AMB_TESTE INSTANCENAME=ws RESPONSEJOB=JOB_WS_0101 DEFAULTPAGE=wsindex.apw ;Configurando IP Externo (se for necessário) [177.87.XXX.XXX:8091/ws] ENABLE=1 PATH=D:\TOTVS\TESTE\Protheus_Data\web\ws ENVIRONMENT=AMB_TESTE INSTANCENAME=ws RESPONSEJOB=JOB_WS_0101 DEFAULTPAGE=wsindex.apw
05. Por último, no mesmo arquivo, agora será inserido as informações de JOB, que serão executadas automaticamente, “esperando as requisições”
;Configurando para Empresa 01 e Filial 01 e 99 instâncias de conexão [JOB_WS_0101] TYPE=WEBEX ENVIRONMENT=AMB_TESTE INSTANCES=1,99 SIGAWEB=WS INSTANCENAME=ws ONSTART=__WSSTART ONCONNECT=__WSCONNECT PREPAREIN=01,01 ;Instrução para quando iniciar o serviço, iniciar o JOB_WS_0101 [ONSTART] JOBS=JOB_WS_0101
Obs.: Se você baixar o appserver.ini disponibilizado no arquivo, faça a configuração devida apontando para o seu license, altere as pastas e caminhos e alterne entre dbf / ctree
Copiando os arquivos para Protheus Data
06. Extraia o arquivo que foi baixado
07. Copie a pasta “web” para a raíz da sua Protheus_Data
Preparando um fonte em AdvPL
08. No arquivo baixado, separe as 3 includes que veio (aarray.ch, json.ch e shash.ch) e coloque na sua pasta de bibliotecas
09. Em integrações via SOAP geralmente existe algum TOKEN ou CHAVE para validar a conexão entre as duas partes, no nosso exemplo, é usado um parâmetro chamado MV_X_TOKEN. Crie na sua base esse parâmetro do tipo Caracter, e coloque um conteúdo único nele, esse conteúdo disponibilize apenas para programadores que irão consumir o seu serviço. No nosso caso, o conteúdo do parâmetro será “terminal@2019!tst123”
10. Crie um novo fonte, no nosso caso será um WebService de consulta de Clientes, iremos chamar o fonte de zWsCliente.prw
11. Dentro desse fonte, teremos 2 métodos, 1 para testar se está funcionando (método TstServ), e outro que irá retornar uma lista de clientes (método RetListCli), recebendo alguns parâmetros de filtro
12. Cada método terá 2 atributos, 1 de recebimento e 1 de envio, abaixo o fonte completo desenvolvido (também está no pacote para download):
//Bibliotecas #Include "Protheus.ch" #Include "APWebSrv.ch" #Include "TBIConn.ch" #Include "TBICode.ch" #Include "TopConn.ch" #Include "aarray.ch" #Include "json.ch" #Include "shash.ch" /* Links de acesso Interno - http://192.168.XXX.XXX:8091/ws/ Externo - http://177.87.XXX.XXX:8091/ws/ WSDL Interno - http://192.168.XXX.XXX:8091/ws/zWsCliente.apw?WSDL WSDL Externo - http://177.87.XXX.XXX:8091/ws/zWsCliente.apw?WSDL */ WsService zWsCliente Description "WebService com funcoes de teste" //Atributos WsData cTstRece as String WsData cTstSend as String WsData cFiltRece as String WsData cFiltSend as String //Métodos WsMethod TstServ Description "Metodo para testar se servico esta em funcionamento" WsMethod RetListCli Description "Metodo para retornar uma lista de clientes filtrando informacoes" EndWsService /* Método TstServ Metodo para testar se servico esta em funcionamento */ WsMethod TstServ WsReceive cTstRece WsSend cTstSend WsService zWsCliente ::cTstSend := "It's Works - Date " + dToC(Date()) + ", Time "+Time() Return .T. /* Método RetListCli Metodo para retornar uma lista de clientes filtrando informacoes Exemplo do JSON que será recebido { "Dados": { "Vendedor": "000000", "Estado": "SP", "Cidado": "Bauru" }, "Token" : "aaaa" } Exemplo de JSON que será enviado de volta (se tudo estiver certo) { "Clientes": { "000001" : { "Codigo":"XXXXXX", "RazaoSocial":"Beluga Corp", "NomeFantasia":"Beluga", "CGC":"00.000.000/0000-00" }, "000002" : { "Codigo":"YYYYYY", "RazaoSocial":"Beluga 2 Corp", "NomeFantasia":"Beluga 2", "CGC":"00.000.000/0000-00" }, ... } } */ WsMethod RetListCli WsReceive cFiltRece WsSend cFiltSend WsService zWsCliente //Retorno do Método RetListCli (.T. se está tudo certo ou .F. se houve falha) Local lRet := .T. //Variável de Token pegando da tabela SX6 Local cTokWs := Alltrim(GetMV('MV_X_TOKEN')) //Parâmetros recebidos pelo JSON (variável cFiltRece) Local cParVend := "" Local cParEst := "" Local cParMun := "" Local cParToken := "" //Consulta SQL para filtragem dos dados e variáveis usadas Local cQryCli := "" Local nAtual := 0 Local cCGC := "" //Variáveis usadas para transformar o JSON em Objeto Private oJSON := Nil Private oDados := Nil //Deserializando o JSON (transformando a "string" em "objeto") If (FWJsonDeserialize(::cFiltRece, @oJSON)) //Separando o objeto de dados, e o Token oDados := oJSON:Dados cParToken := Iif(Type("oJSON:Token") != "U", oJSON:Token, "") //Se o Token recebido for o mesmo do parâmetro, prossegue If cParToken == cTokWs //Pegando os 3 filtros possíveis vindo dentro de "Dados" cParVend := Alltrim(Iif(Type("oDados:Vendedor") != "U", oDados:Vendedor, "")) cParEst := Alltrim(Iif(Type("oDados:Estado") != "U", oDados:Estado, "")) cParMun := Alltrim(Iif(Type("oDados:Cidade") != "U", oDados:Cidade, "")) //Se os 3 parâmetros estiverem em branco, retorna erro, só pode prosseguir se pelo menos 1 estiver preenchido If Empty(cParVend) .And. Empty(cParEst) .And. Empty(cParMun) SetSoapFault('Erro', 'Os 3 parametros estao em branco, preencha pelo menos 1!') lRet := .F. Else //Selecionando os clientes conforme o filtro cQryCli := " SELECT " + CRLF cQryCli += " A1_COD, " + CRLF cQryCli += " A1_NOME, " + CRLF cQryCli += " A1_NREDUZ, " + CRLF cQryCli += " A1_CGC " + CRLF cQryCli += " FROM " + CRLF cQryCli += " " + RetSQLName('SA1') + " SA1 " + CRLF cQryCli += " WHERE " + CRLF cQryCli += " A1_FILIAL = '" + FWxFilial('SA1') + "' " + CRLF If ! Empty(cParVend) cQryCli += " AND UPPER(A1_VEND) LIKE '%" + cParVend + "%' " + CRLF EndIf If ! Empty(cParEst) cQryCli += " AND UPPER(A1_EST) LIKE '%" + cParEst + "%' " + CRLF EndIf If ! Empty(cParMun) cQryCli += " AND UPPER(A1_MUN) LIKE '%" + cParMun + "%' " + CRLF EndIf cQryCli += " AND SA1.D_E_L_E_T_ = ' ' " + CRLF TCQuery cQryCli New Alias "QRY_CLI" //Se existirem dados da consulta If ! QRY_CLI->(EoF()) //Começa a montar o JSON de retorno ::cFiltSend += ' { ' + CRLF ::cFiltSend += ' "Clientes" : { ' + CRLF //Enquanto houver clientes While ! QRY_CLI->(EoF()) //Incrementa o contador nAtual++ //Transformando o CNPJ ou CPF para visualização cCGC := Alltrim(QRY_CLI->A1_CGC) If Len(Alltrim(SA2->A2_CGC)) > 11 cCGC := Alltrim(Transform(cCGC, "@R 99.999.999/9999-99")) Else cCGC := Alltrim(Transform(cCGC, "@R 999.999.999-99")) EndIf //Monta o cliente atual ::cFiltSend += ' "' + StrZero(nAtual, 6) + '": { ' + CRLF ::cFiltSend += ' "Codigo":"' + QRY_CLI->A1_COD + '", ' + CRLF ::cFiltSend += ' "RazaoSocial":"' + Alltrim(QRY_CLI->A1_NOME) + '", ' + CRLF ::cFiltSend += ' "NomeFantasia":"' + Alltrim(QRY_CLI->A1_NREDUZ) + '", ' + CRLF ::cFiltSend += ' "CGC":"' + cCGC + '"' + CRLF ::cFiltSend += ' }' //Pula para o próximo cliente QRY_CLI->(DbSkip()) //Se não for o último cliente, acrescenta a vírgula If ! QRY_CLI->(EoF()) ::cFiltSend += ',' EndIf ::cFiltSend += CRLF EndDo ::cFiltSend += ' } ' + CRLF ::cFiltSend += ' }' + CRLF //Se não existirem clientes, retorna mensagem de falha Else SetSoapFault('Erro', 'Nao existem clientes nesse filtro usado!') lRet := .F. EndIf QRY_CLI->(DbCloseArea()) EndIf //Caso seja um token inválido Else SetSoapFault('Erro', 'Token invalido!') lRet := .F. EndIf //Se houve erro ao deserializar o JSON Else SetSoapFault('Erro', 'JSON nao deserializado!') lRet := .F. EndIf Return lRet
13. Compile a rotina nesse ambiente, e inicie o serviço
Pegando o WSDL
14. O WSDL é o endereço que ficará disponível para acessar a sua rotina, para pegar o endereço, acesse o IP e a porta configurados no appserver.ini seguido por “/ws”, por exemplo, “192.168.XXX.XXX:8091/ws”
15. Nessa página, procure pelo seu serviço zWsCliente e clique nele
16. Na descrição do serviço, clique nesse link
17. Copie a URL, no nosso exemplo, ficou 192.168.XXX.XXX:8091/ws/ZWSCLIENTE.apw?WSDL
Testando a conexão com o SoapUI
18. Baixe o aplicativo SoapUI e configure adicionando essa WSDL nele. Para saber como configurar, leia esse artigo Como configurar o SoapUI
19. Faça um teste no método TstServ, o resultado deve ser o similar ao abaixo
20. Efetue testes no método RetListCli, lembrando que se houver erros, serão mostrados
21. Caso dê certo, exibirá uma lista de clientes
Testando a conexão através do PHP
22. Caso você tenha um servidor rodando PHP, preparei um pequeno fonte para testar a integração com o Protheus, segue abaixo (também está no arquivo compactado para download)
<html> <head> <title>Testes Clientes</title> </head> <body> <?php //Cria as variaveis usadas em branco $vendedor = ''; $estado = ''; $cidade = ''; $ok = false; $mensagem = ''; $clientes = null; //Pega os dados que foram confirmados if (isset($_POST['vendedor'])) { $vendedor = $_POST['vendedor']; $ok = true; } if (isset($_POST['estado'])) { $estado = $_POST['estado']; $ok = true; } if (isset($_POST['cidade'])) { $cidade = $_POST['cidade']; $ok = true; } //Se algum parâmetro foi preenchido if ($ok) { //Tenta conectar com o WebService do Protheus try { $soapClient = new SoapClient('http://192.168.XXX.XXX:8091/ws/zWsCliente.apw?WSDL', ['exceptions' => true]); } catch (SoapFault $exception) { $mensagem = 'Houve um erro ao buscar os dados, <b>contate o Administrador</b>. Exception: <br>'; $mensagem .= $exception->getMessage(); } //Tenta executar o metodo que retorna a lista de clientes try { $requestData = array( "RETLISTCLI" => array( "CFILTRECE" => '{'. ' "Dados": {'. ' "Vendedor":"' . $vendedor . '", '. ' "Estado":"' . $estado . '", '. ' "Cidade":"' . $cidade . '" '. ' },'. ' "Token": "terminal@2019!tst123"'. '}' ) ); $response = $soapClient->__soapCall("RETLISTCLI", $requestData); $jsonLogin = json_decode($response->RETLISTCLIRESULT); if (json_last_error() == 0) { $clientes = $jsonLogin; } } catch (SoapFault $exception) { $mensagem = 'Houve um erro ao buscar os dados, <b>contate o Administrador</b>. Exception: <br>'; $mensagem .= $exception->getMessage(); } } ?> <form action="clientes.php" method="post"> Vendedor:<br> <input type="text" id="vendedor" name="vendedor" placeholder="Digite o código do Vendedor" maxlength="6" style="width: 200px;" value=<?php echo $vendedor;?>><br> Estado:<br> <select id="estado" name="estado"> <option <?php if (empty($estado)) echo 'selected="selected"'; ?>></option> <option <?php if ($estado == "AC") echo 'selected="selected"'; ?>>AC</option> <option <?php if ($estado == "AL") echo 'selected="selected"'; ?>>AL</option> <option <?php if ($estado == "AP") echo 'selected="selected"'; ?>>AP</option> <option <?php if ($estado == "AM") echo 'selected="selected"'; ?>>AM</option> <option <?php if ($estado == "BA") echo 'selected="selected"'; ?>>BA</option> <option <?php if ($estado == "CE") echo 'selected="selected"'; ?>>CE</option> <option <?php if ($estado == "DF") echo 'selected="selected"'; ?>>DF</option> <option <?php if ($estado == "ES") echo 'selected="selected"'; ?>>ES</option> <option <?php if ($estado == "GO") echo 'selected="selected"'; ?>>GO</option> <option <?php if ($estado == "MA") echo 'selected="selected"'; ?>>MA</option> <option <?php if ($estado == "MT") echo 'selected="selected"'; ?>>MT</option> <option <?php if ($estado == "MS") echo 'selected="selected"'; ?>>MS</option> <option <?php if ($estado == "MG") echo 'selected="selected"'; ?>>MG</option> <option <?php if ($estado == "PA") echo 'selected="selected"'; ?>>PA</option> <option <?php if ($estado == "PB") echo 'selected="selected"'; ?>>PB</option> <option <?php if ($estado == "PR") echo 'selected="selected"'; ?>>PR</option> <option <?php if ($estado == "PE") echo 'selected="selected"'; ?>>PE</option> <option <?php if ($estado == "PI") echo 'selected="selected"'; ?>>PI</option> <option <?php if ($estado == "RJ") echo 'selected="selected"'; ?>>RJ</option> <option <?php if ($estado == "RN") echo 'selected="selected"'; ?>>RN</option> <option <?php if ($estado == "RS") echo 'selected="selected"'; ?>>RS</option> <option <?php if ($estado == "RO") echo 'selected="selected"'; ?>>RO</option> <option <?php if ($estado == "RR") echo 'selected="selected"'; ?>>RR</option> <option <?php if ($estado == "SC") echo 'selected="selected"'; ?>>SC</option> <option <?php if ($estado == "SP") echo 'selected="selected"'; ?>>SP</option> <option <?php if ($estado == "SE") echo 'selected="selected"'; ?>>SE</option> <option <?php if ($estado == "TO") echo 'selected="selected"'; ?>>TO</option> </select><br> Cidade:<br> <input type="text" id="cidade" name="cidade" placeholder="Digite o nome da Cidade" maxlength="30" style="width: 200px;" value=<?php echo $cidade;?>><br> <br><br> <button type="submit">Confirmar</button> </form> <hr> Resultado(s):<br> <?php //Se existe mensagem de erro, mostra ela if (! empty($mensagem)) { echo '<font color="red">' . $mensagem . '</font><br>'; } ?> <br> <table border="2"> <thead> <tr> <th><b>Código</b></th> <th><b>Razão Social</b></th> <th><b>Nome Fantasia</b></th> <th><b>CGC</b></th> </tr> </thead> <tbody> <?php if ($clientes != null) { foreach ($clientes->Clientes as $cli) { echo '<tr>'; echo ' <td>' . $cli->Codigo . '</td>'; echo ' <td>' . $cli->RazaoSocial . '</td>'; echo ' <td>' . $cli->NomeFantasia . '</td>'; echo ' <td>' . $cli->CGC . '</td>'; echo '</tr>'; } } ?> </tbody> </table> </body> </html>
23. Abrindo a página php, note que na área de cima são os filtros, e em baixo os resultados. Se ocorrer algum erro, será mostrado a mensagem nos resultados
24. Se tudo der certo, será mostrado o resultado
Basicamente é isso jovens, espero que tenham gostado do tutorial (deu trabalho para montar rs). Com essa premissa é possível fazer várias coisas com o Protheus, como por exemplo, um Painel de Vendas, inclusão de Documento de Entrada, inclusão de Pedidos, consulta de indicadores, consulta de títulos, etc.
Bom pessoal, por hoje é só.
Abraços e até a próxima.
Dan, boa noite.
Como sempre, mais uma excelente aula.
Estou iniciando o estudo de webservice com Rest.
Você poderia montar um material com esta opção.
Um forte abraço.
Boa tarde Vagner.
Vou ver se consigo montar.
Um grande abraço jovem.
Bom dia!
Mais uma vez um excelente conteúdo, obrigado por compartilhar!
Abraço
Eu que agradeço Edivaldo. Um grande abraço.
Isso não foi um tutorial, foi um curso completo. Parabens pelo conteudo.
Obrigado Guilherme. Um grande abraço jovem.
Aparece para mim no teste. Erro : JSON nao deserializado!
Oque sera que esta faltando?
Boa noite Flavio, tudo bem?
Veja os passos 20 e 21, e note se você está enviando a estrutura do JSON corretamente.
Se quiser, mande também aqui para conferirmos.
Um grande abraço.
Boa noite.
Pegue o exemplo acima para testar e aprender, mas quando executo pelo SOAPUI da mensagem
Sender
Erro : JSON nao deserializado!
Qual seria o problema?
Obrigado
Bom dia.
Como está a string json que você está enviando?
Abraços.