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.