1057 lines
42 KiB
JavaScript
1057 lines
42 KiB
JavaScript
const axios = require('axios');
|
|
const https = require('https');
|
|
const logger = require('../utils/logger');
|
|
|
|
class TopSappAPI {
|
|
constructor() {
|
|
this.instances = {
|
|
'guarai_colmeia': {
|
|
baseURL: 'https://131.0.192.6:9910',
|
|
name: 'Guarai e Colmeia',
|
|
cities: ['guarai', 'colmeia']
|
|
},
|
|
'pedro_afonso': {
|
|
baseURL: 'https://45.189.140.18:9910',
|
|
name: 'Pedro Afonso',
|
|
cities: ['pedro afonso', 'pedro_afonso']
|
|
}
|
|
};
|
|
|
|
this.sessions = new Map(); // Armazenar sessões ativas
|
|
|
|
// Configurar axios para ignorar certificados SSL auto-assinados
|
|
this.axiosConfig = {
|
|
httpsAgent: new https.Agent({
|
|
rejectUnauthorized: false
|
|
}),
|
|
timeout: 30000
|
|
// Não definir Content-Type, deixar o FormData fazer isso automaticamente
|
|
};
|
|
|
|
console.log('🔗 TopSappAPI inicializado');
|
|
}
|
|
|
|
/**
|
|
* Determina qual instância usar baseada na cidade/região
|
|
*/
|
|
getInstanceByLocation(location = '') {
|
|
const loc = location.toLowerCase();
|
|
|
|
// Pedro Afonso
|
|
if (loc.includes('pedro') || loc.includes('afonso')) {
|
|
return this.instances.pedro_afonso;
|
|
}
|
|
|
|
// Guarai e Colmeia (padrão)
|
|
return this.instances.guarai_colmeia;
|
|
}
|
|
|
|
/**
|
|
* Realizar login na API
|
|
*/
|
|
async login(instance, credentials) {
|
|
try {
|
|
console.log(`🔐 Fazendo login na instância: ${instance.name}`);
|
|
console.log(`📍 URL: ${instance.baseURL}/Login`);
|
|
console.log(`👤 Usuario: ${credentials.usuario}`);
|
|
|
|
const formData = new FormData();
|
|
formData.append('usuario', credentials.usuario);
|
|
formData.append('senha', credentials.senha);
|
|
formData.append('identificador', credentials.identificador || 'BotTelegram');
|
|
|
|
const response = await axios.post(
|
|
`${instance.baseURL}/Login`,
|
|
formData,
|
|
{
|
|
...this.axiosConfig,
|
|
timeout: 10000
|
|
}
|
|
);
|
|
|
|
console.log(`📊 Status da resposta: ${response.status}`);
|
|
console.log(`📋 Dados da resposta:`, response.data);
|
|
|
|
if (response.data && response.data.resultado === true) {
|
|
const sessionId = response.data.sessao;
|
|
const idUsuario = response.data.id_usuario;
|
|
|
|
this.sessions.set(instance.baseURL, {
|
|
sessionId,
|
|
idUsuario,
|
|
timestamp: Date.now(),
|
|
instance
|
|
});
|
|
|
|
console.log(`✅ Login realizado com sucesso na instância: ${instance.name}`);
|
|
console.log(`🎫 Sessão: ${sessionId}`);
|
|
console.log(`👤 ID do usuário: ${idUsuario}`);
|
|
return { success: true, sessionId, idUsuario, instance };
|
|
} else {
|
|
const errorMsg = response.data?.msg || 'Login falhou';
|
|
console.log(`❌ Erro no login: ${errorMsg}`);
|
|
throw new Error(errorMsg);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error(`❌ Erro no login da instância ${instance.name}:`, error.message);
|
|
return {
|
|
success: false,
|
|
error: error.message,
|
|
instance
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verificar se a sessão está válida
|
|
*/
|
|
isSessionValid(instanceURL) {
|
|
const session = this.sessions.get(instanceURL);
|
|
if (!session) return false;
|
|
|
|
// Sessão expira em 30 minutos
|
|
const thirtyMinutes = 30 * 60 * 1000;
|
|
return (Date.now() - session.timestamp) < thirtyMinutes;
|
|
}
|
|
|
|
/**
|
|
* Obter sessão válida ou fazer novo login
|
|
*/
|
|
async getValidSession(instance, credentials) {
|
|
if (this.isSessionValid(instance.baseURL)) {
|
|
return this.sessions.get(instance.baseURL);
|
|
}
|
|
|
|
const loginResult = await this.login(instance, credentials);
|
|
if (loginResult.success) {
|
|
return this.sessions.get(instance.baseURL);
|
|
}
|
|
|
|
throw new Error(`Não foi possível autenticar na instância ${instance.name}`);
|
|
}
|
|
|
|
/**
|
|
* Fazer requisição autenticada para a API
|
|
*/
|
|
async makeRequest(endpoint, data = {}, instance, credentials) {
|
|
try {
|
|
const session = await this.getValidSession(instance, credentials);
|
|
|
|
const formData = new FormData();
|
|
formData.append('sessao', session.sessionId);
|
|
formData.append('identificador', credentials.identificador || 'BotTelegram');
|
|
|
|
// Adicionar parâmetros extras
|
|
Object.keys(data).forEach(key => {
|
|
if (data[key] !== undefined && data[key] !== null) {
|
|
// Converter idCliente para inteiro se necessário
|
|
const value = key === 'idCliente' ? parseInt(data[key], 10) : data[key];
|
|
formData.append(key, value.toString());
|
|
}
|
|
});
|
|
|
|
// Log do FormData real que será enviado
|
|
const formDataEntries = {};
|
|
for (const [k, v] of formData.entries()) {
|
|
formDataEntries[k] = v;
|
|
}
|
|
console.log(`📡 Fazendo requisição para: ${instance.baseURL}/${endpoint}`);
|
|
console.log(`📊 FormData real enviado:`, formDataEntries);
|
|
|
|
const response = await axios.post(
|
|
`${instance.baseURL}/${endpoint}`,
|
|
formData,
|
|
this.axiosConfig
|
|
);
|
|
|
|
console.log(`📊 Status da resposta: ${response.status}`);
|
|
console.log(`📋 Dados da resposta:`, response.data);
|
|
|
|
if (response.data && (response.data.resultado === true || response.data.status === true)) {
|
|
return {
|
|
success: true,
|
|
data: response.data,
|
|
instance: instance.name
|
|
};
|
|
} else {
|
|
const errorMsg = response.data?.msg || 'Requisição falhou';
|
|
console.log(`❌ Erro na API: ${errorMsg}`);
|
|
throw new Error(errorMsg);
|
|
}
|
|
|
|
} catch (error) {
|
|
const responseBody = error.response?.data;
|
|
console.error(`❌ Erro na requisição ${endpoint}:`, error.message);
|
|
if (responseBody) {
|
|
console.error(`❌ Corpo do erro ${endpoint}:`, JSON.stringify(responseBody));
|
|
}
|
|
return {
|
|
success: false,
|
|
error: responseBody?.msg || responseBody?.mensagem || error.message,
|
|
instance: instance.name
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Buscar cliente por nome, CPF ou ID
|
|
*/
|
|
async buscarCliente(query, location = '', credentials) {
|
|
const instance = this.getInstanceByLocation(location);
|
|
|
|
try {
|
|
// Obter sessão válida para pegar o idUsuario
|
|
const session = await this.getValidSession(instance, credentials);
|
|
|
|
console.log(`🔍 Buscando cliente: "${query}" na instância: ${instance.name}`);
|
|
|
|
const searchParams = {
|
|
idUsuario: session.idUsuario || 1
|
|
};
|
|
|
|
// Determinar tipo de busca
|
|
const queryLimpo = query.trim();
|
|
|
|
if (/^\d+$/.test(queryLimpo)) {
|
|
// Apenas números - pode ser CPF ou ID
|
|
if (queryLimpo.length === 11 || queryLimpo.length === 14) {
|
|
// CPF (11 dígitos) ou CNPJ (14 dígitos)
|
|
console.log(`📋 Buscando por CPF/CNPJ: ${queryLimpo}`);
|
|
searchParams.cpfCnpj = queryLimpo;
|
|
} else {
|
|
// ID do cliente
|
|
console.log(`🆔 Buscando por ID: ${queryLimpo}`);
|
|
searchParams.idCliente = parseInt(queryLimpo, 10);
|
|
}
|
|
} else {
|
|
// Contém letras - é nome ou CPF formatado
|
|
const apenasNumeros = queryLimpo.replace(/[^\d]/g, '');
|
|
|
|
if (apenasNumeros.length === 11 || apenasNumeros.length === 14) {
|
|
// CPF/CNPJ formatado
|
|
console.log(`📋 Buscando por CPF/CNPJ formatado: ${queryLimpo} -> ${apenasNumeros}`);
|
|
searchParams.cpfCnpj = apenasNumeros;
|
|
} else {
|
|
// Nome do cliente
|
|
console.log(`👤 Buscando por nome: ${queryLimpo}`);
|
|
searchParams.nome = queryLimpo;
|
|
|
|
// Fazer busca sem filtros específicos para pegar todos os clientes
|
|
// e depois filtrar localmente por nome
|
|
delete searchParams.nome; // Remove o parâmetro nome para buscar todos
|
|
}
|
|
}
|
|
|
|
let result;
|
|
|
|
// Se é busca por nome, fazer busca geral e filtrar localmente
|
|
if (!searchParams.cpfCnpj && !searchParams.idCliente) {
|
|
console.log(`🔄 Fazendo busca geral para filtrar por nome: "${queryLimpo}"`);
|
|
|
|
// Buscar todos os clientes
|
|
result = await this.makeRequest('ObterClientes', searchParams, instance, credentials);
|
|
|
|
if (result.success && result.data.dados) {
|
|
const todosClientes = result.data.dados;
|
|
console.log(`📊 Total de clientes retornados: ${todosClientes.length}`);
|
|
|
|
// Filtrar por nome localmente com diferentes estratégias
|
|
const nomeQuery = queryLimpo.toLowerCase()
|
|
.normalize('NFD')
|
|
.replace(/[\u0300-\u036f]/g, ''); // Remove acentos
|
|
|
|
const clientesFiltrados = todosClientes.filter(cliente => {
|
|
const nomeCliente = (cliente.nome || '').toLowerCase()
|
|
.normalize('NFD')
|
|
.replace(/[\u0300-\u036f]/g, ''); // Remove acentos
|
|
|
|
// Estratégias de busca:
|
|
// 1. Nome completo exato
|
|
if (nomeCliente === nomeQuery) return true;
|
|
|
|
// 2. Nome contém a busca
|
|
if (nomeCliente.includes(nomeQuery)) return true;
|
|
|
|
// 3. Busca contém o nome (para nomes parciais)
|
|
if (nomeQuery.includes(nomeCliente) && nomeCliente.length > 3) return true;
|
|
|
|
// 4. Busca por palavras individuais
|
|
const palavrasBusca = nomeQuery.split(/\s+/).filter(p => p.length > 2);
|
|
const palavrasNome = nomeCliente.split(/\s+/);
|
|
|
|
// Verificar se todas as palavras da busca estão no nome
|
|
const todasPalavrasEncontradas = palavrasBusca.every(palavraBusca =>
|
|
palavrasNome.some(palavraNome =>
|
|
palavraNome.includes(palavraBusca) || palavraBusca.includes(palavraNome)
|
|
)
|
|
);
|
|
|
|
if (todasPalavrasEncontradas && palavrasBusca.length > 0) return true;
|
|
|
|
// 5. Busca fuzzy - verificar similaridade
|
|
if (this.calcularSimilaridade(nomeCliente, nomeQuery) > 0.8) return true;
|
|
|
|
return false;
|
|
});
|
|
|
|
console.log(`🎯 Clientes filtrados por nome: ${clientesFiltrados.length}`);
|
|
|
|
// Ordenar por relevância (nomes mais similares primeiro)
|
|
clientesFiltrados.sort((a, b) => {
|
|
const nomeA = (a.nome || '').toLowerCase();
|
|
const nomeB = (b.nome || '').toLowerCase();
|
|
const simA = this.calcularSimilaridade(nomeA, nomeQuery);
|
|
const simB = this.calcularSimilaridade(nomeB, nomeQuery);
|
|
return simB - simA; // Ordem decrescente de similaridade
|
|
});
|
|
|
|
// Log dos resultados para debug
|
|
if (clientesFiltrados.length > 0) {
|
|
console.log(`✅ Clientes encontrados:`);
|
|
clientesFiltrados.slice(0, 5).forEach((cliente, index) => {
|
|
console.log(` ${index + 1}. ${cliente.nome} (ID: ${cliente.id})`);
|
|
});
|
|
} else {
|
|
console.log(`❌ Nenhum cliente encontrado para: "${queryLimpo}"`);
|
|
|
|
// Log de amostra para debug
|
|
console.log(`🔍 Amostra de nomes disponíveis:`);
|
|
todosClientes.slice(0, 5).forEach((cliente, index) => {
|
|
console.log(` ${index + 1}. ${cliente.nome}`);
|
|
});
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
clientes: clientesFiltrados.map(cliente => ({
|
|
...cliente,
|
|
cpf: cliente.cpfCnpj || cliente.cpf || cliente.documento || '',
|
|
status: this.interpretarStatusCliente(cliente),
|
|
statusOriginal: cliente.status,
|
|
id_status: cliente.id_status,
|
|
bloqueado: cliente.bloqueado,
|
|
ativo: cliente.ativo
|
|
})),
|
|
instance: result.instance,
|
|
debug: {
|
|
totalOriginal: todosClientes.length,
|
|
totalFiltrado: clientesFiltrados.length,
|
|
termoBusca: queryLimpo
|
|
}
|
|
};
|
|
}
|
|
} else {
|
|
// Busca específica (CPF/ID)
|
|
result = await this.makeRequest('ObterClientes', searchParams, instance, credentials);
|
|
}
|
|
|
|
if (result.success) {
|
|
// Normalizar dados dos clientes para incluir CPF e status correto
|
|
const clientesNormalizados = (result.data.dados || []).map(cliente => ({
|
|
...cliente,
|
|
cpf: cliente.cpfCnpj || cliente.cpf || cliente.documento || '',
|
|
status: this.interpretarStatusCliente(cliente),
|
|
statusOriginal: cliente.status,
|
|
id_status: cliente.id_status,
|
|
bloqueado: cliente.bloqueado,
|
|
ativo: cliente.ativo
|
|
}));
|
|
|
|
return {
|
|
success: true,
|
|
clientes: clientesNormalizados,
|
|
instance: result.instance
|
|
};
|
|
}
|
|
|
|
return result;
|
|
|
|
} catch (error) {
|
|
console.error(`❌ Erro na busca de cliente:`, error);
|
|
return {
|
|
success: false,
|
|
error: error.message,
|
|
instance: instance.name
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obter dados detalhados do cliente incluindo dados cadastrais completos
|
|
* Usa apenas os dados dos serviços que já contêm informações completas do cliente
|
|
*/
|
|
async obterDadosCompletosCliente(idCliente, location = '', credentials) {
|
|
const instance = this.getInstanceByLocation(location);
|
|
|
|
try {
|
|
console.log(`🔍 Obtendo dados completos do cliente ${idCliente} via serviços (evitando erro 400)`);
|
|
|
|
// Usar apenas dados dos serviços que já contêm informações completas
|
|
const servicosData = await this.obterServicosCliente(idCliente, location, credentials);
|
|
|
|
if (!servicosData.success) {
|
|
console.log(`❌ Falha ao obter serviços: ${servicosData.error}`);
|
|
return servicosData;
|
|
}
|
|
|
|
const servicos = servicosData.data.dados || servicosData.data.servicos || [];
|
|
|
|
if (servicos.length === 0) {
|
|
return {
|
|
success: false,
|
|
error: 'Cliente não encontrado ou sem serviços'
|
|
};
|
|
}
|
|
|
|
// Usar dados do primeiro serviço para extrair informações do cliente
|
|
const primeiroServico = servicos[0];
|
|
console.log(`✅ Extraindo dados do cliente dos serviços: ${primeiroServico.instalacao_nome}`);
|
|
|
|
// Montar dados do cliente baseado nas informações dos serviços
|
|
const cliente = {
|
|
id: primeiroServico.id_cliente,
|
|
nome: primeiroServico.instalacao_nome || 'Nome não informado',
|
|
cpf: primeiroServico.cpf || '',
|
|
rg: primeiroServico.rg || '',
|
|
email: primeiroServico.usuario || '',
|
|
telefone: primeiroServico.instalacao_contato1 || primeiroServico.instalacao_contato || '',
|
|
celular: primeiroServico.instalacao_contato1 || '',
|
|
endereco: primeiroServico.instalacao_endereco || '',
|
|
numero: primeiroServico.instalacao_numero || '',
|
|
bairro: primeiroServico.instalacao_bairro || '',
|
|
cidade: primeiroServico.instalacao_cidade || '',
|
|
uf: primeiroServico.instalacao_uf || '',
|
|
cep: primeiroServico.instalacao_cep || '',
|
|
complemento: primeiroServico.instalacao_complemento || '',
|
|
data_cadastro: primeiroServico.data_cadastro || '',
|
|
observacoes: primeiroServico.observacoes || '',
|
|
// Determinar status baseado nos serviços
|
|
status: this.determinarStatusClientePorServicos(servicos),
|
|
// Dados de localização
|
|
latitude: primeiroServico.latitude || '',
|
|
longitude: primeiroServico.longitude || ''
|
|
};
|
|
|
|
console.log(`✅ Dados do cliente extraídos com sucesso`);
|
|
|
|
return {
|
|
success: true,
|
|
cliente: cliente,
|
|
instance: instance.name,
|
|
fonte: 'servicos'
|
|
};
|
|
|
|
} catch (error) {
|
|
console.error(`❌ Erro ao obter dados completos: ${error.message}`);
|
|
return {
|
|
success: false,
|
|
error: error.message,
|
|
instance: instance.name
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determinar status do cliente baseado nos serviços
|
|
*/
|
|
determinarStatusClientePorServicos(servicos) {
|
|
// Se tem pelo menos um serviço ativo, considera cliente ativo
|
|
const temServicoAtivo = servicos.some(servico =>
|
|
servico.id_status === 1 || servico.status === 'liberado'
|
|
);
|
|
|
|
return temServicoAtivo ? 1 : 0;
|
|
}
|
|
|
|
/**
|
|
* Obter dados detalhados do cliente
|
|
*/
|
|
async obterDadosCliente(idCliente, location = '', credentials) {
|
|
const instance = this.getInstanceByLocation(location);
|
|
|
|
return await this.makeRequest('ObterClientes', { idCliente }, instance, credentials);
|
|
}
|
|
|
|
/**
|
|
* Obter serviços do cliente
|
|
*/
|
|
async obterServicosCliente(idCliente, location = '', credentials) {
|
|
const instance = this.getInstanceByLocation(location);
|
|
|
|
// Obter o idUsuario da sessão atual
|
|
const session = await this.getValidSession(instance, credentials);
|
|
|
|
console.log(`🔍 Obtendo serviços para cliente: ${idCliente} (tipo: ${typeof idCliente})`);
|
|
|
|
const clienteIdInt = parseInt(idCliente, 10);
|
|
|
|
// Tentar diferentes abordagens para obter serviços do cliente
|
|
let response = null;
|
|
let tentativas = [
|
|
// Tentativa 1: Endpoint padrão com idCliente
|
|
{ endpoint: 'ObterClientesServicos', data: { idCliente: clienteIdInt, idUsuario: session.idUsuario || 1 } },
|
|
// Tentativa 2: Endpoint alternativo (se existir)
|
|
{ endpoint: 'ObterServicosCliente', data: { idCliente: clienteIdInt, idUsuario: session.idUsuario || 1 } },
|
|
// Tentativa 3: Com parâmetros alternativos
|
|
{ endpoint: 'ObterClientesServicos', data: { cliente_id: clienteIdInt, idUsuario: session.idUsuario || 1 } }
|
|
];
|
|
|
|
logger.debug('Iniciando tentativas de busca de serviços', {
|
|
clienteIdInt,
|
|
sessionId: session.idUsuario,
|
|
tentativasCount: tentativas.length
|
|
});
|
|
|
|
for (let i = 0; i < tentativas.length; i++) {
|
|
const tentativa = tentativas[i];
|
|
try {
|
|
console.log(`📋 Tentativa ${i + 1} - Endpoint: ${tentativa.endpoint}, Dados:`, tentativa.data);
|
|
logger.debug(`Tentativa ${i + 1}`, {
|
|
endpoint: tentativa.endpoint,
|
|
data: tentativa.data
|
|
});
|
|
|
|
response = await this.makeRequest(tentativa.endpoint, tentativa.data, instance, credentials);
|
|
logger.apiCall(tentativa.endpoint, tentativa.data, response);
|
|
|
|
if (response.success && response.data) {
|
|
console.log(`✅ Sucesso na tentativa ${i + 1} - Total de itens: ${response.data.dados?.length || response.data.length || 0}`);
|
|
|
|
// Normalizar os dados de status dos serviços
|
|
if (response.data.dados && Array.isArray(response.data.dados)) {
|
|
response.data.dados = response.data.dados.map(servico => ({
|
|
...servico,
|
|
// Garantir que id_status está consistente
|
|
id_status: this.interpretarStatusServico(servico),
|
|
status: this.obterTextoStatus(this.interpretarStatusServico(servico))
|
|
}));
|
|
|
|
console.log(`🔄 Status dos serviços normalizados`);
|
|
}
|
|
|
|
// Verificar se os dados são relevantes para o cliente
|
|
const dados = response.data.dados || response.data;
|
|
if (Array.isArray(dados) && dados.length > 0) {
|
|
// Verificar quantos itens realmente pertencem ao cliente
|
|
const servicosDoCliente = dados.filter(item => {
|
|
const itemClienteId = parseInt(item.idCliente || item.cliente_id || item.id_cliente || 0, 10);
|
|
return itemClienteId === clienteIdInt;
|
|
});
|
|
|
|
console.log(`🎯 Serviços filtrados para cliente ${clienteIdInt}: ${servicosDoCliente.length} de ${dados.length} total`);
|
|
logger.debug('Filtragem de serviços', {
|
|
clienteIdBuscado: clienteIdInt,
|
|
servicosEncontrados: servicosDoCliente.length,
|
|
totalServicos: dados.length,
|
|
primeiroItem: dados[0] ? {
|
|
id_cliente: dados[0].id_cliente,
|
|
idCliente: dados[0].idCliente,
|
|
cliente_id: dados[0].cliente_id
|
|
} : null
|
|
});
|
|
|
|
// Se encontrou serviços específicos do cliente, usar essa resposta
|
|
if (servicosDoCliente.length > 0 || i === tentativas.length - 1) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.log(`❌ Falha na tentativa ${i + 1}: ${error.message}`);
|
|
if (i === tentativas.length - 1) {
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* Interpretar status do serviço corretamente
|
|
*/
|
|
interpretarStatusServico(servico) {
|
|
// Verificar diferentes campos de status
|
|
|
|
// Se tem id_status explícito, usar ele
|
|
if (servico.id_status) {
|
|
return parseInt(servico.id_status, 10);
|
|
}
|
|
|
|
// Interpretar pelo campo status textual
|
|
if (servico.status) {
|
|
const statusLower = servico.status.toLowerCase();
|
|
|
|
if (statusLower.includes('liberado') || statusLower.includes('ativo')) {
|
|
return 1; // Ativo/Liberado
|
|
}
|
|
|
|
if (statusLower.includes('bloqueado')) {
|
|
return 2; // Bloqueado
|
|
}
|
|
|
|
if (statusLower.includes('desativado') || statusLower.includes('inativo')) {
|
|
return 3; // Desativado
|
|
}
|
|
}
|
|
|
|
// Verificar por datas
|
|
if (servico.data_desativacao && servico.data_desativacao !== '0000-00-00' && servico.data_desativacao !== null) {
|
|
return 3; // Desativado
|
|
}
|
|
|
|
if (servico.data_bloqueio && servico.data_bloqueio !== '0000-00-00' && servico.data_bloqueio !== null) {
|
|
// Verificar se foi desbloqueado depois
|
|
if (!servico.data_desbloqueio || servico.data_desbloqueio === '0000-00-00' || servico.data_desbloqueio === null) {
|
|
return 2; // Bloqueado
|
|
}
|
|
|
|
// Se tem data de desbloqueio, verificar qual é mais recente
|
|
if (new Date(servico.data_desbloqueio) < new Date(servico.data_bloqueio)) {
|
|
return 2; // Bloqueado (bloqueio é mais recente)
|
|
}
|
|
}
|
|
|
|
// Por padrão, considerar ativo
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Obter texto descritivo do status
|
|
*/
|
|
obterTextoStatus(idStatus) {
|
|
switch (parseInt(idStatus, 10)) {
|
|
case 1: return 'liberado';
|
|
case 2: return 'bloqueado';
|
|
case 3: return 'desativado';
|
|
default: return 'desconhecido';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obter faturas em aberto do cliente
|
|
* @param {number|string} idServico - opcional, filtra pelo serviço específico
|
|
*/
|
|
async obterFaturas(idCliente, location = '', credentials, idServico = null) {
|
|
const instance = this.getInstanceByLocation(location);
|
|
|
|
// Obter o idUsuario da sessão atual
|
|
const session = await this.getValidSession(instance, credentials);
|
|
|
|
const payload = {
|
|
idCliente,
|
|
idUsuario: session.idUsuario || 1,
|
|
status: 0,
|
|
situacaoFatura: 0
|
|
};
|
|
|
|
if (idServico) {
|
|
payload.idServico = idServico;
|
|
}
|
|
|
|
return await this.makeRequest('ObterDocReceber', payload, instance, credentials);
|
|
}
|
|
|
|
/**
|
|
* Obter dados para pagamento da fatura (linha digitável)
|
|
*/
|
|
async obterDadosFatura(idFatura, idCliente, location = '', credentials) {
|
|
const instance = this.getInstanceByLocation(location);
|
|
|
|
// Obter o idUsuario da sessão atual
|
|
const session = await this.getValidSession(instance, credentials);
|
|
|
|
return await this.makeRequest('ObterDocReceberDadosFatura', {
|
|
idFatura,
|
|
idCliente,
|
|
idUsuario: session.idUsuario || 1
|
|
}, instance, credentials);
|
|
}
|
|
|
|
/**
|
|
* Obter boleto em Base64
|
|
*/
|
|
async obterBoletoBase64(idFatura, location = '', credentials) {
|
|
const instance = this.getInstanceByLocation(location);
|
|
|
|
return await this.makeRequest('ObterDocReceberBase64', { idFatura }, instance, credentials);
|
|
}
|
|
|
|
/**
|
|
* Alterar status do serviço (bloquear/liberar)
|
|
*/
|
|
async alterarStatusServico(idServico, idCliente, status, location = '', credentials) {
|
|
const instance = this.getInstanceByLocation(location);
|
|
|
|
// Obter o idUsuario da sessão atual
|
|
const session = await this.getValidSession(instance, credentials);
|
|
|
|
// Status: 1 = Ativo, 2 = Bloqueado, 3 = Desativado
|
|
return await this.makeRequest('AlterarServicoStatus', {
|
|
idServico,
|
|
idCliente,
|
|
idUsuario: session.idUsuario || 1,
|
|
idStatus: status
|
|
}, instance, credentials);
|
|
}
|
|
|
|
/**
|
|
* Bloquear serviço do cliente
|
|
*/
|
|
async bloquearServico(idServico, idCliente, location = '', credentials) {
|
|
return await this.alterarStatusServico(idServico, idCliente, 2, location, credentials);
|
|
}
|
|
|
|
/**
|
|
* Liberar serviço do cliente
|
|
*/
|
|
async liberarServico(idServico, idCliente, location = '', credentials) {
|
|
return await this.alterarStatusServico(idServico, idCliente, 1, location, credentials);
|
|
}
|
|
|
|
/**
|
|
* Obter técnicos disponíveis
|
|
*/
|
|
async obterTecnicos(location = '', credentials) {
|
|
const instance = this.getInstanceByLocation(location);
|
|
|
|
// Obter o idUsuario da sessão atual
|
|
const session = await this.getValidSession(instance, credentials);
|
|
|
|
return await this.makeRequest('ObterTecnicos', {
|
|
idUsuario: session.idUsuario || 1
|
|
}, instance, credentials);
|
|
}
|
|
|
|
/**
|
|
* Obter operadores disponíveis
|
|
*/
|
|
async obterOperadores(location = '', credentials) {
|
|
const instance = this.getInstanceByLocation(location);
|
|
|
|
// Obter o idUsuario da sessão atual
|
|
const session = await this.getValidSession(instance, credentials);
|
|
|
|
return await this.makeRequest('ObterOperadores', {
|
|
idUsuario: session.idUsuario || 1
|
|
}, instance, credentials);
|
|
}
|
|
|
|
/**
|
|
* Obter funcionários disponíveis
|
|
*/
|
|
async obterFuncionarios(location = '', credentials) {
|
|
const instance = this.getInstanceByLocation(location);
|
|
|
|
// Obter o idUsuario da sessão atual
|
|
const session = await this.getValidSession(instance, credentials);
|
|
|
|
return await this.makeRequest('ObterFuncionarios', {
|
|
idUsuario: session.idUsuario || 1,
|
|
desativado: false
|
|
}, instance, credentials);
|
|
}
|
|
|
|
/**
|
|
* Obter categorias de suporte
|
|
*/
|
|
async obterSuporteCategorias(location = '', credentials) {
|
|
const instance = this.getInstanceByLocation(location);
|
|
|
|
// Obter o idUsuario da sessão atual
|
|
const session = await this.getValidSession(instance, credentials);
|
|
|
|
return await this.makeRequest('ObterSuporteCategorias', {
|
|
idUsuario: session.idUsuario || 1
|
|
}, instance, credentials);
|
|
}
|
|
|
|
/**
|
|
* Obter tipos de atendimento do suporte
|
|
*/
|
|
async obterSuporteTiposAtendimento(location = '', credentials) {
|
|
const instance = this.getInstanceByLocation(location);
|
|
|
|
// Obter o idUsuario da sessão atual
|
|
const session = await this.getValidSession(instance, credentials);
|
|
|
|
const dados = {
|
|
idUsuario: session.idUsuario || 1
|
|
};
|
|
|
|
return await this.makeRequest('ObterSuporteTiposAtendimento', dados, instance, credentials);
|
|
}
|
|
|
|
/**
|
|
* Adicionar novo suporte
|
|
*/
|
|
async adicionarSuporte(dadosSuporte, location = '', credentials) {
|
|
const instance = this.getInstanceByLocation(location);
|
|
|
|
// Obter o idUsuario da sessão atual
|
|
const session = await this.getValidSession(instance, credentials);
|
|
|
|
const dados = {
|
|
idUsuario: session.idUsuario || 1,
|
|
idCliente: dadosSuporte.idCliente,
|
|
idServico: dadosSuporte.idServico,
|
|
idCategoria: dadosSuporte.idCategoria,
|
|
descricao: dadosSuporte.descricao
|
|
};
|
|
|
|
// Tentar com idAtendimento (técnico no sistema de suporte) se fornecido
|
|
if (dadosSuporte.idAtendimento && Number.isInteger(dadosSuporte.idAtendimento)) {
|
|
dados.idAtendimento = dadosSuporte.idAtendimento;
|
|
console.log('🎫 API - Incluindo idAtendimento:', dadosSuporte.idAtendimento);
|
|
}
|
|
|
|
if (dadosSuporte.contato && dadosSuporte.contato.trim() !== '') {
|
|
dados.contato = dadosSuporte.contato;
|
|
}
|
|
|
|
if (dadosSuporte.contatoTelefone && dadosSuporte.contatoTelefone.trim() !== '') {
|
|
dados.contatoTelefone = dadosSuporte.contatoTelefone;
|
|
}
|
|
|
|
if (dadosSuporte.outrasInformacoes && dadosSuporte.outrasInformacoes.trim() !== '') {
|
|
dados.outrasInformacoes = dadosSuporte.outrasInformacoes;
|
|
}
|
|
|
|
console.log('🎫 API - Dados finais para envio:', dados);
|
|
|
|
// Tentativa 1: com idAtendimento
|
|
const result = await this.makeRequest('AdicionarSuporte', dados, instance, credentials);
|
|
if (result.success) {
|
|
console.log('✅ API - Suporte criado com sucesso:', result);
|
|
return result;
|
|
}
|
|
|
|
// Tentativa 2: sem idAtendimento (campo opcional que a API pode rejeitar)
|
|
if (dados.idAtendimento) {
|
|
console.log('🔄 API - Tentando criar suporte SEM idAtendimento...');
|
|
const dadosSemAtendimento = { ...dados };
|
|
delete dadosSemAtendimento.idAtendimento;
|
|
const retryResult = await this.makeRequest('AdicionarSuporte', dadosSemAtendimento, instance, credentials);
|
|
console.log('✅ API - Resultado sem idAtendimento:', retryResult);
|
|
return retryResult;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Encaminhar suporte para um técnico
|
|
*/
|
|
async encaminharSuporte(idSuporte, idTecnico, descricao, location = '', credentials) {
|
|
const instance = this.getInstanceByLocation(location);
|
|
const session = await this.getValidSession(instance, credentials);
|
|
|
|
return await this.makeRequest('EncaminharSuporte', {
|
|
idUsuario: session.idUsuario || 1,
|
|
idSuporte: parseInt(idSuporte, 10),
|
|
idTecnico: parseInt(idTecnico, 10),
|
|
descricao
|
|
}, instance, credentials);
|
|
}
|
|
|
|
/**
|
|
* Obter suportes abertos de um cliente
|
|
*/
|
|
async obterSuportesAbertos(idCliente, location = '', credentials) {
|
|
const instance = this.getInstanceByLocation(location);
|
|
|
|
const session = await this.getValidSession(instance, credentials);
|
|
|
|
return await this.makeRequest('ObterSuporte', {
|
|
idUsuario: session.idUsuario || 1,
|
|
idCliente: parseInt(idCliente, 10),
|
|
status: 0 // 0 = abertos
|
|
}, instance, credentials);
|
|
}
|
|
|
|
/**
|
|
* Finalizar suporte
|
|
*/
|
|
async finalizarSuporte(idSuporte, descricao, location = '', credentials) {
|
|
const instance = this.getInstanceByLocation(location);
|
|
|
|
const session = await this.getValidSession(instance, credentials);
|
|
|
|
return await this.makeRequest('FinalizarSuporte', {
|
|
idUsuario: session.idUsuario || 1,
|
|
idSuporte: parseInt(idSuporte, 10),
|
|
descricao
|
|
}, instance, credentials);
|
|
}
|
|
|
|
/**
|
|
* Obter lista de OLTs cadastradas no TopSapp
|
|
*/
|
|
async obterOlts(location = '', credentials) {
|
|
const instance = this.getInstanceByLocation(location);
|
|
try {
|
|
const session = await this.getValidSession(instance, credentials);
|
|
return await this.makeRequest('ObterOlts', { idUsuario: session.idUsuario || 1 }, instance, credentials);
|
|
} catch (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Atualizar dados FTTH de um serviço (vincular ONU a cliente/serviço)
|
|
*/
|
|
async atualizarFTTH(idCliente, idServico, ftthData, location = '', credentials) {
|
|
const instance = this.getInstanceByLocation(location);
|
|
try {
|
|
const session = await this.getValidSession(instance, credentials);
|
|
return await this.makeRequest('AlterarServico', {
|
|
idUsuario: session.idUsuario || 1,
|
|
idCliente: parseInt(idCliente),
|
|
idServico: parseInt(idServico),
|
|
...ftthData
|
|
}, instance, credentials);
|
|
} catch (error) {
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
// Nota: AlterarServico não suporta campos DHCP (dhcp_username, dhcp_agent_circuit_id, dhcp_liberar).
|
|
// Esses campos só existem no CadastrarClienteServico (criação de serviço novo).
|
|
// A configuração IPoE é gerada pelo bot e deve ser aplicada manualmente no painel TopSapp.
|
|
|
|
/**
|
|
* Listar todas as instâncias disponíveis
|
|
*/
|
|
getInstances() {
|
|
return Object.values(this.instances);
|
|
}
|
|
|
|
/**
|
|
* Converter Base64 para PDF
|
|
*/
|
|
convertBase64ToPDF(base64String, filename = 'boleto.pdf') {
|
|
try {
|
|
// Remove header se presente (data:application/pdf;base64,)
|
|
const base64Data = base64String.replace(/^data:application\/pdf;base64,/, '');
|
|
|
|
// Converte para buffer
|
|
const buffer = Buffer.from(base64Data, 'base64');
|
|
|
|
return {
|
|
success: true,
|
|
buffer,
|
|
filename,
|
|
mimeType: 'application/pdf'
|
|
};
|
|
|
|
} catch (error) {
|
|
return {
|
|
success: false,
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Limpar sessões expiradas
|
|
*/
|
|
clearExpiredSessions() {
|
|
const thirtyMinutes = 30 * 60 * 1000;
|
|
const now = Date.now();
|
|
|
|
for (const [instanceURL, session] of this.sessions.entries()) {
|
|
if ((now - session.timestamp) >= thirtyMinutes) {
|
|
this.sessions.delete(instanceURL);
|
|
console.log(`🧹 Sessão expirada removida: ${session.instance.name}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Interpretar status do cliente corretamente
|
|
*/
|
|
interpretarStatusCliente(cliente) {
|
|
// Verificar diferentes campos de status que podem indicar se o cliente está ativo
|
|
|
|
// Se tem campo 'ativo' explícito
|
|
if (typeof cliente.ativo !== 'undefined') {
|
|
return cliente.ativo === 1 || cliente.ativo === true ? 1 : 0;
|
|
}
|
|
|
|
// Se tem campo 'bloqueado'
|
|
if (typeof cliente.bloqueado !== 'undefined') {
|
|
return cliente.bloqueado === 0 || cliente.bloqueado === false ? 1 : 0;
|
|
}
|
|
|
|
// Verificar por id_status (alguns sistemas usam diferentes códigos)
|
|
if (cliente.id_status) {
|
|
// Status 1 = Ativo, 2 = Bloqueado, 3 = Desativado, etc.
|
|
return cliente.id_status === 1 ? 1 : 0;
|
|
}
|
|
|
|
// Se tem data de desativação, está inativo
|
|
if (cliente.data_desativacao && cliente.data_desativacao !== '0000-00-00' && cliente.data_desativacao !== null) {
|
|
return 0;
|
|
}
|
|
|
|
// Se tem data de bloqueio sem data de desbloqueio, está bloqueado
|
|
if (cliente.data_bloqueio && cliente.data_bloqueio !== '0000-00-00' && cliente.data_bloqueio !== null) {
|
|
if (!cliente.data_desbloqueio || cliente.data_desbloqueio === '0000-00-00' || cliente.data_desbloqueio === null) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Por padrão, usar o status original ou assumir ativo se não há informação de bloqueio/desativação
|
|
return cliente.status || 1;
|
|
}
|
|
|
|
/**
|
|
* Obter histórico de acessos por estação/IP (ObterClientesServicosAcessos)
|
|
*/
|
|
async obterAcessos(params, instance, credentials) {
|
|
return this.makeRequest('ObterClientesServicosAcessos', params, instance, credentials);
|
|
}
|
|
|
|
/**
|
|
* Calcular similaridade entre duas strings (algoritmo de Jaro-Winkler simplificado)
|
|
*/
|
|
calcularSimilaridade(str1, str2) {
|
|
if (str1 === str2) return 1.0;
|
|
if (str1.length === 0 || str2.length === 0) return 0.0;
|
|
|
|
// Normalizar strings
|
|
const s1 = str1.toLowerCase().trim();
|
|
const s2 = str2.toLowerCase().trim();
|
|
|
|
if (s1 === s2) return 1.0;
|
|
|
|
// Calcular distância de Levenshtein simplificada
|
|
const maxLen = Math.max(s1.length, s2.length);
|
|
let matches = 0;
|
|
|
|
for (let i = 0; i < Math.min(s1.length, s2.length); i++) {
|
|
if (s1[i] === s2[i]) matches++;
|
|
}
|
|
|
|
// Adicionar pontos por palavras em comum
|
|
const words1 = s1.split(/\s+/);
|
|
const words2 = s2.split(/\s+/);
|
|
|
|
let wordMatches = 0;
|
|
words1.forEach(word1 => {
|
|
if (words2.some(word2 => word1.includes(word2) || word2.includes(word1))) {
|
|
wordMatches++;
|
|
}
|
|
});
|
|
|
|
const charSim = matches / maxLen;
|
|
const wordSim = wordMatches / Math.max(words1.length, words2.length);
|
|
|
|
// Média ponderada: 60% caracteres, 40% palavras
|
|
return (charSim * 0.6) + (wordSim * 0.4);
|
|
}
|
|
}
|
|
|
|
module.exports = TopSappAPI;
|