Exemplo de criação de Pedido de Venda com JSON e WebService REST

Hoje vamos mostrar como incluir e consultar pedidos de venda no Protheus utilizando JSON e REST.

Primeiramente jovens, o fonte foi criado pelo Rodrigo dos Santos, uma das lendas do Black TDN . Ele publicou o código no nosso grupo AdvPLers no Discord. Se quiserem acompanhar o trabalho dele, ele está organizando o GitHub, deem um Follow lá, o cara é fera – https://github.com/rodrigomicrosiga .

Basicamente, esse é um fonte onde existem dois métodos POST, um com FWJsonDeserialize e outro com o uso de JsonObject, onde é necessário informar uma string JSON, com a seguinte parametrização:

{	
	"Client":"000001",
	"Store":"01",
	"CustomerDelivery":"000001",
	"DeliveryStore":"01",
	"PaymentTerms":"000",
	"MessageForNote":"Mensagem para nota",
	"Nature":"0000000001",
	"Items":
	[
	  {
	    "Product":"000000000000001",
		"SalesQuantity":1,
		"SalesPrice":100,
		"Value":100,
		"InputTypeOutPut":"501"
	  },
	  {
	    "Product":"000000000000002",
		"SalesQuantity":1,
		"SalesPrice":100,
		"Value":100,
		"InputTypeOutPut":"501"
	  }
	]
}

Além dos métodos POST, tem um método GET para buscar informações do pedido.

Abaixo o código fonte completo (foi criado ID para cada método, pois existem verbos repetidos, por exemplo, existem dois POST – tem uma limitação dessa de não poder declarar ID em binários antigos antes da 12.1.17):

#include 'totvs.ch'
#include 'restful.ch'
 
#DEFINE LOG_DIRECTORY               "\log_integ"
#DEFINE TYPE_OF_NORMAL_REQUEST      "N"
#DEFINE OPEN_REQUEST                "pedido em aberto"
#DEFINE APPLICATION_CLOSED          "pedido encerrado"
#DEFINE REQUEST_RELEASED            "pedido liberado"
#DEFINE REQUEST_BLOCKED_BY_RULE     "pedido bloqueado por regra"
#DEFINE REQUEST_BLOCKED_BY_FUNDS    "pedido bloqueado por verba"
 
/*
API para inclusão e consulta de pedidos de venda
 
Autor: Rodrigo dos Santos ( www.blacktdn.com.br )
 
OBS.: Para o release 17 é necessário possuir LIB / binário que suporte ID na declaração do método
*/
 
WSRESTFUL SalesOrder DESCRIPTION 'API para manutenção de pedidos de venda' SECURITY 'MATA410' FORMAT APPLICATION_JSON
    WSDATA RequestNumber As Character
 
    WSMETHOD POST SalesOrderInclusion   DESCRIPTION 'Inclusão de pedidos de venda'          WSSYNTAX '/SalesOrder/SalesOrderInclusion'  PRODUCES APPLICATION_JSON
    WSMETHOD POST OrderInclusion        DESCRIPTION 'Inclusão de pedidos de venda'          WSSYNTAX '/SalesOrder/OrderInclusion'       PRODUCES APPLICATION_JSON
    WSMETHOD GET  RequestQuery          DESCRIPTION 'Consulta status do pedido de venda'    WSSYNTAX '/SalesOrder{RequestNumber}'       PRODUCES APPLICATION_JSON
ENDWSRESTFUL
 
/*
SalesOrderInclusion método POST utilizando jSonObject
 
Autor: Rodrigo dos Santos ( www.blacktdn.com.br )
*/
 
