2025-09-15 20:55:19 -03:00
const TicketSnModel = require ( '../models/ticketSnModel' ) ;
const TicketGlpiModel = require ( '../models/ticketGlpiModel' ) ;
2025-10-23 17:30:03 -03:00
const TicketUpdateModel = require ( '../models/ticketUpdateModel' ) ;
2025-09-17 20:56:32 -03:00
const TicketSyncModel = require ( '../models/ticketSyncModel' ) ;
2026-02-19 18:22:38 -03:00
const { logInfo , logError } = require ( '../utils/logger' ) ;
2026-06-26 11:39:57 -03:00
const { addWorkNoteToServiceNow , updateExternalTicketInServiceNow , queueExternalTicketLinkRetry , reassignTicketInServiceNow } = require ( './servicenowService' ) ;
2026-02-24 17:40:39 -03:00
const { stripHTML } = require ( '../utils/commentSanitizer' ) ;
const outOfScopeSolutionTypeId = Number ( process . env . GLPI _OOS _SOLUTION _TYPE _ID || 27 ) ;
2026-06-26 11:39:57 -03:00
const caoaTiGroupId = process . env . SERVICENOW _CAOA _TI _GROUP _ID ;
2025-10-23 17:30:03 -03:00
2025-09-15 20:55:19 -03:00
const syncTicketsToGlpi = async ( ) => {
try {
logInfo ( '🔄 Iniciando sincronização para GLPI...' ) ;
const pendingTickets = await TicketSnModel . getPendingTickets ( ) ;
if ( pendingTickets . length === 0 ) {
logInfo ( '✅ Nenhum ticket pendente para sincronizar' ) ;
return ;
2025-09-11 20:57:43 -03:00
}
2025-09-15 20:55:19 -03:00
logInfo ( ` 📋 Encontrados ${ pendingTickets . length } tickets para sincronizar ` ) ;
2025-10-13 06:06:52 -03:00
2025-09-15 20:55:19 -03:00
for ( const ticket of pendingTickets ) {
await processSingleTicket ( ticket ) ;
2025-09-11 20:57:43 -03:00
}
2025-09-15 20:55:19 -03:00
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
}
2025-09-15 20:55:19 -03:00
} ;
2025-09-11 20:57:43 -03:00
2025-09-15 20:55:19 -03:00
const processSingleTicket = async ( ticket ) => {
2025-09-17 20:56:32 -03:00
2025-09-15 20:55:19 -03:00
try {
logInfo ( ` 🔍 Processando ticket: ${ ticket . ticket _number } ` ) ;
2025-10-17 16:58:14 -03:00
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' ) ;
2026-03-04 16:37:04 -03:00
const linkedOk = await updateExternalTicketInServiceNow ( ticket . id , ticket . glpi _ticket _id ) ;
await queueExternalTicketLinkRetry ( ticket . ticket _sync _id , ticket . id , ticket . glpi _ticket _id , linkedOk ) ;
2025-10-17 16:58:14 -03:00
return ;
}
2025-10-13 06:06:52 -03:00
2025-10-17 16:58:14 -03:00
const existingTicket = await TicketGlpiModel . ticketExists ( ticket . ticket _number ) ;
2025-10-29 14:20:21 -03:00
await TicketSyncModel . updateLastSync ( ticket . id , 'glpi' ) ;
2025-10-13 06:06:52 -03:00
2025-10-17 16:58:14 -03:00
if ( existingTicket ) {
logInfo ( ` ✅ Ticket ${ ticket . ticket _number } encontrado no GLPI. Atualizando registro local com o ID: ${ existingTicket } ` ) ;
2025-10-28 14:06:00 -03:00
const glpiLiveStatus = await TicketGlpiModel . getTicketStatus ( existingTicket ) ;
2025-10-29 14:20:21 -03:00
if ( glpiLiveStatus === 6 ) {
2025-10-28 14:06:00 -03:00
logInfo ( ` Ticket GLPI ${ existingTicket } já está Fechado. Marcando ticket SN ${ ticket . ticket _number } como 'closed'/'ignored' e não monitorado. ` ) ;
await TicketSyncModel . updateStatusAndGlpiTicket ( ticket . id , 'ignored' , 'closed' , existingTicket ) ;
2025-10-29 14:20:21 -03:00
return ;
2025-10-28 14:06:00 -03:00
}
2025-10-17 16:58:14 -03:00
await TicketSyncModel . updateStatusAndGlpiTicket ( ticket . id , 'synced' , 'synced' , existingTicket ) ;
2026-03-04 16:37:04 -03:00
const existingLinkOk = await updateExternalTicketInServiceNow ( ticket . id , existingTicket ) ;
await queueExternalTicketLinkRetry ( ticket . ticket _sync _id , ticket . id , existingTicket , existingLinkOk ) ;
2025-10-17 16:58:14 -03:00
logInfo ( ` ✅ Ticket ${ ticket . ticket _number } já existe no GLPI! GLPI ID: ${ existingTicket } ` ) ;
2025-10-29 14:20:21 -03:00
return ;
2025-09-15 20:55:19 -03:00
}
2025-09-17 20:56:32 -03:00
2025-09-15 20:55:19 -03:00
const glpiData = {
ticket _number : ticket . ticket _number ,
short _description : ticket . short _description ,
description : ticket . description ,
2026-02-19 18:22:38 -03:00
justificativa : ticket . justificativa ,
2025-09-17 20:56:32 -03:00
caller _id : ticket . caller _id ,
2025-09-15 20:55:19 -03:00
caller _email : ticket . caller _email ,
caller _phone : ticket . telefone ,
caller _ramal : ticket . ramal ,
2025-09-17 20:56:32 -03:00
type : ticket . tipo ,
state : ticket . status ,
priority : '3' ,
location _id : ticket . location _id ,
2025-09-15 20:55:19 -03:00
location _name : ticket . location _name ,
opened _at : ticket . opened _at ,
updated _at : ticket . updated _at
} ;
const glpiTicketId = await TicketGlpiModel . createTicket ( glpiData ) ;
2025-10-29 14:20:21 -03:00
await TicketSyncModel . updateLastSync ( ticket . id , 'glpi' ) ;
2025-10-23 17:30:03 -03:00
await TicketSyncModel . updateStatusAndGlpiTicket ( ticket . id , 'synced' , 'synced' , glpiTicketId ) ;
2026-03-04 16:37:04 -03:00
const externalLinkOk = await updateExternalTicketInServiceNow ( ticket . id , glpiTicketId ) ;
await queueExternalTicketLinkRetry ( ticket . ticket _sync _id , ticket . id , glpiTicketId , externalLinkOk ) ;
2025-09-15 20:55:19 -03:00
logInfo ( ` ✅ Ticket ${ ticket . ticket _number } sincronizado! GLPI ID: ${ glpiTicketId } ` ) ;
} catch ( error ) {
logError ( error , ` ❌ Erro no ticket ${ ticket . ticket _number } ` ) ;
2025-10-17 16:58:14 -03:00
try {
2025-10-23 17:30:03 -03:00
await TicketSyncModel . updateStatus ( ticket . id , 'error' , 'collected' ) ;
2025-10-17 16:58:14 -03:00
} catch ( innerError ) {
2025-09-17 20:56:32 -03:00
logError ( innerError , ` ❌ Erro ao registrar falha de sincronização para ticket ${ ticket . id } ` ) ;
}
2025-09-15 20:55:19 -03:00
}
2025-08-30 19:04:35 -03:00
} ;
2025-10-23 17:30:03 -03:00
const checkGlpiStatusChanges = async ( ) => {
try {
logInfo ( '📡 Verificando mudanças de status nos tickets do GLPI...' ) ;
const ticketsToMonitor = await TicketSyncModel . getTicketsToMonitor ( ) ;
if ( ticketsToMonitor . length === 0 ) {
logInfo ( 'Nenhum ticket ativo para verificar status no GLPI.' ) ;
return ;
}
for ( const ticket of ticketsToMonitor ) {
const glpiStatus = await TicketGlpiModel . getTicketStatus ( ticket . glpi _ticket _id ) ;
const currentDbStatus = ticket . glpi _sync _status ;
2025-10-29 14:20:21 -03:00
const glpiStatusAsString = TicketGlpiModel . mapGlpiStatusToString ( glpiStatus ) . toLowerCase ( ) . replace ( / /g , '_' ) ;
2025-10-23 17:30:03 -03:00
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 ) ;
2026-02-24 17:40:39 -03:00
if ( solution ? . solutiontypes _id === outOfScopeSolutionTypeId ) {
logInfo ( ` REGRA DETECTADA: Ticket ${ ticket . glpi _ticket _id } está fora do escopo (SolutionType ID: ${ outOfScopeSolutionTypeId } ). ` ) ;
const justification = solution ? . content ? stripHTML ( solution . content ) : 'Ticket marcado como "Fora do escopo" no GLPI.' ;
2026-06-26 11:39:57 -03:00
await addWorkNoteToServiceNow ( ticket . sn _ticket _id , ` Ticket fora do escopo Sothis. Chamado transferido para a fila CAOA Redes com justificativa: ${ justification } ` ) ;
await reassignTicketInServiceNow ( ticket . sn _ticket _id , caoaTiGroupId ) ;
2025-10-23 17:30:03 -03:00
await TicketSyncModel . updateStatus ( ticket . sn _ticket _id , 'closed' , 'ignored' ) ;
await TicketGlpiModel . forceCloseTicket ( ticket . glpi _ticket _id ) ;
2025-10-29 14:20:21 -03:00
continue ;
2025-10-23 17:30:03 -03:00
}
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 } ` ,
} ) ;
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' ) {
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' ) ;
}
} ;
2026-02-23 11:39:09 -03:00
const addServiceNowClosureNoteToGlpi = async ( ticket , collaboratorName , finalStatus = 'encerrado' ) => {
try {
const resolvedBy = collaboratorName && collaboratorName . trim ( )
? collaboratorName . trim ( )
: 'colaborador CAOA' ;
const statusText = finalStatus === 'solucionado'
? 'Chamado solucionado no Service Now pela CAOA.'
: 'Chamado encerrado no Service Now pela CAOA.' ;
const styledMessage = `
2026-02-24 17:40:39 -03:00
< div style = "border:1px solid #fecaca; border-left:4px solid #c53030; background:#fff5f5; padding:12px; border-radius:4px;" >
< div style = "font-weight:700; color:#9b2c2c; margin-bottom:6px;" > Encerramento automatico no ServiceNow < / d i v >
< div style = "color:#742a2a;" >
2026-02-23 11:39:09 -03:00
$ { statusText }
< / d i v >
2026-02-24 17:40:39 -03:00
< div style = "margin-top:8px; color:#742a2a; font-size:12px;" >
2026-02-23 11:39:09 -03:00
Responsavel informado : $ { resolvedBy }
< / d i v >
2026-02-24 17:40:39 -03:00
< div style = "margin-top:6px; color:#742a2a; font-size:12px;" >
Chamado resolvido no ServiceNow e removido do monitoramento automatico da integracao .
< / d i v >
2026-02-23 11:39:09 -03:00
< / d i v > ` ;
await TicketGlpiModel . insertComment ( { content : styledMessage } , ticket . glpi _ticket _id ) ;
await TicketSyncModel . updateLastSync ( ticket . sn _ticket _id , 'glpi' ) ;
logInfo ( ` Nota de encerramento do ServiceNow adicionada ao GLPI ${ ticket . glpi _ticket _id } . ` ) ;
return true ;
} catch ( error ) {
logError ( error , ` Falha ao adicionar nota de encerramento do SN no GLPI ${ ticket . glpi _ticket _id } ` ) ;
return false ;
}
} ;
2025-10-23 17:30:03 -03:00
const closeTicketInGlpi = async ( ticket ) => {
try {
logInfo ( ` 🔒 Iniciando fechamento do chamado GLPI ${ ticket . glpi _ticket _id } ... ` ) ;
const closeMessageRecord = await TicketUpdateModel . getLatestUpdate ( ticket . id , 'servicenow' ) ;
const solutionContent = closeMessageRecord ? . content || 'Chamado encerrado via integração com o ServiceNow.' ;
await TicketGlpiModel . closeTicket ( ticket . glpi _ticket _id , solutionContent ) ;
2025-10-29 14:20:21 -03:00
await TicketSyncModel . updateLastSync ( ticket . sn _ticket _id , 'glpi' ) ;
2025-10-23 17:30:03 -03:00
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 } ` ) ;
2025-10-29 14:20:21 -03:00
throw error ;
2025-10-23 17:30:03 -03:00
}
} ;
2025-08-30 19:04:35 -03:00
module . exports = {
2025-10-23 17:30:03 -03:00
syncTicketsToGlpi ,
checkGlpiStatusChanges ,
2026-02-23 11:39:09 -03:00
closeTicketInGlpi ,
addServiceNowClosureNoteToGlpi
2025-10-29 14:20:21 -03:00
} ;
/ * *
* @ module glpiTicketService
* @ description Este serviço contém a lógica de negócio para a criação e atualização de tickets no GLPI .
* Ele atua como um intermediário entre os controladores e os modelos de dados .
*
* Principais Funcionalidades :
* - ` syncTicketsToGlpi() ` : Função principal que busca tickets pendentes no banco de dados local ( previamente coletados do ServiceNow ) e orquestra sua criação no GLPI .
* - ` processSingleTicket(ticket) ` : Processa um único ticket . Verifica se ele já existe no GLPI para evitar duplicatas . Se não existir , formata os dados ( incluindo o enriquecimento da descrição para requisições ) e chama o ` TicketGlpiModel ` para criar o ticket . Se já existir , apenas atualiza o registro de sincronização .
* - ` checkGlpiStatusChanges() ` : ( Atualmente não utilizado no fluxo principal , sua lógica foi integrada ao ` processStatusController ` ) Função projetada para detectar proativamente mudanças de status no GLPI .
* - ` closeTicketInGlpi(ticket) ` : Orquestra o processo de fechamento de um ticket no GLPI . Ele busca a nota de resolução vinda do ServiceNow e a utiliza para adicionar uma solução no GLPI antes de mudar o status para "Solucionado" .
2026-02-19 18:22:38 -03:00
* /