snglpi/src/services/servicenowService.js
2025-10-10 08:12:26 -03:00

334 lines
12 KiB
JavaScript

// src/services/servicenowService.js
const axios = require('axios');
const apiConfig = require('../../config/apiConfig');
const TicketSnModel = require('../models/ticketSnModel');
const { logInfo, logError, logSync } = require('../utils/logger');
const fs = require('fs');
const TicketUpdateModel = require('../models/ticketUpdateModel');
const TicketSyncModel = require('../models/ticketSyncModel');
const fetchTicketsFromServiceNow = async () => {
try {
logInfo('📡 Buscando incidentes do ServiceNow...', { service: 'servicenow' });
const ticketsResponse = await axios.get(apiConfig.snIncidentConfig.baseUrl, {
auth: apiConfig.servicenowAuthentication.auth
});
// comentado para evitar muitos arquivos
//fs.writeFileSync('servicenow_tickets.json', JSON.stringify(ticketsResponse.data, null, 2));
//logInfo('💾 Backup JSON de incidentes salvo', { count: ticketsResponse.data.result.length });
// Salvar os tickets no banco de dados
logInfo('💾 Iniciando salvamento de incidentes no banco...');
for (const ticket of ticketsResponse.data.result) {
await TicketSnModel.saveTicket(ticket, 'incidente');
}
logSync('ServiceNow', ticketsResponse.data.result.length, 'incidentes');
} catch (error) {
logError(error, 'SERVICE NOW Tickets');
}
}
const fetchRequestsFromServiceNow = async () => {
try {
logInfo('📡 Buscando requisições do ServiceNow...', { service: 'servicenow' });
const requestsResponse = await axios.get(apiConfig.snRequestConfig.baseUrl, {
auth: apiConfig.servicenowAuthentication.auth
});
// Comentado para evitar muitos arquivos
//fs.writeFileSync('servicenow_requests.json', JSON.stringify(requestsResponse.data, null, 2));
//logInfo('💾 Backup JSON de requisições salvo', { count: requestsResponse.data.result.length });
// Salvar as requisições no banco de dados
logInfo('💾 Iniciando salvamento de requisições no banco...');
for (const request of requestsResponse.data.result) {
await TicketSnModel.saveTicket(request, 'requisicao');
}
logSync('ServiceNow', requestsResponse.data.result.length, 'requisições');
} catch (error) {
logError(error, 'SERVICE NOW Requests');
}
}
// Função para obter o sys_id de um ticket pela tabela de sincronização do banco de dados local
const getTicketSysId = async (ticketId) => {
try {
const ticket = await TicketSnModel.findById(ticketId);
return ticket.sys_id;
} catch (error) {
logError(error, '🚨Falha ao obter sys_id do ticket');
return null;
}
}
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;
}
}
// 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:
// 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
// exemplo de resposta da API:
/*
"result": [
{
"sys_id": "SYS_ID_DO_COMENTARIO",
"sys_created_on": "2025-10-02 12:24:19",
"element_id": "SYS_ID_DO_INCIDENTE",
"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`;
const response = await axios.get(url, {
auth: apiConfig.servicenowAuthentication.auth
});
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
}
}
} 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
const createCommentInServiceNow = async (sysId, comment, ticketType) => {
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."}'
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;
}
const response = await axios.patch(url, payload, {
auth: apiConfig.servicenowAuthentication.auth
});
// Verificar se retorna status 200 para sucesso
if (response.status !== 200) {
logError(`❌ Falha ao criar comentário. Status: ${response.status}`, { sysId, ticketType });
return null;
}
// retornar id do comentário criado
const commentId = await getCommentId(sysId, comment);
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 });
return commentId;
} catch (error) {
logError(error, '🚨Falha ao criar comentário no ServiceNow');
}
}
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;
}
}
// 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
// para fins de teste, gerar um arquivo JSON com os comentários de um ticket específico
const fetchCommentsFromServiceNow = async (ticketId) => {
try {
console.log(`Iniciando busca de comentários do ServiceNow para o ticket SN Ticket: ${ticketId}`);
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;
}
}
logInfo(`Total de comentários encontrados para o ticket SN Ticket ${ticketId}: ${allComments.length} comentários`, { ticketId, count: allComments.length });
// salvar em arquivo para análise
const filename = `servicenow_comments_ticket_${ticketId}.json`;
// Salva no arquivo
fs.writeFileSync(filename, JSON.stringify(allComments, null, 2));
//Trata os dados para inserir no banco intermediario 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');
// Insere cada comment no banco intermediario
for (const comment of filteredComments) {
const existingComment = await TicketUpdateModel.getCommentSync(ticketId, comment.sys_id);
if (existingComment) {
logInfo(`Comentário: ${comment.sys_id} já existe no banco de dados`, { step: 3 });
continue;
} else {
console.log(comment);
const ticketSnId = await TicketSyncModel.getIdSyncBySnId(ticketId);
const commentData = {
ticket_sync_id: parseInt(ticketSnId),
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
};
commentInserted = await TicketUpdateModel.insert(commentData);
if (commentInserted.success) {
// Insere Glpi status como pending e SN status como synced
await TicketSyncModel.updateStatus(ticketGlpiId.id, 'pending', 'synced');
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;
}
}
}
logInfo(`💾 Backup JSON de comentários salvo: ${filename}`, { count: allComments.length });
} catch (error) {
logError(error, '🚨Falha ao buscar comentários do ServiceNow');
return [];
}
};
module.exports = {
fetchTicketsFromServiceNow,
fetchRequestsFromServiceNow,
getTicketSysId,
createCommentInServiceNow,
getCommentId,
existingCommentInServiceNow,
syncCommentToServiceNow,
fetchCommentsFromServiceNow
}