WSMETHOD POST SalesOrderInclusion WSRECEIVE WSRESTFUL SalesOrder
    Local lRet  := .T.
 
    Local aArea := GetArea()
    Local aCabec
    Local aItens
    Local aLinha
 
    Local oJson
    Local oItems
 
    Local cJson     := Self:GetContent()
    Local cError    
 
    Local nX
    Local nY
 
    Private lMsErroAuto     := .F.
    Private lMsHelpAuto     := .T.
    Private lAutoErrNoFile  := .T.
 
    //Se não existir o diretório de logs dentro da Protheus Data, será criado
    IF .NOT. ExistDir(LOG_DIRECTORY)
        MakeDir(LOG_DIRETCTORY)
    EndIF    
 
    //Definindo o conteúdo como JSON, e pegando o content e dando um parse para ver se a estrutura está ok
    Self:SetContentType("application/json")
    oJson   := JsonObject():New()
    cError  := oJson:FromJson(cJson)
 
    //Se tiver algum erro no Parse, encerra a execução
    IF .NOT. Empty(cError)
        SetRestFault(500,'Parser Json Error')
        lRet    := .F.
    Else
 
        //Se encontrar o cliente existente conforme dados do JSON
        DbSelectArea('SA1')
        SA1->(dbSetOrder(1))
        IF (SA1->(dbSeek(FWxFilial("SA1")+PadR(oJson:GetJsonObject('Client'),TamSX3("A1_COD")[1])+PadR(oJson:GetJsonObject('Store'),TamSX3("A1_LOJA")[1]))))
            aCabec  := {}
            aItens  := {}
            /* removido trecho abaixo pois o cliente afirma que existe geração de numeração automatica
            cPedido := GetSXENum("SC5","C5_NUM")
            aAdd(aCabec,{"C5_NUM",  cPedido,    NIL})
            */
            aAdd(aCabec,{"C5_TIPO",     TYPE_OF_NORMAL_REQUEST,                         NIL})
            aAdd(aCabec,{"C5_CLIENTE",  AllTrim(oJson:GetJsonObject('Client')),         NIL})
            aAdd(aCabec,{"C5_LOJACLI",  AllTrim(oJson:GetJsonObject('Store')),          NIL})
            aAdd(aCabec,{"C5_LOJAENT",  AllTrim(oJson:GetJsonObject('DeliveryStore')),  NIL})
            aAdd(aCabec,{"C5_CONDPAG",  AllTrim(oJson:GetJsonObject('PaymentTerms')),   NIL})
            aAdd(aCabec,{"C5_MENNOTA",  AllTrim(oJson:GetJsonObject('MessageForNote')), NIL})
            aAdd(aCabec,{"C5_NATUREZ",  AllTrim(oJson:GetJsonObject('Nature')),         NIL})
 
            //Busca os itens no JSON, percorre eles e adiciona no array da SC6
            oItems  := oJson:GetJsonObject('Items')
            For nX  := 1 To Len (oItems)
                aLinha  := {}
                aAdd(aLinha,{"C6_ITEM",     StrZero(nX,2),                                          NIL})
                aAdd(aLinha,{"C6_PRODUTO",  AllTrim(oItems[nX]:GetJsonObject('Product')),           NIL})
                aAdd(aLinha,{"C6_QTDVEN",   oItems[nX]:GetJsonObject('SalesQuantity'),              NIL})
                aAdd(aLinha,{"C6_PRCVEN",   oItems[nX]:GetJsonObject('SalesPrice'),                 NIL})
                aAdd(aLinha,{"C6_VALOR",    oItems[nX]:GetJsonObject('Value'),                      NIL})
                aAdd(aLinha,{"C6_TES",      AllTrim(oItems[nX]:GetJsonObject('InputTypeOutPut')),   NIL})
                aAdd(aItens,aLinha)
            Next nX      
 
            //Chama a inclusão automática de pedido de venda
            MsExecAuto({|x, y, z| MATA410(x, y, z)},aCabec,aItens,3)
 
            //Se houve erro, gera um arquivo de log dentro do diretório da protheus data
            IF lMsErroAuto
                /* removido trecho abaixo pois o cliente afirma que existe geração de numeração automatica
                RollBackSX8()
                */
                cArqLog := oJson:GetJsonObject('CLient')+oJson:GetJsonObject('Store')+"-"+ StrTran(Time(), ':', '-')+".log"
                aLogAuto    := {}
                aLogAuto    := GetAutoGrLog()
                For nY := 1 To Len(aLogAuto)
                    cErro += aLogAuto[nY] + CRLF
                Next nY
                MemoWrite(LOG_DIRECTORY + cArqLog,cErro)
                SetRestFault(500, cErro)
                lRet    := .F.
            ELSE
                /* removido trecho abaixo pois o cliente afirma que existe geração de numeração automatica
                ConfirmSX8()
                */
                cJsonRet    := '{"Pedido gerado com sucesso":"'+SC5->C5_NUM+'"}'
                Self:SetResponse(cJsonRet)
            EndIF
        ELSE
            SetRestFault(500,EncodeUTF8("Cliente não encontrado"))
            lRet := .F.
        EndIF
    EndIf
 
    RestArea(aArea)
    FreeObj(oJson)
Return(lRet)
 
/*
OrderInclusion método POST utilizando FWJsonDeserialize
 
Autor: Rodrigo dos Santos ( www.blacktdn.com.br )
*/
 
