No artigo de hoje, vamos demonstrar em como consumir a API do GS1, para criar o código GTIN do Produto e já popular as informações das tabelas SB1 e SB5.
Esse fonte foi desenvolvido pelo grande Vinicius Reies ( https://www.linkedin.com/in/vreies ).
Basicamente é necessário:
- Um parâmetro, de nome MV_X_GS1EM com o eMail de acesso para gerar o Token no GS1
- Um parâmetro, de nome MV_X_GS1PS com a senha desse email para gerar o Token no GS1
- Um parâmetro, de nome MV_X_GS1AU com a string da autenticação Basic usada no GS1
- Um parâmetro, de nome MV_X_GS1ID com o client_id no GS1
Nisso a lógica funciona da seguinte forma:
- É recebido o código de um produto
- Se esse produto não tiver ainda o Código GTIN (B1_CODGTIN)
- Ai é acionado a geração do Token através de uma função chamada fRestTken() usando FWRest
- Após gerar o Token, ai é montado uma string com as informações do cadastro de produtos (isso precisa ser revisado por cada empresa, conforme regras de negócio)
- Essa string é enviada à API do GS1 através da classe FWRest
- Se deu tudo certo, a informação vai ser gravada no B1_CODGTIN e se houver no B5_2CODBAR (nesse exemplo foi usado RecLock, mas você poderia adaptar e usar MsExecAuto)
Segue abaixo o código fonte conforme a lógica descrita acima:
//Bibliotecas
#Include "Totvs.ch"
/*/{Protheus.doc} zSB1Gtin
Verificação e atribuição de GTIN para produtos
@type user function
@author Vinicius
@since 06/01/2025
@version version
@param cCodProd, character, codigo do produto
/*/
User Function zSB1Gtin(cCodProd)
Local aArea := FWGetArea()
Local cToken := "" // Variável que armazena o token
Private cRetorno := "" // Variável para validar retorno de erro
// Abrindo a tabela SB1
DbSelectArea("SB1")
SB1->(DbSetOrder(1)) // Filial + Código
// Verificando existência e posicionando através do código do produto
If SB1->(MsSeek(FWxFilial("SB1") + cCodProd))
// Verificando se B1_CODGTIN está vazio
If Empty(SB1->B1_CODGTIN)
// Requisição para pegar token
cToken := fRestTken()
if !Empty(cToken)
// Requisição para aplicar cadastro
fRestCad(cToken)
EndIf
Else
// Código Gtin já existente
cRetorno := "Campo B1_CODGTIN já existia para o produto: " + SB1->B1_CODGTIN
EndIf
Else
cRetorno := "Produto não encontrado"
EndIf
FWRestArea(aArea)
Return cRetorno
// Função para requisição REST do token
Static Function fRestTken()
Local cJsonText := "" // JSON para access_token
Local cToken := "" // Variável para armazenar o token
Local cResultado := "" // Armazena resulta da requisição
Local cErro := "" // Armazena o erro
Local aHeader := {} // Header para montar a requisição REST
Local oRestUrl := FWRest():New("http://api.gs1br.org/") // URL para access_token. (testes: http://api-hml.gs1br.org/oauth/access-token) (produção: http://api.gs1br.org/oauth/access-token)
Local cEmailGs1 := u_zGetMV("MV_X_GS1EM") // Email de acesso para requisição do token
Local cSenhaGs1 := u_zGetMV("MV_X_GS1PS") // Senha de acesso para requisição do token
Local cBasicGs1 := u_zGetMV("MV_X_GS1AU") // String em Base64 da Autenticação Basic no GS1
Local jResponse // Response do json
Local aArray := {} // Array de objetos do retorno de erro de login
Local cMsgErro := "" // Variável string para erro de login.
// Adiciona os cabeçalhos necessários para a requisição
aAdd(aHeader, 'Content-Type: application/json')
aAdd(aHeader, 'Authorization: Basic ' + cBasicGs1)
// Setando o path do diretório
oRestUrl:setPath("oauth/access-token")
// JSON para pegar o access_token (Ambiente de produção)
cJsonText := '{' + CRLF
cJsonText += ' "grant_type":"password",' + CRLF
cJsonText += ' "username":"' + cEmailGs1 + '",' + CRLF
cJsonText += ' "password":"' + cSenhaGs1 + '"' + CRLF
cJsonText += '}' + CRLF
// Setando os parâmetros da variável para o objeto
oRestUrl:SetPostParams(cJsonText)
// Realiza a requisição POST
If oRestUrl:Post(aHeader)
cResultado := oRestUrl:GetResult()
//Pega o JSON de resposta, e pega o token
jResponse := JsonObject():New()
jResponse:FromJson(cResultado)
//Pega o Token de acesso
cToken := Iif( ValType(jResponse['access_token']) != "U", jResponse['access_token'], "")
Else
cErro := oRestUrl:GetResult()
If Empty(cErro)
cErro := oRestUrl:GetLastError()
Else
//Pega o JSON de resposta, e pega o token
jResponse := JsonObject():New()
jResponse:FromJson(cErro)
// Falha na requisição
aArray := jResponse:GetJsonObject('errors')
If Len(aArray) > 0
cMsgErro := aArray[1]:GetJsonObject('message')
EndIf
EndIf
cRetorno := "Falha na obtenção do token -" + cMsgErro
EndIf
Return cToken
// Função para requisição REST cadastro
Static Function fRestCad(cToken)
Local cJsonText := "" // Json para access_token
Local aHeader := {} // Header para montar a requisição REST
Local oRestUrl := FWRest():New("https://api.gs1br.org/") // Url para cadastro (testes: https://api-hml.gs1br.org/gs1/v2/products) (produção: https://api.gs1br.org/gs1/v2/products)
Local cB1Desc := Alltrim(StrTran(SB1->B1_DESC, '"', '')) // Variável para limpar descrição do produto
Local cResultado := "" // Resultado da requisição
Local cGtin := "" // Variável para armazenar o Gtin do produto
Local jResponse := {} // Response do json
Local cBasicGs1 := u_zGetMV("MV_X_GS1AU") // String em Base64 da Autenticação Basic no GS1
Local cCliIdGs1 := u_zGetMV("MV_X_GS1ID") // String com o client_id
Local cMessage := "" // Variável para armazenar erro de Gateway
aAdd(aHeader, 'Content-Type: application/json')
aAdd(aHeader, 'Authorization: Basic ' + cBasicGs1)
aAdd(aHeader, 'client_id: ' + cCliIdGs1)
aAdd(aHeader, 'access_token:' + cToken)
// setando o path do diretório
oRestUrl:setPath("gs1/v2/products")
// JSON para cadastrar produto
cJsonText := '{ ' + CRLF
cJsonText += ' "company":{ ' + CRLF
cJsonText += ' "cad":"A64812" ' + CRLF
cJsonText += ' }, ' + CRLF
cJsonText += ' "gtinStatusCode":"ACTIVE", ' + CRLF
cJsonText += ' "tradeItemDescriptionInformationLang":[ ' + CRLF
cJsonText += ' { ' + CRLF
cJsonText += ' "tradeItemDescription":"' + cB1Desc + '", ' + CRLF // SB1->B1_DESC
cJsonText += ' "languageCode":"pt-BR", ' + CRLF
cJsonText += ' "default":true ' + CRLF
cJsonText += ' } ' + CRLF
cJsonText += ' ], ' + CRLF
cJsonText += ' "gs1TradeItemIdentificationKey":{ ' + CRLF
cJsonText += ' "gs1TradeItemIdentificationKeyCode":"GTIN_13" ' + CRLF
cJsonText += ' }, ' + CRLF
cJsonText += ' "referencedFileInformations":[ ' + CRLF
cJsonText += ' { ' + CRLF
cJsonText += ' "uniformResourceIdentifier":"http://site.com.br/produtos/' + Alltrim(SB1->B1_COD) + '.jpg", ' + CRLF
cJsonText += ' "referencedFileTypeCode":"PLANOGRAM", ' + CRLF
cJsonText += ' "featuredFile":true ' + CRLF
cJsonText += ' } ' + CRLF
cJsonText += ' ], ' + CRLF
cJsonText += ' "tradeItemMeasurements":{ ' + CRLF
cJsonText += ' "netContent":{ ' + CRLF
cJsonText += ' "measurementUnitCode":"GRM", ' + CRLF
cJsonText += ' "value":' + cValToChar(SB1->B1_PESO) + CRLF // cValToChar(SB1->B1_PESO)
cJsonText += ' } ' + CRLF
cJsonText += ' }, ' + CRLF
cJsonText += ' "tradeItemWeight":{ ' + CRLF
cJsonText += ' "grossWeight":{ ' + CRLF
cJsonText += ' "value":' + cValToChar(SB1->B1_PESBRU) + ',' + CRLF // cValToChar(SB1->B1_PESBRU)
cJsonText += ' "measurementUnitCode":"KGM" ' + CRLF
cJsonText += ' } ' + CRLF
cJsonText += ' }, ' + CRLF
cJsonText += ' "brandNameInformationLang":[ ' + CRLF
cJsonText += ' { ' + CRLF
cJsonText += ' "brandName":"' + Alltrim(Posicione("SA2", 1, FWxFilial("SA2") + SB1->B1_PROC, "A2_NOME")) + '", ' + CRLF // Posicione("SA2", 1, FWxFilial("SA2") + SB1->B1_PROC, "A2_NOME")
cJsonText += ' "languageCode":"pt-BR", ' + CRLF
cJsonText += ' "default":true ' + CRLF
cJsonText += ' } ' + CRLF
cJsonText += ' ], ' + CRLF
cJsonText += ' "tradeItemClassification":{ ' + CRLF
cJsonText += ' "additionalTradeItemClassifications":[ ' + CRLF
cJsonText += ' { ' + CRLF
cJsonText += ' "additionalTradeItemClassificationSystemCode":"NCM", ' + CRLF
cJsonText += ' "additionalTradeItemClassificationCodeValue":"' + Alltrim(Transform(SB1->B1_POSIPI, PesqPict("SB1", "B1_POSIPI"))) + '"' + CRLF // SB1->B1_POSIPI Alltrim(Transform(SB1->B1_POSIPI, PesqPict("SB1", "B1_POSIPI")))
cJsonText += ' }, ' + CRLF
cJsonText += ' { ' + CRLF
cJsonText += ' "additionalTradeItemClassificationCodeValue":"' + Alltrim(Transform(SB1->B1_CEST, PesqPict("SB1", "B1_CEST"))) + '", ' + CRLF // SB1->B1_CEST
cJsonText += ' "additionalTradeItemClassificationSystemCode":"CEST" ' + CRLF
cJsonText += ' } ' + CRLF
cJsonText += ' ], ' + CRLF
cJsonText += ' "gpcCategoryCode":"77000000" ' + CRLF
cJsonText += ' }, ' + CRLF
cJsonText += ' "tradeItem":{ ' + CRLF
cJsonText += ' "targetMarket":{ ' + CRLF
cJsonText += ' "targetMarketCountryCodes":[ ' + CRLF
cJsonText += ' "076" ' + CRLF
cJsonText += ' ] ' + CRLF
cJsonText += ' }, ' + CRLF
cJsonText += ' "tradeItemUnitDescriptorCode":"PALLET" ' + CRLF
cJsonText += ' }, ' + CRLF
cJsonText += ' "acceptResponsibility":true, ' + CRLF
cJsonText += ' "shareDataIndicator":true, ' + CRLF
cJsonText += ' "placeOfProductActivity":{ ' + CRLF
cJsonText += ' "countryOfOrigin":{ ' + CRLF
cJsonText += ' "countryCode":"076", ' + CRLF
cJsonText += ' "countrySubdivisionCodes":[ ' + CRLF
cJsonText += ' ' + CRLF
cJsonText += ' ] ' + CRLF
cJsonText += ' } ' + CRLF
cJsonText += ' } ' + CRLF
cJsonText += '} ' + CRLF
// Setando os parâmetros da variável para o objeto
oRestUrl:SetPostParams(cJsonText)
jResponse := JsonObject():New()
// Realiza a requisição POST
If oRestUrl:Post(aHeader)
cResultado := oRestUrl:GetResult()
// Pega o JSON de resposta
jResponse:FromJson(cResultado)
// Acessa o valor do campo "gtin" dentro do objeto aninhado
If ValType(jResponse["product"]) != "U" .AND. ValType(jResponse["product"]["gs1TradeItemIdentificationKey"]) != "U"
cGtin := Iif(ValType(jResponse["product"]["gs1TradeItemIdentificationKey"]["gtin"]) != "U", jResponse["product"]["gs1TradeItemIdentificationKey"]["gtin"], "")
//Atualiza o produto
RecLock("SB1", .F.)
SB1->B1_CODGTIN := cGtin
SB1->(MsUnlock())
//Atualiza o complemento do produto
DbSelectArea("SB5")
SB5->(DbSetOrder(1))
If SB5->(MsSeek(FWxFilial("SB5") + SB1->B1_COD))
RecLock("SB5", .F.)
SB5->B5_2CODBAR := cGtin
SB5->(MsUnlock())
EndIf
cRetorno := "GTIN cadastrado com sucesso: " + cGtin
EndIf
Else
cErro := oRestUrl:GetResult()
jResponse:FromJson(cErro)
If Empty(cErro)
cErro := oRestUrl:GetLastError()
EndIf
If "GATEWAY" $ Upper(cErro)
cMessage := "(Produto não possui imagem na url site.com.br)"
ElseIf !Empty(jResponse["data"]["validations"][1]["message"])
cMessage := DecodeUTF8(jResponse["data"]["validations"][1]["message"])
EndIf
cRetorno := "Houve uma falha na geração do Gtin. " + cMessage
EndIf
Return
Bom pessoal, por hoje é só.
Abraços e até a próxima.