snglpi/src/controllers/processStatusController.js

145 lines
10 KiB
JavaScript
Raw Normal View History

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' || snCurrentStatus === 'Encerrado') { // 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
};