WSMETHOD POST OrderInclusion WSRECEIVE WSRESTFUL SalesOrder
    Local cJson     := Self:GetContent()
    Local cJsonRet  := ""
    Local cArqLog   := ""
    Local cErro     := ""
 
    Local nX
    Local nY
 
    Local lRet  := .T.
 
    Local oParseJson    := NIL
 
    Local aArea     := GetArea()
    Local aCabec    := {}
    Local aItens    := {}
    Local aLinha    := {}
    Local aLogAuto  := {}
 
    Private lMsErroAuto     := .F.
    Private lMsHelpAuto     := .T.
    Private lAutoErrNoFile  := .T.
 
    //Se não existir o diretório de logs dentro da Protheus Data, será criado
    IF .NOT. ExistDir(LOG_DIRECTORY)
        MakeDir(LOG_DIRECTORY)
    EndIF
 
    //Definindo o conteúdo como JSON, e pegando o content e dando um parse para ver se a estrutura está ok
    Self:SetContentType("application/json")
    If FWJsonDeserialize(cJson,@oParseJson)
 
        //Se encontrar o cliente existente conforme dados do JSON
        DbSelectArea('SA1')
        SA1->(dbSetOrder(1))
        IF (SA1->(dbSeek(FWxFilial("SA1")+oParseJson:Client+oParseJson:Store)))
            aCabec  := {}
            aItens  := {}
            /* removido trecho abaixo pois o cliente afirma que existe geração de numeração automatica
            cPedido := GetSXENum("SC5","C5_NUM")
            aAdd(aCabec,{"C5_NUM",  cPedido,    NIL})
            */
            aAdd(aCabec,{"C5_TIPO",     TYPE_OF_NORMAL_REQUEST,             NIL})
            aAdd(aCabec,{"C5_CLIENTE",  AllTrim(oParseJson:Client),         NIL})
            aAdd(aCabec,{"C5_LOJACLI",  AllTrim(oParseJson:Store),          NIL})
            aAdd(aCabec,{"C5_LOJAENT",  AllTrim(oParseJson:DeliveryStore),  NIL})
            aAdd(aCabec,{"C5_CONDPAG",  AllTrim(oParseJson:PaymentTerms),   NIL})
            aAdd(aCabec,{"C5_MENNOTA",  AllTrim(oParseJson:MessageForNote), NIL})
            aAdd(aCabec,{"C5_NATUREZ",  AllTrim(oParseJson:Nature),         NIL})
 
            //Busca os itens no JSON, percorre eles e adiciona no array da SC6
            For nX  := 1 To Len (oParseJson:Items)
                alinha  := {}
                aAdd(aLinha,{"C6_ITEM",     StrZero(nX,2),                                  NIL})
                aAdd(aLinha,{"C6_PRODUTO",  AllTrim(oParseJson:Items[nX]:Product),          NIL})
                aAdd(aLinha,{"C6_QTDVEN",   oParseJson:Items[nX]:SalesQuantity,             NIL})
                aAdd(aLinha,{"C6_PRCVEN",   oParseJson:Items[nX]:SalesPrice,                NIL})
                aAdd(aLinha,{"C6_VALOR",    oParseJson:Items[nX]:Value,                     NIL})
                aAdd(aLinha,{"C6_TES",      AllTrim(oParseJson:Items[nX]:InputTypeOutPut),  NIL})
                aAdd(aItens,aLinha)
            Next nX      
 
            //Chama a inclusão automática de pedido de venda
            MsExecAuto({|x, y, z| MATA410(x, y, z)},aCabec,aItens,3)
 
            //Se houve erro, gera um arquivo de log dentro do diretório da protheus data
            IF lMsErroAuto
                /* removido trecho abaixo pois o cliente afirma que existe geração de numeração automatica
                RollBackSX8()
                */
                cArqLog := oJson:GetJsonObject('CLient')+oJson:GetJsonObject('Store')+"-"+ StrTran(Time(), ':', '-')+".log"
                aLogAuto    := {}
                aLogAuto    := GetAutoGrLog()
                For nY := 1 To Len(aLogAuto)
                    cErro += aLogAuto[nY] + CRLF
                Next nY
                MemoWrite(LOG_DIRECTORY + cArqLog,cErro)
                SetRestFault(500,cErro)
                lRet    := .F.
            ELSE
                /* removido trecho abaixo pois o cliente afirma que existe geração de numeração automatica
                ConfirmSX8()
                */
                cJsonRet    := '{"Pedido gerado com sucesso":"'+SC5->C5_NUM+'"}'
                Self:SetResponse(cJsonRet)
            EndIF
        ELSE
            SetRestFault(500,EncodeUTF8("Cliente não encontrado"))
            lRet := .F.
        EndIF
    ELSE
        SetRestFault(500,'Parser Json Error')
        lRet    := .F.
    EndIf
 
    RestArea(aArea)
Return(lRet)
 
/*
RequestQuery método GET
 
Autor: Rodrigo dos Santos ( www.blacktdn.com.br )
*/
 
