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-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-05 20:28:15 -03:00
// comentado para evitar muitos arquivos
2025-09-15 20:55:19 -03:00
//fs.writeFileSync('servicenow_tickets.json', JSON.stringify(ticketsResponse.data, null, 2));
2025-09-05 20:28:15 -03:00
//logInfo('💾 Backup JSON de incidentes salvo', { count: ticketsResponse.data.result.length });
2025-09-03 21:40:04 -03:00
// Salvar os tickets no banco de dados
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-09-05 20:28:15 -03:00
await TicketSnModel . saveTicket ( ticket , 'incidente' ) ;
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
// Comentado para evitar muitos arquivos
2025-09-15 20:55:19 -03:00
//fs.writeFileSync('servicenow_requests.json', JSON.stringify(requestsResponse.data, null, 2));
2025-09-05 20:28:15 -03:00
//logInfo('💾 Backup JSON de requisições salvo', { count: requestsResponse.data.result.length });
2025-09-03 16:11:30 -03:00
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-09-05 20:28:15 -03:00
await TicketSnModel . saveTicket ( request , 'requisicao' ) ;
2025-09-03 21:40:04 -03:00
}
2025-09-05 20:28:15 -03:00
logSync ( 'ServiceNow' , requestsResponse . data . result . length , 'requisições' ) ;
2025-09-03 21:40:04 -03:00
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
const getAllCommentsFromServiceNow = async ( ticketId ) => {
try {
const sys _id = await getTicketSysId ( ticketId ) ;
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 || [ ] ;
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:39:01 -03:00
// salvar em arquivo para análise
const filename = ` servicenow_comments_ticket_ ${ ticketId } .json ` ;
fs . writeFileSync ( filename , JSON . stringify ( allComments , null , 2 ) ) ;
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 ,
getAllCommentsFromServiceNow
2025-09-05 20:28:15 -03:00
}