// 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 }); logInfo('💾 Iniciando salvamento de incidentes no banco...'); for (const ticket of ticketsResponse.data.result) { //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' } //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'); } 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 }); // Salvar as requisições no banco de dados logInfo('💾 Iniciando salvamento de requisições no banco...'); for (const request of requestsResponse.data.result) { //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 (request.state?.display_value === 'Encerrado' || request.state?.display_value === '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'); } 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; } } // Função para obter o tipo de um ticket pela tabela de sincronização do banco de dados local 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 { 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 { 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'); } } // Função para criar um comentário 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 (ticket) => { try { logInfo(`Iniciando busca de comentários do ServiceNow para o ticket SN Ticket: ${ticket.glpi_ticket_id}`); await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'synced', 'pending_cmt_check'); const sys_id = ticket.sys_id; 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: ${ticket.glpi_ticket_id}`); 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; } } // 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 []; } logInfo(`Encontrado ${filteredComments.length} comentários para o ticket SN Ticket: ${ticket.glpi_ticket_id}`) const commentsInserted = []; // Insere cada comment no banco intermediario for (const comment of filteredComments) { await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'synced', 'pendind_cmt_sync'); const existingComment = await TicketUpdateModel.getCommentSync(comment.sys_id); if (existingComment) { TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'synced', 'synced'); logInfo(`Comentário: ${comment.sys_id} já existe no banco de dados`, { step: 3 }); continue; } else { const commentData = { ticket_sync_id: ticket.id, 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 }; const commentInserted = await TicketUpdateModel.insert(commentData); if (commentInserted.success) { 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 }); } else { logError(`❌ Erro ao inserir comentário ID ${comment.sys_id} no banco: ${commentInserted.error}`, { step: 4 }); return []; } } } } catch (error) { logError(error, '🚨Falha ao buscar comentários do ServiceNow'); return []; } }; module.exports = { fetchTicketsFromServiceNow, fetchRequestsFromServiceNow, getTicketSysId, createCommentInServiceNow, getCommentId, existingCommentInServiceNow, syncCommentToServiceNow, fetchCommentsFromServiceNow }