WSMETHOD GET RequestQuery WSRECEIVE RequestNumber WSRESTFUL SalesOrder
    Local lRet  := .T.
 
    Local cViewSC5  := GetNextAlias()
 
    Local aData := {}
 
    Local oData := NIL
 
    /*
    obrigatorio informar o numero do pedido
    */
    IF Empty(Self:RequestNumber)
        SetRestFault(500,EncodeUTF8('O parametro RequestNumber é obrigatório'))
        lRet    := .F.
        Return(lRet)
    EndIF
 
    /*
    montagem da consulta
    */
    BeginSQL Alias cViewSC5
        SELECT SC5.C5_FILIAL
            ,SC5.C5_NUM
            ,SC5.C5_LIBEROK
            ,SC5.C5_NOTA
            ,SC5.C5_BLQ
        FROM %Table:SC5% SC5
        WHERE SC5.C5_FILIAL = %FWxFilial:SC5%
            AND SC5.C5_NUM = %Exp:AllTrim(Self:RequestNumber)%
            AND SC5.%NotDel%
    EndSQL    
 
    //Verifica se tem dados na query
    dbSelectArea(cViewSC5)
    (cViewSC5)->(dbGoTop())
    IF (cViewSC5)->(.NOT. Eof())
 
        //Enquanto houver dados na query
        While (cViewSC5)->(.NOT. Eof())
 
            //Cria um objeto JSON
            oData           := JsonObject():New()
 
            //Se o pedido tiver em aberto
            IF Empty((cViewSC5)->C5_LIBEROK) .AND. Empty((cViewSC5)->C5_NOTA) .AND. Empty((cViewSC5)->C5_BLQ)
                oData['status'] := OPEN_REQUEST
 
            //Se o pedido estiver encerrado
            ELSEIF !Empty((cViewSC5)->C5_NOTA) .OR. (cViewSC5)->C5_LIBEROK=='E' .AND. Empty((cViewSC5)->C5_BLQ)
                oData['status'] := APPLICATION_CLOSED
 
            //Se o pedido estiver liberado
            ELSEIF !Empty((cViewSC5)->C5_LIBEROK) .AND. Empty((cViewSC5)->C5_NOTA) .AND. Empty((cViewSC5)->C5_BLQ)
                oData['status'] := REQUEST_RELEASED  
 
            //Se o pedido tiver bloqueio por regra
            ELSEIF (cViewSC5)->C5_BLQ=='1'
                oData['status'] := REQUEST_BLOCKED_BY_RULE
 
            //Se o pedido tiver bloqueio por verba
            ELSEIF (cViewSc5)->C5_BLQ=='2'
                oData['status'] := REQUEST_BLOCKED_BY_FUNDS
            EndIF
 
            //Adiciona no array a informação e libera o objeto
            aAdd(aData,oData)
            FreeObj(oData)
 
            (cViewSC5)->(dbSkip())
        EndDo
 
        //Define o retorno do método
        Self:SetResponse(FwJsonSerialize(aData))
 
    ELSE
        SetRestFault(500,EncodeUTF8('Não existem dados para serem apresentados'))
        lRet    := .F.
    EndIF    
 
    (cViewSC5)->(dbCloseArea())
 
Return(lRet)

Bom pessoal, por hoje é só.

Abraços e até a próxima.

Dan (Daniel Atilio)
Cristão de ramificação protestante. Especialista em Engenharia de Software pela FIB, graduado em Banco de Dados pela FATEC Bauru e técnico em informática pelo CTI da Unesp. Entusiasta de soluções Open Source e blogueiro nas horas vagas. Autor e mantenedor do portal Terminal de Informação.

8 Responses

  1. Ricardo Rey disse:

    Obrigado por compartilhar!
    Só estou tendo um problema…
    No método Post ele não respeita o método que chamo, a rotina sempre entra em SalesOrderInclusion

  2. Adalberto Mendes Neto disse:

    Bom dia.

    Muito boa essa materia.

    Mas vcs teriam um fonte assim com json mas para incluir cadastro de clientes no protheus vindo do meu e-commerce?

    Grato.

  3. Nicolas Vicente disse:

    Boa tarde!

    Estamos querendo realizar o cadastro automático de usuários no Protheus, via JSON, conforme documentação da TOTVS (https://tdn.totvs.com/pages/releaseview.action?pageId=274327398) porém sou novo em desenvolvimento e fiquei perdido, não entendi como posso realizar o cadastro via JSON sendo que o cadastro de usuários não tem uma “tabela” para realizarmos o “post”. Você teria algum exemplo ou alguma luz para ajudar?

    Obrigado!

    • Bom dia Nicolas.
      Eu nunca precisei criar usuários via WS. Mas parece que o exemplo esta bem completo. Na parte que esta escrito “Exemplo de requisição para a inclusão de usuário”, chegou a testar via Postman?
      Outro ponto também, tente entrar no nosso fórum no Discord (link no cabeçalho do site), pode ser que lá alguém já tenha realizado essa operação.
      Abraços.

  4. REINALDO MAURICIO SANTOS disse:

    Não consegui implentar, ele da erro na FWxFilial

Deixe uma resposta para Nicolas VicenteCancelar resposta

Terminal de Informação