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 Atilio (Daniel Atilio)
Especialista em Engenharia de Software pela FIB. Entusiasta de soluções Open Source. E blogueiro nas horas vagas.

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