334 lines
12 KiB
JavaScript
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
|
|
} |