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.