snglpi/src/services/glpiTicketService.js

189 lines
8.7 KiB
JavaScript
Raw Normal View History

2025-08-30 19:04:35 -03:00
// src/services/glpiService.js
const TicketSnModel = require('../models/ticketSnModel');
const TicketGlpiModel = require('../models/ticketGlpiModel');
const TicketUpdateModel = require('../models/ticketUpdateModel');
const TicketSyncModel = require('../models/ticketSyncModel');
const { logInfo, logError } = require('../utils/logger');
const { addWorkNoteToServiceNow } = require('./servicenowService');
2025-08-30 19:04:35 -03:00
// Método principal que o controller vai chamar
const syncTicketsToGlpi = async () => {
try {
logInfo('🔄 Iniciando sincronização para GLPI...');
// 1. Buscar tickets pendentes
const pendingTickets = await TicketSnModel.getPendingTickets();
if (pendingTickets.length === 0) {
logInfo('✅ Nenhum ticket pendente para sincronizar');
return;
}
logInfo(`📋 Encontrados ${pendingTickets.length} tickets para sincronizar`);
2025-10-13 06:06:52 -03:00
// 2. Processar cada ticket
for (const ticket of pendingTickets) {
await processSingleTicket(ticket);
}
logInfo('🎉 Sincronização com GLPI concluída!');
} catch (error) {
logError(error, '❌ Erro na sincronização GLPI');
throw error;
2025-08-30 19:04:35 -03:00
}
};
// Processar um ticket individual
const processSingleTicket = async (ticket) => {
try {
logInfo(`🔍 Processando ticket: ${ticket.ticket_number}`);
// 1. Verifica se o ticket já possui um ID do GLPI no nosso banco de sincronização.
if (ticket.glpi_ticket_id) {
logInfo(`✅ Ticket ${ticket.ticket_number} já possui GLPI ID (${ticket.glpi_ticket_id}) registrado. Apenas atualizando status.`);
await TicketSyncModel.updateStatus(ticket.id, 'synced', 'synced');
return;
}
2025-10-13 06:06:52 -03:00
// 2. Se não tem ID local, verifica se o ticket existe no GLPI (buscando pelo número).
const existingTicket = await TicketGlpiModel.ticketExists(ticket.ticket_number);
await TicketSyncModel.updateLastSync(ticket.id, 'glpi'); // Atualiza o timestamp
2025-10-13 06:06:52 -03:00
if (existingTicket) {
logInfo(`✅ Ticket ${ticket.ticket_number} encontrado no GLPI. Atualizando registro local com o ID: ${existingTicket}`);
// Atualiza nosso registro de sincronização com o ID do GLPI encontrado.
await TicketSyncModel.updateStatusAndGlpiTicket(ticket.id, 'synced', 'synced', existingTicket);
logInfo(`✅ Ticket ${ticket.ticket_number} já existe no GLPI! GLPI ID: ${existingTicket}`);
2025-10-13 06:06:52 -03:00
return;
}
// 3. Converter dados para formato GLPI
const glpiData = {
ticket_number: ticket.ticket_number,
short_description: ticket.short_description,
description: ticket.description,
caller_id: ticket.caller_id,
caller_email: ticket.caller_email,
caller_phone: ticket.telefone,
caller_ramal: ticket.ramal,
type: ticket.tipo,
state: ticket.status,
priority: '3',
location_id: ticket.location_id,
location_name: ticket.location_name,
opened_at: ticket.opened_at,
updated_at: ticket.updated_at
};
// 3. Inserir no GLPI
const glpiTicketId = await TicketGlpiModel.createTicket(glpiData);
await TicketSyncModel.updateLastSync(ticket.id, 'glpi'); // Atualiza o timestamp
// 4. Atualizar registro de sincronização
await TicketSyncModel.updateStatusAndGlpiTicket(ticket.id, 'synced', 'synced', glpiTicketId);
logInfo(`✅ Ticket ${ticket.ticket_number} sincronizado! GLPI ID: ${glpiTicketId}`);
} catch (error) {
logError(error, `❌ Erro no ticket ${ticket.ticket_number}`);
// Se temos um registro de sync, atualizar o status para error
try {
await TicketSyncModel.updateStatus(ticket.id, 'error', 'collected');
} catch (innerError) {
logError(innerError, `❌ Erro ao registrar falha de sincronização para ticket ${ticket.id}`);
}
}
2025-08-30 19:04:35 -03:00
};
const checkGlpiStatusChanges = async () => {
try {
logInfo('📡 Verificando mudanças de status nos tickets do GLPI...');
// 1. Busca todos os tickets que estão sendo monitorados
const ticketsToMonitor = await TicketSyncModel.getTicketsToMonitor();
if (ticketsToMonitor.length === 0) {
logInfo('Nenhum ticket ativo para verificar status no GLPI.');
return;
}
for (const ticket of ticketsToMonitor) {
// 2. Para cada ticket, busca seu status real no GLPI
const glpiStatus = await TicketGlpiModel.getTicketStatus(ticket.glpi_ticket_id);
// 3. Compara o status real com o que temos no nosso banco
const currentDbStatus = ticket.glpi_sync_status;
const glpiStatusAsString = TicketGlpiModel.mapGlpiStatusToString(glpiStatus).toLowerCase().replace(/ /g, '_'); // ex: 'em_atendimento'
// Se o status no GLPI mudou para Solucionado (5) e nosso banco ainda não reflete isso
if (glpiStatus === 5 && currentDbStatus !== 'solved' && currentDbStatus !== 'closed') {
logInfo(`Ticket GLPI ${ticket.glpi_ticket_id} foi Solucionado. Preparando para sincronização...`);
const solution = await TicketGlpiModel.getTicketSolution(ticket.glpi_ticket_id);
// REGRA "FORA DO ESCOPO"
if (solution?.solutiontypes_id === 27) {
logInfo(`REGRA DETECTADA: Ticket ${ticket.glpi_ticket_id} está fora do escopo (SolutionType ID: 27).`);
const justification = solution?.content || 'Ticket marcado como "Fora do escopo" no GLPI.';
await addWorkNoteToServiceNow(ticket.sn_ticket_id, `Ticket fechado no GLPI com a justificativa: ${justification}`);
await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'closed', 'ignored');
await TicketGlpiModel.forceCloseTicket(ticket.glpi_ticket_id);
continue; // Pula para o próximo ticket
}
// FLUXO NORMAL DE RESOLUÇÃO
const sanitizedContent = solution?.content ? stripHTML(solution.content) : 'Chamado solucionado sem notas.';
await TicketUpdateModel.insert({
ticket_sync_id: ticket.id,
source_system: 'glpi',
update_type: 'close_message',
content: sanitizedContent,
author: null,
created_at: solution?.date_mod || new Date(),
source_id: `solution-${ticket.glpi_ticket_id}`,
});
// Marca o ticket para ser resolvido no SN
await TicketSyncModel.updateStatusAndSourceLast(ticket.sn_ticket_id, 'solved', 'pending_solve', 'GLPI');
} else if (glpiStatus === 6 && currentDbStatus !== 'closed') {
logInfo(`Ticket GLPI ${ticket.glpi_ticket_id} foi Fechado permanentemente. Marcando como 'closed'.`);
await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'closed', ticket.sn_sync_status);
} else if (glpiStatus < 5 && glpiStatusAsString !== currentDbStatus && ticket.source_last !== 'GLPI') {
// Para outros status (reabertura, etc.), apenas passa o bastão.
logInfo(`Mudança de status (aberto) detectada no GLPI para o ticket ${ticket.glpi_ticket_id}. De '${currentDbStatus}' para '${glpiStatusAsString}'.`);
await TicketSyncModel.updateSourceLast(ticket.sn_ticket_id, 'GLPI');
}
}
} catch (error) {
logError(error, '🚨Falha ao verificar mudanças de status no GLPI');
}
};
const closeTicketInGlpi = async (ticket) => {
try {
logInfo(`🔒 Iniciando fechamento do chamado GLPI ${ticket.glpi_ticket_id}...`);
// Busca a mensagem de fechamento vinda do ServiceNow
const closeMessageRecord = await TicketUpdateModel.getLatestUpdate(ticket.id, 'servicenow');
const solutionContent = closeMessageRecord?.content || 'Chamado encerrado via integração com o ServiceNow.';
// Chama o model para fechar o ticket no GLPI
await TicketGlpiModel.closeTicket(ticket.glpi_ticket_id, solutionContent);
await TicketSyncModel.updateLastSync(ticket.sn_ticket_id, 'glpi'); // Atualiza o timestamp
logInfo(`✅ Chamado ${ticket.glpi_ticket_id} solucionado com sucesso no GLPI.`);
} catch (error) {
logError(error, `🚨Falha ao fechar chamado no GLPI: ${ticket.glpi_ticket_id}`);
throw error; // Propaga o erro para o controller tratar
}
};
2025-08-30 19:04:35 -03:00
module.exports = {
syncTicketsToGlpi,
checkGlpiStatusChanges,
closeTicketInGlpi
2025-08-30 19:04:35 -03:00
};