UNPKG

winthor-api-wta

Version:

Pacote Integração com API do WTA do ERP Winthor

438 lines (381 loc) 15.5 kB
const axios = require('axios'); const { createHash } = require('crypto'); const { Cliente } = require('./utils'); //assim cria o construtor vai criar a senha MD5 e o htttpclient (requisição obter token no login) class WinthorWTA { constructor (host, login, senha) { // Se o primeiro parâmetro for um objeto, extrai as propriedades dele if (typeof host === 'object' && host !== null) { ({ host, login, senha } = host); } this._validateConstructorParams(host, login, senha); // Remove barras finais do host se existirem this.host = host.replace(/\/+$/, ''); this.login = login; this.senha = senha; this.accessToken = null; this.httpClient = this._createHttpClient(); } /** * Valida os parâmetros do construtor * @private */ _validateConstructorParams (host, login, senha) { if (!host || !login || !senha) { throw new Error('Host, login e senha são obrigatórios'); } if (typeof host !== 'string' || typeof login !== 'string' || typeof senha !== 'string') { throw new Error('Host, login e senha devem ser strings'); } } /** * Cria o cliente HTTP com configurações padrão * @private */ _createHttpClient (headers = {}) { return axios.create({ baseURL: this.host, headers: { 'Content-Type': 'application/json', ...headers, }, timeout: 30000, // 30 segundos validateStatus: (status) => status < 500, // Aceita apenas status < 500 }); } /** * Converte a senha para MD5 e depois para maiúsculas * @param {string} senha * @returns {string} */ _encryptPassword (senha) { const md5 = createHash('md5').update(senha.toUpperCase()).digest('hex'); return md5.toUpperCase(); } /** * Processa parâmetros de consulta com mapeamento * @private */ _processParams (params, paramMap) { const processedParams = {}; for (const [key, value] of Object.entries(params)) { const paramKey = paramMap[key] || key; processedParams[paramKey] = typeof value === 'boolean' ? value.toString() : value; } return processedParams; } /** * Cria query string a partir de parâmetros * @private */ _buildQueryString (params) { return Object.entries(params) .map(([key, val]) => `${key}=${val}`) .join('&') .replace(/%2C/g, ','); } /** * Valida se está autenticado * @private */ _validateAuthentication () { if (!this.accessToken) { throw new Error('Não autenticado. Chame o método conectar() primeiro.'); } } /** * Trata erros de resposta da API * @private */ _handleApiError (error, operation) { if (error.response) { const { status, data } = error.response; const message = data.detailedMessage ? `${operation}: ${status} - ${data.message} - Detalhe: ${data.detailedMessage}` : `${operation}: ${status} - ${data.message}`; throw new Error(message); } throw new Error(`Erro na requisição: ${error.message}`); } /** * Realiza a autenticação na API * @returns {Promise<string>} accessToken */ async conectar () { try { const senhaMd5 = this._encryptPassword(this.senha); const response = await this.httpClient.post('/winthor/autenticacao/v1/login', { login: this.login, senha: senhaMd5, }); if (response.status === 200 && response.data.accessToken) { this.accessToken = response.data.accessToken; // Atualiza o httpClient com o token de acesso this.httpClient = this._createHttpClient({ 'Authorization': `Bearer ${this.accessToken}`, }); return this.accessToken; } else { throw new Error('Falha na autenticação: resposta inesperada da API'); } } catch (error) { this._handleApiError(error, 'Erro na autenticação'); } } /** * Realiza a desconexão na API */ async desconectar () { try { this._validateAuthentication(); const response = await this.httpClient.get('/winthor/autenticacao/v1/logout'); if (response.status === 200) { // Atualiza o httpClient sem o token de acesso this.httpClient = this._createHttpClient(); this.accessToken = null; } else { throw new Error('Falha na desconexão: resposta inesperada da API'); } } catch (error) { this._handleApiError(error, 'Erro na desconexão'); } } /** * Lista Filiais com o parâmetro opcional * @param {Object} params - Parâmetros de filtro * @param {string} [params.codigofilial] - Código do filial deseja buscar (ex: '1') * @returns {Promise<Array>} Lista de Filiais */ async filiais (params = {}) { this._validateAuthentication(); const paramMap = { 'codigofilial': 'id', }; try { const processedParams = this._processParams(params, paramMap); const queryString = this._buildQueryString(processedParams); const url = `/api/branch/v1/${queryString ? `?${queryString}` : ''}`; const response = await this.httpClient.get(url); return response.data; } catch (error) { this._handleApiError(error, 'Erro ao buscar filiais'); } } /** * Lista clientes com os parâmetros opcionais * @param {Object} params - Parâmetros de filtro * @param {boolean} [params.enderecoEntrega] - Retornar endereços de entrega? * @param {string} [params.filiais] - Códigos de filiais separados por vírgula * @param {'S'|'N'} [params.consumidorFinal] - 'S' para consumidor final, 'N' para não consumidor * @param {'J'|'F'} [params.tipoPessoa] - 'J' para pessoa jurídica, 'F' para física * @param {string} [params.cpfCnpj] - CPF ou CNPJ do cliente * @returns {Promise<Array>} Lista de clientes */ async clientes (params = {}) { this._validateAuthentication(); const paramMap = { 'enderecoEntrega': 'withDeliveryAddress', 'filiais': 'branchId', 'consumidorFinal': 'finalCostumer', 'tipoPessoa': 'personalType', 'cpfCnpj': 'personIdentificationNumber', }; // Validação dos parâmetros this._validateClienteParams(params); try { const processedParams = this._processParams(params, paramMap); const queryString = this._buildQueryString(processedParams); const url = `/api/wholesale/v1/customer/list${queryString ? `?${queryString}` : ''}`; const response = await this.httpClient.get(url); return response.data; } catch (error) { this._handleApiError(error, 'Erro ao buscar clientes'); } } /** * Valida parâmetros específicos de clientes * @private */ _validateClienteParams (params) { if (params.tipoPessoa && !['J', 'F'].includes(params.tipoPessoa)) { throw new Error('tipoPessoa deve ser "J" (Jurídica) ou "F" (Física)'); } if (params.consumidorFinal && !['S', 'N'].includes(params.consumidorFinal)) { throw new Error('consumidorFinal deve ser "S" (Sim) ou "N" (Não)'); } } /** * Lista cliente específico * @param {Object} params - Parâmetros de filtro * @param {string} params.codigocliente - Código do cliente (obrigatório) * @param {boolean} [params.enderecoEntrega] - Retornar endereços de entrega? * @returns {Promise<Object>} Dados do cliente */ async cliente (params = {}) { this._validateAuthentication(); if (!params.codigocliente) { throw new Error('Parâmetro codigocliente é obrigatório.'); } const paramMap = { 'enderecoEntrega': 'withDeliveryAddress', 'codigocliente': 'customerId', }; try { const processedParams = this._processParams(params, paramMap); const queryString = this._buildQueryString(processedParams); const url = `/api/wholesale/v1/customer/${queryString ? `?${queryString}` : ''}`; const response = await this.httpClient.get(url); return response.data; } catch (error) { this._handleApiError(error, 'Erro ao buscar cliente'); } } /** * Lista Estoque de Produtos * @param {Object} params - Parâmetros de filtro * @param {string} params.codigofilial - Código do filial (obrigatório) * @param {string} [params.codigoproduto] - Código do produto * @returns {Promise<Array>} Lista de Estoque produto */ async estoque (params = {}) { this._validateAuthentication(); if (!params.codigofilial && !params.branchId) { throw new Error('Parâmetro codigofilial é obrigatório.'); } const paramMap = { 'codigofilial': 'branchId', 'codigoproduto': 'productId', }; try { const processedParams = this._processParams(params, paramMap); const queryString = this._buildQueryString(processedParams); const url = `/api/stock-vtex/v1/available/list${queryString ? `?${queryString}` : ''}`; const response = await this.httpClient.get(url); return response.data; } catch (error) { this._handleApiError(error, 'Erro ao buscar estoque'); } } /** * Lista Preço de Produto * @param {Object} params - Parâmetros de filtro * @param {string} params.codigofilial - Código do filial (obrigatório) * @param {string} params.codigoproduto - Código do produto (obrigatório) * @param {boolean} [params.precoporembalagem] - Retornar o preço dividindo pelo fator conversão da embalagem? * @returns {Promise<Array>} Lista de Preços */ async preco (params = {}) { this._validateAuthentication(); if (!params.codigofilial) { throw new Error('Parâmetro codigofilial é obrigatório.'); } if (!params.codigoproduto) { throw new Error('Parâmetro codigoproduto é obrigatório.'); } const paramMap = { 'codigofilial': 'branchId', 'codigoproduto': 'productId', 'precoporembalagem': 'useMultiplePricesPerProductPackage', }; try { const processedParams = this._processParams(params, paramMap); const queryString = this._buildQueryString(processedParams); const url = `/api/wholesale/v1/price/list${queryString ? `?${queryString}` : ''}`; const response = await this.httpClient.get(url); return response.data; } catch (error) { this._handleApiError(error, 'Erro ao buscar preços'); } } /** * Busca o XML de uma Nota Fiscal * @param {Object} params - Parâmetros de filtro * @param {string} [params.numeropedido] - Número do pedido de venda * @param {string} [params.numerotransacao] - Número da transação de venda * @param {boolean} [params.retornabase64] - Retornar em base64 (default: false) * @returns {Promise<Object>} XML da nota fiscal */ async buscaXml (params = {}) { this._validateAuthentication(); if (!params.numeropedido && !params.numerotransacao) { throw new Error('Parâmetro numeropedido ou numerotransacao é obrigatório.'); } const paramMap = { 'numeropedido': 'orderId', 'numerotransacao': 'transactionId', 'retornabase64': 'returnBase64', }; try { const processedParams = this._processParams(params, paramMap); const queryString = this._buildQueryString(processedParams); const url = `/winthor/fiscal/v1/documentosfiscais/nfe/invoiceDocument${queryString ? `?${queryString}` : ''}`; const response = await this.httpClient.get(url); return response.data; } catch (error) { this._handleApiError(error, 'Erro ao buscar XML'); } } /** * Cadastrar ou Atualizar Cliente com parâmetros * @description * - Se o CNPJ já existir na base, a função ATUALIZARÁ o cliente existente com os novos dados. * - Campos obrigatórios devem ser informados mesmo em atualizações. * @param {Object} params - Parâmetros de filtro * @param {number} params.CODIGOATIVIDADE - Código da atividade (Obrigatório) * @param {string} params.ENDERECOCOBRANCA - Endereço de cobrança (40 caracteres, Obrigatório) * @param {string} params.NUMEROCOBRANÇA - Número do endereço de cobrança (6 caracteres) * @param {string} params.BAIRROCOBRANCA - Bairro de cobrança (40 caracteres) * @param {string} params.CODCOB - Código de cobrança (4 caracteres) * @param {string} params.UFCOBRANCA - UF de cobrança (2 caracteres) * @param {string} params.CEPCOBRANCA - CEP de cobrança (9 caracteres, Obrigatório) * @param {string} params.BAIRROCOMERCIAL - Bairro comercial (40 caracteres, Obrigatório) * @param {string} params.ESTENT - Estado comercial (2 caracteres) * @param {number} params.CODIGOCIDADE - Código da cidade (Obrigatório) * @param {string} params.ENDERECOCOMERCIAL - Endereço comercial (40 caracteres, Obrigatório) * @param {string} params.NUMNEROCOMERCIAL - Número do endereço comercial (6 caracteres) * @param {string} params.CEPCOMERCIAL - CEP comercial (9 caracteres, Obrigatório) * @param {string} params.COMPLEMENTOCOBRANCA - Complemento do endereço de cobrança (80 caracteres) * @param {string} params.COMPLEMENTOCOMERCIAL - Complemento do endereço comercial (80 caracteres) * @param {string} params.COMPLEMENTOENTREGA - Complemento do endereço de entrega (80 caracteres) * @param {boolean} params.CONTRIBUINTE - Indica se é contribuinte * @param {boolean} params.TIPOPESSOA - Tipo de pessoa (Jurídica 'J' ou Física 'F') * @param {string} params.TELEFONECOMERCIAL - Telefone comercial (13 caracteres) * @param {string} params.TELEFONEENTREGA - Telefone para entrega (13 caracteres) * @param {string} params.TELEFONECOBRANCA - Telefone para cobrança (13 caracteres) * @param {number} params.CODIGOPAIS - Código do país (Default: 1058 - Brasil, Obrigatório) * @param {string} params.EMAIL - E-mail (100 caracteres, Obrigatório) * @param {string} params.EMAILNFE - E-mail para NFe (3500 caracteres) * @param {boolean} params.CONSUMIDORFINAL - Indica se é consumidor final * @param {string} params.NOMECLIENTE - Nome do cliente (60 caracteres, Obrigatório) * @param {number} params.CODIGOPLANOPAGAMENTO - Código do plano de pagamento * @param {string} params.CNPJ - CNPJ/CPF (18 caracteres, Obrigatório) * @param {number} params.CODIGOVENDEDOR - Código do vendedor (Obrigatório) * @param {number} params.CODIGOPRACA - Código da praça (Obrigatório) * @param {number} params.INSCRICAOESTADUAL - Inscrição estadual (Obrigatório) Caso não tenha, informar ISENTO * @param {boolean} params.CALCULAST - Indica cálculo de ST (Default: 'S') * @param {string} params.NOMEFANTASIA - Nome fantasia (40 caracteres) * @param {number} params.CODIGOREDE - Código da rede * @returns {Promise<Array>} Lista de Estoque produto * @example * // Exemplo de uso: * await wta.cadastrarAtualizarCliente({ * CNPJ: '12345678901234' * }); */ async cadastrarAtualizarCliente (params = {}) { this._validateAuthentication(); if (Object.keys(params).length === 0) { throw new Error('Parâmetros são obrigatórios.'); } try { const cliente = new Cliente(params); const url = '/api/wholesale/v1/customer/'; const response = await this.httpClient.post(url, cliente); return response.data; } catch (error) { this._handleApiError(error, 'Erro ao cadastrar/atualizar cliente'); } } // Outros métodos da API podem ser adicionados aqui... } module.exports = WinthorWTA;