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
|
2025-10-17 16:58:14 -03:00
|
|
|
const idTableTicketSn = await TicketSnModel.saveTicket(ticket, 'incidente');
|
2025-10-13 06:06:52 -03:00
|
|
|
|
|
|
|
|
//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'
|
|
|
|
|
}
|
2025-10-17 16:58:14 -03:00
|
|
|
//Cria apenas se não existir
|
|
|
|
|
const exists = await TicketSyncModel.getIdSyncBySnId(idTableTicketSn);
|
|
|
|
|
if (!exists) await TicketSyncModel.createSyncRecord(dataSync);
|
|
|
|
|
else await TicketSyncModel.updateStatus(exists, 'synced', 'pending_check');
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-17 16:58:14 -03:00
|
|
|
if (request.state?.display_value === 'Encerrado' || request.state?.display_value === 'Encerrado - Omitido') {
|
2025-10-13 06:06:52 -03:00
|
|
|
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-17 16:58:14 -03:00
|
|
|
// Função para obter o tipo de um ticket pela tabela de sincronização do banco de dados local
|
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 {
|
2025-10-02 10:16:56 -03:00
|
|
|
|
|
|
|
|
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 {
|
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-17 16:58:14 -03:00
|
|
|
// Função para criar um comentário
|
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
|
2025-10-17 16:58:14 -03:00
|
|
|
const existingCommentInServiceNow = async (ticketId, comment) => {
|
2025-10-02 10:16:56 -03:00
|
|
|
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-17 16:58:14 -03:00
|
|
|
const fetchCommentsFromServiceNow = async (ticket) => {
|
2025-10-07 10:28:39 -03:00
|
|
|
try {
|
2025-10-17 16:58:14 -03:00
|
|
|
|
|
|
|
|
logInfo(`Iniciando busca de comentários do ServiceNow para o ticket SN Ticket: ${ticket.glpi_ticket_id}`);
|
2025-10-10 08:12:26 -03:00
|
|
|
|
2025-10-17 16:58:14 -03:00
|
|
|
await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'synced', 'pending_cmt_check');
|
2025-10-13 06:06:52 -03:00
|
|
|
|
2025-10-17 16:58:14 -03:00
|
|
|
const sys_id = ticket.sys_id;
|
|
|
|
|
|
2025-10-07 10:28:39 -03:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
};
|
2025-10-17 16:58:14 -03:00
|
|
|
logInfo(`Buscando página ${page} de comentários do ticket SN Ticket: ${ticket.glpi_ticket_id}`);
|
2025-10-07 10:28:39 -03:00
|
|
|
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
|
2025-10-17 16:58:14 -03:00
|
|
|
}
|
2025-10-07 10:28:39 -03:00
|
|
|
|
|
|
|
|
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-13 06:06:52 -03:00
|
|
|
|
2025-10-17 16:58:14 -03:00
|
|
|
// Traz apenas os tickets que não são do sys_created_by: "SOTHIS.CAOA"
|
|
|
|
|
const filteredComments = allComments.filter(comment => comment.sys_created_by !== 'SOTHIS.CAOA');
|
|
|
|
|
if (filteredComments.length === 0) {
|
|
|
|
|
TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'synced ', 'synced');
|
|
|
|
|
logInfo(`Nenhum comentário encontrado no Service Now para o ticket GLPI ID: ${ticket.glpi_ticket_id}`);
|
|
|
|
|
return [];
|
|
|
|
|
}
|
2025-10-13 06:06:52 -03:00
|
|
|
|
2025-10-17 16:58:14 -03:00
|
|
|
logInfo(`Encontrado ${filteredComments.length} comentários para o ticket SN Ticket: ${ticket.glpi_ticket_id}`)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const commentsInserted = [];
|
2025-10-10 08:12:26 -03:00
|
|
|
// Insere cada comment no banco intermediario
|
|
|
|
|
for (const comment of filteredComments) {
|
|
|
|
|
|
2025-10-17 16:58:14 -03:00
|
|
|
await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'synced', 'pendind_cmt_sync');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const existingComment = await TicketUpdateModel.getCommentSync(comment.sys_id);
|
2025-10-10 08:12:26 -03:00
|
|
|
|
|
|
|
|
if (existingComment) {
|
2025-10-17 16:58:14 -03:00
|
|
|
TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'synced', 'synced');
|
2025-10-10 08:12:26 -03:00
|
|
|
logInfo(`Comentário: ${comment.sys_id} já existe no banco de dados`, { step: 3 });
|
|
|
|
|
continue;
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
|
|
const commentData = {
|
2025-10-17 16:58:14 -03:00
|
|
|
ticket_sync_id: ticket.id,
|
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
|
|
|
|
|
|
|
|
const commentInserted = await TicketUpdateModel.insert(commentData);
|
2025-10-10 08:12:26 -03:00
|
|
|
|
|
|
|
|
if (commentInserted.success) {
|
2025-10-17 16:58:14 -03:00
|
|
|
await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'pendind_cmt_sync', 'synced');
|
|
|
|
|
commentsInserted.push(commentInserted);
|
|
|
|
|
logInfo(`✅ Comentário ID ${comment.sys_id} do ${comment.source_system} inserido no banco!`, { step: 4 });
|
|
|
|
|
|
2025-10-10 08:12:26 -03:00
|
|
|
} else {
|
|
|
|
|
logError(`❌ Erro ao inserir comentário ID ${comment.sys_id} no banco: ${commentInserted.error}`, { step: 4 });
|
2025-10-17 16:58:14 -03:00
|
|
|
return [];
|
2025-10-10 08:12:26 -03:00
|
|
|
}
|
|
|
|
|
|
2025-10-17 16:58:14 -03:00
|
|
|
|
|
|
|
|
|
2025-10-10 08:12:26 -03:00
|
|
|
}
|
|
|
|
|
}
|
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
|
|
|
}
|