2025-10-23 17:30:03 -03:00
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 ) ;
2025-10-28 14:06:00 -03:00
// Converte o status numérico do GLPI para a string correspondente no ServiceNow (ex: 'Em Atendimento')
const glpiLiveStatusAsString = TicketGlpiModel . mapGlpiStatusToString ( glpiLiveStatus ) ;
2025-10-23 17:30:03 -03:00
2025-10-28 14:06:00 -03:00
// Compara o status real do GLPI com o status que temos do ServiceNow
if ( glpiLiveStatus !== null && glpiLiveStatusAsString !== snCurrentStatus ) {
logInfo ( ` Mudança de status detectada no GLPI para o ticket ${ ticket . glpi _ticket _id } . GLPI está como ' ${ glpiLiveStatusAsString } ', SN está como ' ${ snCurrentStatus } '. ` ) ;
2025-10-23 17:30:03 -03:00
// 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. ` ) ;
2025-10-24 13:13:08 -03:00
} else if ( snCurrentStatus === 'Encerrado - Omitido' || snCurrentStatus === 'Encerrado' ) { // SN foi para 'Closed'
2025-10-23 17:30:03 -03:00
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 {
2025-10-28 14:06:00 -03:00
// REGRA DE NEGÓCIO: Não pode fechar um ticket que está "Em Espera" ou "Aguardando Atendimento" no SN.
if ( snCurrentStatus === 'Em Espera' || snCurrentStatus === 'Aguardando Atendimento' ) {
logWarning ( ` Tentativa de fechar o ticket SN ${ ticketSnDetails . ticket _number } que está ' ${ snCurrentStatus } '. Ação bloqueada. ` ) ;
// Não Adiciona uma nota no SN e reabre o ticket no GLPI para refletir o status do SN.
//await addWorkNoteToServiceNow(ticket.sn_ticket_id, `O ticket correspondente no GLPI (${ticket.glpi_ticket_id}) foi solucionado, mas o chamado no ServiceNow está '${snCurrentStatus}'. É necessário retomar o atendimento para prosseguir com o encerramento.`);
//Atualiza o status do GLPI
await TicketGlpiModel . updateStatus ( ticket . glpi _ticket _id , snCurrentStatus ) ; // Reabre o GLPI imediatamente
await TicketSyncModel . updateStatusAndSourceLast ( ticket . sn _ticket _id , 'synced' , 'synced' , 'SNOW' ) ; // Passa o bastão para o SN
continue ; // Pula para o próximo ticket
}
2025-10-23 17:30:03 -03:00
// 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
} ;