bling-mcp
Version:
API MCP para integração com Bling
333 lines (286 loc) • 9.6 kB
text/typescript
import { blingClient, renovarToken } from '../blingClient.js';
import axios from 'axios';
// Interfaces migradas do types global
export interface ContatoResponse {
data: Contato[];
pagination: Pagination;
}
export interface Contato {
id: number;
nome: string;
codigo?: string;
situacao?: string;
tipo?: string;
numeroDocumento?: string;
fantasia?: string;
contribuinte?: number;
cpfCnpj?: string;
rgIe?: string;
endereco?: Endereco;
telefone?: string;
celular?: string;
email?: string;
dataNascimento?: string;
sexo?: string;
clienteDesde?: string;
contatoRepresentante?: string;
informacaoContato?: string;
limiteCredito?: number;
paisOrigem?: string;
[key: string]: any;
}
export interface Endereco {
endereco?: string;
numero?: string;
complemento?: string;
bairro?: string;
cep?: string;
municipio?: string;
uf?: string;
pais?: string;
}
export interface Pagination {
page: number;
totalPages: number;
totalItems: number;
limit: number;
}
export interface ListarContatosParams {
pesquisa?: string;
limite?: number;
pagina?: number;
idTipoContato?: number;
idSituacao?: number;
}
export interface ListarContatosResponse {
contatos: Contato[];
total: number;
pagina: number;
totalPaginas: number;
}
/**
* Interface para criação ou atualização de contato
*/
export interface ContatoPayload {
nome: string;
tipo: string;
codigo?: string;
situacao?: string;
numeroDocumento?: string;
fantasia?: string;
telefone?: string;
celular?: string;
email?: string;
endereco?: {
endereco?: string;
numero?: string;
complemento?: string;
bairro?: string;
cep?: string;
municipio?: string;
uf?: string;
pais?: string;
};
[key: string]: any;
}
/**
* Lista os contatos cadastrados no Bling
*
* @param params - Parâmetros para a listagem de contatos
* @returns Resposta com os contatos e informações de paginação
*/
export async function listarContatos(params?: ListarContatosParams): Promise<ContatoResponse> {
try {
console.log(`Buscando contatos com parâmetros:`, JSON.stringify(params, null, 2));
const response = await blingClient.get('/contatos', {
params
});
console.log('Resposta da API de contatos:');
console.log('Status:', response.status);
// Formato real da API é diferente do esperado
// Vamos adaptar para nosso formato
if (response.data && Array.isArray(response.data.data)) {
// Criar uma resposta no formato esperado
return {
data: response.data.data as Contato[],
pagination: {
page: response.data.page || 1,
totalPages: response.data.totalPages || 1,
totalItems: response.data.data.length,
limit: response.data.limit || response.data.data.length
}
};
}
// Se não é um array, vamos ver se é outro formato conhecido
console.log('Verificando outros formatos de resposta');
// Se a própria resposta já é um array, usamos diretamente
if (Array.isArray(response.data)) {
return {
data: response.data,
pagination: {
page: 1,
totalPages: 1,
totalItems: response.data.length,
limit: 50
}
};
}
// Tentar criar uma resposta válida mesmo com formato inesperado
return {
data: Array.isArray(response.data) ? response.data :
(response.data && Array.isArray(response.data.data)) ? response.data.data : [],
pagination: {
page: 1,
totalPages: 1,
totalItems: Array.isArray(response.data) ? response.data.length :
(response.data && Array.isArray(response.data.data)) ? response.data.data.length : 0,
limit: 50
}
};
} catch (error) {
return handleApiError(error, 'listarContatos', params);
}
}
/**
* Obtém um contato específico por ID
*
* @param id - ID do contato
* @returns Dados do contato
*/
export async function obterContato(id: number | string): Promise<Contato> {
try {
console.log(`Buscando contato com ID: ${id}`);
const response = await blingClient.get(`/contatos/${id}`);
console.log('Status:', response.status);
console.log('Estrutura da resposta:', JSON.stringify(Object.keys(response.data), null, 2));
// A API retorna os dados no formato { data: {...} }
if (response.data && response.data.data) {
return response.data.data as Contato;
}
return response.data as Contato;
} catch (error) {
return handleApiError(error, 'obterContato', { id });
}
}
/**
* Cria um novo contato
*
* @param contato - Dados do contato a ser criado
* @returns Contato criado
*/
export async function criarContato(contato: ContatoPayload): Promise<Contato> {
try {
console.log(`Criando novo contato:`, JSON.stringify(contato, null, 2));
const response = await blingClient.post('/contatos', contato);
console.log('Status:', response.status);
console.log('Estrutura da resposta:', JSON.stringify(Object.keys(response.data), null, 2));
// A API retorna os dados no formato { data: {...} }
if (response.data && response.data.data) {
return response.data.data as Contato;
}
return response.data as Contato;
} catch (error) {
return handleApiError(error, 'criarContato', contato);
}
}
/**
* Atualiza um contato existente
*
* @param id - ID do contato
* @param contato - Dados a serem atualizados
* @returns Contato atualizado
*/
export async function atualizarContato(id: number | string, contato: Partial<ContatoPayload>): Promise<Contato> {
try {
console.log(`Atualizando contato ${id}:`, JSON.stringify(contato, null, 2));
const response = await blingClient.put(`/contatos/${id}`, contato);
console.log('Status:', response.status);
console.log('Estrutura da resposta:', JSON.stringify(Object.keys(response.data), null, 2));
// A API retorna os dados no formato { data: {...} }
if (response.data && response.data.data) {
return response.data.data as Contato;
}
return response.data as Contato;
} catch (error) {
return handleApiError(error, 'atualizarContato', { id, contato });
}
}
/**
* Remove um contato
*
* @param id - ID do contato
*/
export async function excluirContato(id: number | string): Promise<void> {
try {
console.log(`Excluindo contato com ID: ${id}`);
await blingClient.delete(`/contatos/${id}`);
console.log('Contato excluído com sucesso');
} catch (error) {
return handleApiError(error, 'excluirContato', { id });
}
}
/**
* Obtém os tipos de contato disponíveis
*/
export async function listarTiposContato(): Promise<any[]> {
try {
console.log('Listando tipos de contato');
const response = await blingClient.get('/tipos-contato');
console.log('Status:', response.status);
if (response.data && Array.isArray(response.data.data)) {
return response.data.data;
}
return Array.isArray(response.data) ? response.data : [];
} catch (error) {
return handleApiError(error, 'listarTiposContato', {});
}
}
/**
* Obtém o contato "consumidor final"
*/
export async function obterConsumidorFinal(): Promise<Contato> {
try {
console.log('Obtendo contato consumidor final');
const response = await blingClient.get('/contatos/consumidor-final');
console.log('Status:', response.status);
if (response.data && response.data.data) {
return response.data.data as Contato;
}
return response.data as Contato;
} catch (error) {
return handleApiError(error, 'obterConsumidorFinal', {});
}
}
/**
* Função auxiliar para tratamento de erros da API
*/
async function handleApiError(error: unknown, funcName: string, params: any): Promise<any> {
if (axios.isAxiosError(error) && error.response?.status === 401) {
// Token expirado, tentar renovar
console.log(`Token expirado (401) em ${funcName}, tentando renovar...`);
try {
await renovarToken();
// Tentar executar a função novamente com os mesmos parâmetros
// Recursão com verificação para evitar loop infinito
if (funcName === 'listarContatos') return listarContatos(params as ListarContatosParams);
if (funcName === 'obterContato') return obterContato(params.id);
if (funcName === 'criarContato') return criarContato(params as ContatoPayload);
if (funcName === 'atualizarContato') return atualizarContato(params.id, params.contato);
if (funcName === 'excluirContato') return excluirContato(params.id);
if (funcName === 'listarTiposContato') return listarTiposContato();
if (funcName === 'obterConsumidorFinal') return obterConsumidorFinal();
throw new Error(`Função desconhecida: ${funcName}`);
} catch (renewError) {
console.error(`Erro ao renovar token em ${funcName}:`, renewError);
throw new Error(`Falha na renovação do token: ${renewError instanceof Error ? renewError.message : 'Erro desconhecido'}`);
}
}
if (axios.isAxiosError(error)) {
console.error(`Erro em ${funcName}:`);
console.error('Status:', error.response?.status);
console.error('Data:', JSON.stringify(error.response?.data, null, 2));
throw new Error(`Erro ao ${funcName}: ${error.response?.data?.message || error.message}`);
}
console.error(`Erro não relacionado ao Axios em ${funcName}:`, error);
throw error;
}