2025-09-23 21:01:01 -03:00
// src/services/servicenowService.js
2025-08-30 19:04:35 -03:00
const axios = require ( 'axios' ) ;
2025-09-03 11:13:23 -03:00
const apiConfig = require ( '../../config/apiConfig' ) ;
2025-09-05 20:28:15 -03:00
const TicketSnModel = require ( '../models/ticketSnModel' ) ;
const { logInfo , logError , logSync } = require ( '../utils/logger' ) ;
2025-10-07 10:39:01 -03:00
const fs = require ( 'fs' ) ;
2025-10-10 08:12:26 -03:00
const TicketUpdateModel = require ( '../models/ticketUpdateModel' ) ;
const TicketSyncModel = require ( '../models/ticketSyncModel' ) ;
2025-08-30 19:04:35 -03:00
2025-09-03 11:13:23 -03:00
const fetchTicketsFromServiceNow = async ( ) => {
2025-08-30 19:04:35 -03:00
try {
2025-09-05 20:28:15 -03:00
logInfo ( '📡 Buscando incidentes do ServiceNow...' , { service : 'servicenow' } ) ;
2025-09-03 21:40:04 -03:00
2025-09-03 16:11:30 -03:00
const ticketsResponse = await axios . get ( apiConfig . snIncidentConfig . baseUrl , {
auth : apiConfig . servicenowAuthentication . auth
2025-09-03 11:13:23 -03:00
} ) ;
2025-09-03 21:40:04 -03:00
2025-09-05 20:28:15 -03:00
logInfo ( '💾 Iniciando salvamento de incidentes no banco...' ) ;
2025-09-03 21:40:04 -03:00
for ( const ticket of ticketsResponse . data . result ) {
2025-10-13 06:06:52 -03:00
//Insere no banco de dados como Incidente
const idTableTicketSn = await TicketSnModel . saveTicket ( ticket , 'incidente' ) ;
//Salvando atualização no banco intermediario e defininfo sync status
const ticketStatus = ticket . state ? . display _value ;
const dataSync = {
glpi _ticket _id : null ,
sn _ticket _id : idTableTicketSn ,
last _sn _sync : new Date ( ) ,
last _glpi _sync : null ,
created _at : null ,
updated _at : null
}
if ( ticketStatus === 'Encerrado' || ticketStatus === 'Encerrado - Omitido' ) {
dataSync . sn _sync _status = 'closed'
dataSync . glpi _sync _status = 'ignored'
} else {
dataSync . sn _sync _status = 'synced'
dataSync . glpi _sync _status = 'pending_check'
}
await TicketSyncModel . createSyncRecord ( dataSync ) ;
2025-09-03 21:40:04 -03:00
}
2025-09-05 20:28:15 -03:00
logSync ( 'ServiceNow' , ticketsResponse . data . result . length , 'incidentes' ) ;
2025-09-03 21:40:04 -03:00
2025-09-03 16:11:30 -03:00
} catch ( error ) {
2025-09-05 20:28:15 -03:00
logError ( error , 'SERVICE NOW Tickets' ) ;
2025-09-03 16:11:30 -03:00
}
}
const fetchRequestsFromServiceNow = async ( ) => {
try {
2025-09-05 20:28:15 -03:00
logInfo ( '📡 Buscando requisições do ServiceNow...' , { service : 'servicenow' } ) ;
2025-09-03 16:11:30 -03:00
const requestsResponse = await axios . get ( apiConfig . snRequestConfig . baseUrl , {
auth : apiConfig . servicenowAuthentication . auth
} ) ;
2025-09-05 20:28:15 -03:00
// Salvar as requisições no banco de dados
logInfo ( '💾 Iniciando salvamento de requisições no banco...' ) ;
2025-09-03 21:40:04 -03:00
for ( const request of requestsResponse . data . result ) {
2025-10-13 06:06:52 -03:00
//Salvando a requisicao no banco intermediario
const idTableTicketSn = await TicketSnModel . saveTicket ( request , 'requisicao' ) ;
const dataSync = {
glpi _ticket _id : null ,
sn _ticket _id : idTableTicketSn ,
last _sn _sync : new Date ( ) ,
last _glpi _sync : null ,
created _at : null ,
updated _at : null
}
if ( ticketStatus === 'Encerrado' || ticketStatus === 'Encerrado - Omitido' ) {
dataSync . sn _sync _status = 'closed'
dataSync . glpi _sync _status = 'ignored'
} else {
dataSync . sn _sync _status = 'collected'
dataSync . glpi _sync _status = 'pending_check'
}
//Cria apenas se não existir
const exists = await TicketSyncModel . getIdSyncBySnId ( idTableTicketSn ) ;
if ( ! exists ) await TicketSyncModel . createSyncRecord ( dataSync ) ;
}
logSync ( 'ServiceNow' , requestsResponse . data . result . length , 'requisições' ) ;
2025-08-30 19:04:35 -03:00
} catch ( error ) {
2025-09-05 20:28:15 -03:00
logError ( error , 'SERVICE NOW Requests' ) ;
2025-08-30 19:04:35 -03:00
}
2025-09-03 11:13:23 -03:00
}
2025-08-30 19:04:35 -03:00
2025-09-25 15:59:52 -03:00
// Função para obter o sys_id de um ticket pela tabela de sincronização do banco de dados local
2025-10-02 10:16:56 -03:00
const getTicketSysId = async ( ticketId ) => {
2025-09-25 15:59:52 -03:00
try {
2025-10-02 10:16:56 -03:00
const ticket = await TicketSnModel . findById ( ticketId ) ;
2025-09-25 15:59:52 -03:00
return ticket . sys _id ;
} catch ( error ) {
logError ( error , '🚨Falha ao obter sys_id do ticket' ) ;
return null ;
}
}
2025-10-02 10:16:56 -03:00
const getTicketTypeById = async ( ticketId ) => {
try {
const ticket = await TicketSnModel . findById ( ticketId ) ;
const ticketType = ticket . tipo ;
logInfo ( ` ⚠️ Tipo do ticket ${ ticketId } - Tipo: ${ ticketType } ` , { ticketId , ticketType } ) ;
return ticketType ;
} catch ( error ) {
logError ( error , '🚨Falha ao obter tipo do ticket' ) ;
return null ;
}
}
2025-09-25 15:59:52 -03:00
// Função para obter o código de um comentário dentro de um ticket do ServiceNow
const getCommentId = async ( sysId , comment ) => {
try {
// Exemplo de requisição GET para buscar comentários:
2025-10-02 15:28:17 -03:00
// GET https://caoadev.service-now.com/api/now/table/sys_journal_field?sysparm_query=element_id=<SYS_ID_DO_INCIDENTE>^element=comments^ORDERBYDESCsys_created_on&sysparm_fields=sys_id,element,value,sys_created_on,sys_created_by,element_id&sysparm_limit=1
2025-10-02 10:16:56 -03:00
// exemplo de resposta da API:
/ *
"result" : [
{
2025-10-02 15:28:17 -03:00
"sys_id" : "SYS_ID_DO_COMENTARIO" ,
2025-10-02 10:16:56 -03:00
"sys_created_on" : "2025-10-02 12:24:19" ,
2025-10-02 15:28:17 -03:00
"element_id" : "SYS_ID_DO_INCIDENTE" ,
2025-10-02 10:16:56 -03:00
"value" : "Teste fora do código" ,
"sys_created_by" : "SOTHIS.CAOA" ,
"element" : "comments"
} * /
const url = ` ${ apiConfig . snTableJournalConfig . baseUrl } ?sysparm_query=element_id= ${ sysId } ^element=comments^ORDERBYDESCsys_created_on&sysparm_fields=sys_id,element,value,sys_created_on,sys_created_by,element_id ` ;
2025-09-25 15:59:52 -03:00
const response = await axios . get ( url , {
auth : apiConfig . servicenowAuthentication . auth
} ) ;
2025-10-02 10:16:56 -03:00
const data = response . data ;
const comments = data . result ;
// Procurar o comentário exato na lista retornada
for ( const item of comments ) {
if ( item . value && item . value . includes ( comment ) ) {
return item . sys _id ; // Retorna o sys_id do comentário encontrado
2025-09-25 15:59:52 -03:00
}
}
} catch ( error ) {
logError ( error , '🚨Falha ao obter ID do comentário no ServiceNow' ) ;
return null ;
}
}
// Função para criar um comentário em um ticket no ServiceNow
2025-10-02 10:16:56 -03:00
const createCommentInServiceNow = async ( sysId , comment , ticketType ) => {
2025-09-25 15:59:52 -03:00
try {
// Exemplo de requisição PATCH para atualizar um ticket:
// curl -u user:pass \
// -H "Content-Type: application/json" \
// -X PATCH "https://SEU_INSTANCE.service-now.com/api/now/table/incident/{SYS_ID_DO_INCIDENTE}" \
// -d '{"comments":"Estamos analisando seu chamado. Retorno em breve."}'
2025-10-02 10:16:56 -03:00
let url = '' ;
let payload = { } ;
if ( ticketType == 'incidente' ) {
url = ` ${ apiConfig . snTableIncidentConfig . baseUrl } ${ sysId } ` ;
payload = {
comments : comment
} ;
} else if ( ticketType == 'requisicao' ) {
url = ` ${ apiConfig . snTableRequestConfig . baseUrl } ${ sysId } ` ;
payload = {
comments : comment
} ;
} else {
logError ( ` Tipo de ticket desconhecido: ${ ticketType } ` , { sysId , ticketType } ) ;
return null ;
}
2025-09-25 15:59:52 -03:00
2025-10-02 10:16:56 -03:00
const response = await axios . patch ( url , payload , {
2025-09-25 15:59:52 -03:00
auth : apiConfig . servicenowAuthentication . auth
} ) ;
2025-10-02 10:16:56 -03:00
// Verificar se retorna status 200 para sucesso
if ( response . status !== 200 ) {
logError ( ` ❌ Falha ao criar comentário. Status: ${ response . status } ` , { sysId , ticketType } ) ;
return null ;
}
2025-09-25 15:59:52 -03:00
// retornar id do comentário criado
const commentId = await getCommentId ( sysId , comment ) ;
2025-10-02 10:16:56 -03:00
if ( ! commentId ) {
logError ( '❌ Comentário criado, mas não foi possível obter o ID do comentário' , { sysId } ) ;
return null ;
}
logInfo ( ` ✅ Comentário criado com sucesso no ServiceNow. Comment ID: ${ commentId } ` , { sysId , ticketType } ) ;
2025-09-25 15:59:52 -03:00
return commentId ;
} catch ( error ) {
logError ( error , '🚨Falha ao criar comentário no ServiceNow' ) ;
}
}
2025-10-02 10:16:56 -03:00
const syncCommentToServiceNow = async ( ticketId , comment ) => {
logInfo ( ` Iniciando sincronização do comentário para o ticket SN Ticket: ${ ticketId } ` ) ;
const snSysId = await getTicketSysId ( ticketId ) ;
const ticketType = await getTicketTypeById ( ticketId ) ;
if ( ! snSysId ) {
logError ( ` ❌ sys_id do ServiceNow não encontrado para o ticket SN Ticket: ${ ticketId } ` ) ;
return null ;
}
const snCommentId = await createCommentInServiceNow ( snSysId , comment , ticketType ) ;
return snCommentId ;
}
// Função que verifica se o comentário já existe no ServiceNow antes de criar
const existingCommentInServiceNow = async ( ticketId , comment ) => {
try {
const sys _id = await getTicketSysId ( ticketId ) ;
const commentExists = await getCommentId ( sys _id , comment ) ;
return commentExists ;
} catch ( error ) {
logError ( error , '🚨Falha ao verificar existência do comentário no ServiceNow' ) ;
return false ;
}
}
2025-10-07 10:28:39 -03:00
// Função para buscar todos os comentários de tickets no ServiceNow a partir do ticket id no banco local
// Deve-se atentar para a paginação da API do ServiceNow
2025-10-07 10:39:01 -03:00
// para fins de teste, gerar um arquivo JSON com os comentários de um ticket específico
2025-10-07 10:28:39 -03:00
2025-10-13 06:06:52 -03:00
const fetchCommentsFromServiceNow = async ( ) => {
2025-10-07 10:28:39 -03:00
try {
2025-10-10 08:12:26 -03:00
2025-10-13 06:06:52 -03:00
const ticketsSyncList = await TicketSyncModel . getTicketsToSync ( ) ;
2025-10-10 08:12:26 -03:00
console . log ( ` Iniciando busca de comentários do ServiceNow para o ticket SN Ticket: ${ ticketId } ` ) ;
2025-10-13 06:06:52 -03:00
//Coleta o sys id no bando intermediario para busca no service now
2025-10-07 10:28:39 -03:00
const sys _id = await getTicketSysId ( ticketId ) ;
2025-10-13 06:06:52 -03:00
//Se não encontrar é impossível atualizar
2025-10-07 10:28:39 -03:00
if ( ! sys _id ) {
logError ( 'sys_id não encontrado para ticket' , { ticketId } ) ;
return [ ] ;
}
const allComments = [ ] ;
const pageSize = 100 ;
let offset = 0 ;
let page = 0 ;
const internalMaxPages = 1000 ; // safeguard interno para evitar loop infinito
while ( true ) {
page ++ ;
const params = {
sysparm _query : ` element_id= ${ encodeURIComponent ( sys _id ) } ^element=comments ` ,
sysparm _fields : 'sys_id,element,value,sys_created_on,sys_created_by,element_id' ,
sysparm _limit : pageSize ,
sysparm _offset : offset
} ;
logInfo ( ` Buscando página ${ page } de comentários do ticket SN Ticket: ${ ticketId } ` , { ticketId , page } ) ;
const response = await axios . get ( apiConfig . snTableJournalConfig . baseUrl , {
auth : apiConfig . servicenowAuthentication . auth ,
params
} ) ;
const comments = response ? . data ? . result || [ ] ;
2025-10-13 06:06:52 -03:00
2025-10-07 10:28:39 -03:00
if ( ! Array . isArray ( comments ) || comments . length === 0 ) {
break ; // não há mais comentários
}
allComments . push ( ... comments ) ;
if ( comments . length < pageSize ) {
break ; // última página
}
// avançar para próxima página
offset += pageSize ;
// proteção extra para evitar loop infinito em caso de comportamento inesperado da API
if ( page >= internalMaxPages ) {
logError ( new Error ( 'Max pages reached while paginating comments' ) , { ticketId , page } ) ;
break ;
}
}
2025-10-07 10:57:09 -03:00
logInfo ( ` Total de comentários encontrados para o ticket SN Ticket ${ ticketId } : ${ allComments . length } comentários ` , { ticketId , count : allComments . length } ) ;
2025-10-07 10:28:39 -03:00
2025-10-07 10:39:01 -03:00
// salvar em arquivo para análise
const filename = ` servicenow_comments_ticket_ ${ ticketId } .json ` ;
2025-10-10 08:12:26 -03:00
// Salva no arquivo
2025-10-07 10:39:01 -03:00
fs . writeFileSync ( filename , JSON . stringify ( allComments , null , 2 ) ) ;
2025-10-10 08:12:26 -03:00
2025-10-13 06:06:52 -03:00
//Trata os dados para inserir no banco intermediario a
// as os tickets que não são do sys_created_by: "SOTHIS.CAOA"
2025-10-10 08:12:26 -03:00
const filteredComments = allComments . filter ( comment => comment . sys _created _by !== 'SOTHIS.CAOA' ) ;
2025-10-13 06:06:52 -03:00
2025-10-10 08:12:26 -03:00
// Insere cada comment no banco intermediario
for ( const comment of filteredComments ) {
2025-10-13 06:06:52 -03:00
const existingComment = await TicketUpdateModel . getCommentSync ( ticketId , comment . sys _id ) ;
2025-10-10 08:12:26 -03:00
if ( existingComment ) {
logInfo ( ` Comentário: ${ comment . sys _id } já existe no banco de dados ` , { step : 3 } ) ;
continue ;
} else {
2025-10-13 06:06:52 -03:00
2025-10-10 08:12:26 -03:00
console . log ( comment ) ;
const ticketSnId = await TicketSyncModel . getIdSyncBySnId ( ticketId ) ;
2025-10-13 06:06:52 -03:00
const ticketSyncId = await TicketSyncModel . getIdSyncBySnId ( ticketId ) ;
2025-10-10 08:12:26 -03:00
const commentData = {
ticket _sync _id : parseInt ( ticketSnId ) ,
2025-10-13 06:06:52 -03:00
ticket _sync _id : ticketSyncId ,
2025-10-10 08:12:26 -03:00
source _system : 'servicenow' ,
content : comment . value ,
created _at : new Date ( comment . sys _created _on ) ,
author : "CAOA" ,
updated _at : new Date ( ) ,
source _id : comment . sys _id ,
destiny _id : null
} ;
2025-10-13 06:06:52 -03:00
2025-10-10 08:12:26 -03:00
commentInserted = await TicketUpdateModel . insert ( commentData ) ;
2025-10-13 06:06:52 -03:00
if ( ! ticketSyncId ) {
logError ( ` ❌ TicketSyncId não encontrado para o ticket SN ID: ${ ticketId } ` , { step : 4 } ) ;
continue ;
}
const commentInserted = await TicketUpdateModel . insert ( commentData ) ;
2025-10-10 08:12:26 -03:00
if ( commentInserted . success ) {
2025-10-13 06:06:52 -03:00
// Insere Glpi status como
// ding e SN status como synced
await TicketSyncModel . updateStatus ( ticketGlpiId . id , '
ding ', ' synced ' ) ;
// Atualiza o status para indicar que o SN está sincronizado e o GLPI está
// dente de atualização
await TicketSyncModel . updateStatus ( ticketSyncId , '
ding ', ' synced ' ) ;
2025-10-10 08:12:26 -03:00
logInfo ( ` ✅ Comentário ID ${ comment . sys _id } inserido no banco! Novo ID: ${ commentInserted . newId } ` , { step : 4 } ) ;
} else {
logError ( ` ❌ Erro ao inserir comentário ID ${ comment . sys _id } no banco: ${ commentInserted . error } ` , { step : 4 } ) ;
}
if ( ! ticketSyncId ) {
logError ( ` ❌ TicketSyncId não encontrado para o ticket GLPI ID: ${ ticketId } ` , { step : 4 } ) ;
continue ;
} else {
continue ;
}
}
}
2025-10-07 10:39:01 -03:00
logInfo ( ` 💾 Backup JSON de comentários salvo: ${ filename } ` , { count : allComments . length } ) ;
2025-10-07 10:28:39 -03:00
} catch ( error ) {
logError ( error , '🚨Falha ao buscar comentários do ServiceNow' ) ;
return [ ] ;
}
} ;
2025-08-30 19:04:35 -03:00
module . exports = {
2025-09-03 16:11:30 -03:00
fetchTicketsFromServiceNow ,
2025-09-25 15:59:52 -03:00
fetchRequestsFromServiceNow ,
getTicketSysId ,
createCommentInServiceNow ,
getCommentId ,
2025-10-02 10:16:56 -03:00
existingCommentInServiceNow ,
2025-10-07 10:28:39 -03:00
syncCommentToServiceNow ,
2025-10-10 08:12:26 -03:00
fetchCommentsFromServiceNow
2025-09-05 20:28:15 -03:00
}