145 lines
10 KiB
JavaScript
145 lines
10 KiB
JavaScript
|
|
const TicketSyncModel = require('../models/ticketSyncModel');
|
||
|
|
const TicketSnModel = require('../models/ticketSnModel');
|
||
|
|
const TicketGlpiModel = require('../models/ticketGlpiModel');
|
||
|
|
const TicketUpdateModel = require('../models/ticketUpdateModel');
|
||
|
|
const { logInfo, logError, logWarning } = require('../utils/logger');
|
||
|
|
const { stripHTML } = require('../utils/commentSanitizer');
|
||
|
|
const { closeTicketInServiceNow, updateStatusInServiceNow, addWorkNoteToServiceNow } = require('../services/servicenowService');
|
||
|
|
const { closeTicketInGlpi } = require('../services/glpiTicketService');
|
||
|
|
|
||
|
|
const processStatusAndClosureController = async () => {
|
||
|
|
logInfo('Iniciando processamento de status e fechamento de tickets...');
|
||
|
|
|
||
|
|
try {
|
||
|
|
// --- FASE 1: Sincronização de Status para Tickets Abertos (Bidirecional) ---
|
||
|
|
logInfo('Iniciando verificação e sincronização de status...');
|
||
|
|
// Busca todos os tickets que não estão permanentemente fechados.
|
||
|
|
const ticketsToMonitor = await TicketSyncModel.getTicketsToMonitor();
|
||
|
|
|
||
|
|
if (ticketsToMonitor.length === 0) {
|
||
|
|
logInfo('Nenhum ticket sincronizado (aberto) encontrado para verificação de status.');
|
||
|
|
} else {
|
||
|
|
logInfo(`Encontrados ${ticketsToMonitor.length} tickets para monitorar status (synced/solved).`);
|
||
|
|
}
|
||
|
|
|
||
|
|
for (const ticket of ticketsToMonitor) {
|
||
|
|
try {
|
||
|
|
// Obter detalhes completos do ticket SN para ter sys_id, tipo e status atual
|
||
|
|
const ticketSnDetails = await TicketSnModel.findById(ticket.sn_ticket_id);
|
||
|
|
if (!ticketSnDetails) {
|
||
|
|
logWarning(`Detalhes do ticket SN ${ticket.sn_ticket_id} não encontrados. Pulando.`);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
const snCurrentStatus = ticketSnDetails.status;
|
||
|
|
|
||
|
|
// ================= LÓGICA DE DETECÇÃO DE MUDANÇA NO GLPI =================
|
||
|
|
// Primeiro, verificamos se o GLPI mudou de estado por conta própria.
|
||
|
|
const glpiLiveStatus = await TicketGlpiModel.getTicketStatus(ticket.glpi_ticket_id);
|
||
|
|
const glpiDbStatus = ticket.glpi_sync_status;
|
||
|
|
|
||
|
|
// Converte o status numérico do GLPI para a string que usamos no nosso banco (ex: 'solved')
|
||
|
|
const glpiLiveStatusAsString = TicketGlpiModel.mapGlpiStatusToString(glpiLiveStatus).toLowerCase().replace(/ /g, '_').replace('-', '_');
|
||
|
|
|
||
|
|
if (glpiLiveStatus !== null && glpiLiveStatusAsString !== glpiDbStatus) {
|
||
|
|
logInfo(`Mudança de status detectada no GLPI para o ticket ${ticket.glpi_ticket_id}. De '${glpiDbStatus}' para '${glpiLiveStatusAsString}'.`);
|
||
|
|
// Se uma mudança foi detectada no GLPI, forçamos o "bastão" para ele.
|
||
|
|
ticket.source_last = 'GLPI';
|
||
|
|
}
|
||
|
|
|
||
|
|
// ================= LÓGICA SNOW -> GLPI =================
|
||
|
|
// Se a última atualização veio do ServiceNow, atualiza o GLPI
|
||
|
|
if (ticket.source_last === 'SNOW') {
|
||
|
|
logInfo(`SN ${ticketSnDetails.ticket_number} (SNOW -> GLPI): Verificando status '${snCurrentStatus}'`);
|
||
|
|
|
||
|
|
// **LÓGICA DE REABERTURA REFINADA**
|
||
|
|
// Busca o status atual do GLPI para tomar uma decisão inteligente.
|
||
|
|
const glpiCurrentStatus = await TicketGlpiModel.getTicketStatus(ticket.glpi_ticket_id);
|
||
|
|
// Se o ticket no GLPI está permanentemente Fechado (6), não fazemos nada.
|
||
|
|
if (glpiCurrentStatus === 6) {
|
||
|
|
logWarning(`Ticket GLPI ${ticket.glpi_ticket_id} está permanentemente Fechado (6). Ação de reabertura pelo SN ('${snCurrentStatus}') foi bloqueada.`);
|
||
|
|
} else if (snCurrentStatus === 'Resolvido' && glpiCurrentStatus !== 5) { // SN foi para 'Resolved' e GLPI ainda não está 'Solucionado'
|
||
|
|
logInfo(`SN ${ticketSnDetails.ticket_number} foi Resolvido. Sincronizando para GLPI...`);
|
||
|
|
await closeTicketInGlpi(ticket);
|
||
|
|
await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'solved', 'solved');
|
||
|
|
} else if (snCurrentStatus === 'Resolvido' && glpiCurrentStatus === 5) {
|
||
|
|
// Ambos já estão resolvidos, nenhuma ação necessária.
|
||
|
|
logInfo(`SN ${ticketSnDetails.ticket_number} e GLPI ${ticket.glpi_ticket_id} já estão no estado Resolvido/Solucionado. Nenhuma ação necessária.`);
|
||
|
|
} else if (snCurrentStatus === 'Encerrado - Omitido') { // SN foi para 'Closed'
|
||
|
|
logInfo(`SN ${ticketSnDetails.ticket_number} foi Fechado. Sincronizando para GLPI...`);
|
||
|
|
await TicketGlpiModel.forceCloseTicket(ticket.glpi_ticket_id);
|
||
|
|
await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'closed', 'closed');
|
||
|
|
} else {
|
||
|
|
// Se estiver Solucionado (5) ou em qualquer outro estado, permite a atualização (reabertura).
|
||
|
|
await TicketGlpiModel.updateStatus(ticket.glpi_ticket_id, snCurrentStatus);
|
||
|
|
logInfo(`Status do ticket GLPI ${ticket.glpi_ticket_id} atualizado para '${snCurrentStatus}'.`);
|
||
|
|
await TicketSyncModel.updateStatusAndSourceLast(ticket.sn_ticket_id, 'synced', 'synced', 'GLPI'); // Passa o bastão
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// ================= LÓGICA GLPI -> SNOW =================
|
||
|
|
// Se a última atualização veio do GLPI, atualiza o ServiceNow
|
||
|
|
else if (ticket.source_last === 'GLPI') {
|
||
|
|
logInfo(`SN ${ticketSnDetails.ticket_number} (GLPI -> SNOW): Verificando status no GLPI.`);
|
||
|
|
const glpiCurrentStatus = glpiLiveStatus; // Reutiliza o status que já buscamos
|
||
|
|
if (glpiCurrentStatus !== null) {
|
||
|
|
if (glpiCurrentStatus === 5) { // GLPI foi para 'Solucionado'
|
||
|
|
// Apenas processa se o SN não estiver já resolvido/fechado
|
||
|
|
if (ticket.sn_sync_status !== 'solved' && ticket.sn_sync_status !== 'closed') {
|
||
|
|
logInfo(`GLPI ${ticket.glpi_ticket_id} foi Solucionado. Sincronizando para SN...`);
|
||
|
|
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);
|
||
|
|
} else {
|
||
|
|
// FLUXO NORMAL DE RESOLUÇÃO
|
||
|
|
const closeNotes = solution?.content ? stripHTML(solution.content) : 'Resolvido via integração GLPI.';
|
||
|
|
const resolvedAt = solution?.date_mod || new Date();
|
||
|
|
await closeTicketInServiceNow(ticket.sn_ticket_id, closeNotes, resolvedAt);
|
||
|
|
await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'solved', 'solved');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else if (glpiCurrentStatus === 6) {
|
||
|
|
logInfo(`GLPI ${ticket.glpi_ticket_id} foi Fechado. Sincronizando para SN...`);
|
||
|
|
// Apenas marca nosso banco como fechado, pois o SN já deve estar fechado ou será fechado por automação interna.
|
||
|
|
if (ticket.glpi_sync_status !== 'closed') {
|
||
|
|
await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'closed', ticket.sn_sync_status);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
// Se o status for de um ticket aberto (reabertura, etc.), propaga a mudança.
|
||
|
|
const glpiStatusAsString = TicketGlpiModel.mapGlpiStatusToString(glpiCurrentStatus);
|
||
|
|
if (glpiStatusAsString !== snCurrentStatus) {
|
||
|
|
logInfo(`Diferença de status detectada (GLPI: ${glpiStatusAsString}, SN: ${snCurrentStatus}). Sincronizando...`);
|
||
|
|
await updateStatusInServiceNow(ticket.sn_ticket_id, glpiStatusAsString);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// Passa o bastão de volta para o SN se a ação não for de finalização
|
||
|
|
if (glpiCurrentStatus < 5) {
|
||
|
|
await TicketSyncModel.updateStatusAndSourceLast(ticket.sn_ticket_id, 'synced', 'synced', 'SNOW');
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
logWarning(`Não foi possível obter o status atual do ticket GLPI ${ticket.glpi_ticket_id}.`);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// Se source_last não está definido ou é desconhecido, podemos definir um padrão ou logar um aviso.
|
||
|
|
// Por enquanto, vamos apenas logar.
|
||
|
|
else {
|
||
|
|
logWarning(`source_last desconhecido para o ticket SN ${ticketSnDetails.ticket_number}. Pulando sincronização de status.`);
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
logError(error, `Erro ao sincronizar status do ticket SN ${ticket.sn_ticket_id} (GLPI ID: ${ticket.glpi_ticket_id}).`);
|
||
|
|
// Marcar o ticket como erro para revisão manual
|
||
|
|
await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'error_sync', 'error_sync');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
logError(error, 'Erro geral no processamento de status e fechamento de tickets.');
|
||
|
|
}
|
||
|
|
};
|
||
|
|
module.exports = {
|
||
|
|
processStatusAndClosureController
|
||
|
|
};
|