diff --git a/src/app.js b/src/app.js index 7d1a4b5..fed4d7b 100644 --- a/src/app.js +++ b/src/app.js @@ -1,4 +1,5 @@ // src/app.js +require('dotenv').config(); const { processTicketsController } = require('./controllers/processTicketsController'); const { processCommentsController } = require('./controllers/processCommentsController'); const { logInfo } = require('./utils/logger'); @@ -10,7 +11,7 @@ logInfo('Aplicação iniciada', { async function main() { try { - await processTicketsController(); + //await processTicketsController(); await processCommentsController(); } catch (error) { logInfo('Erro na aplicação:', error); diff --git a/src/controllers/processCommentsController.js b/src/controllers/processCommentsController.js index 918b534..4c0e75e 100644 --- a/src/controllers/processCommentsController.js +++ b/src/controllers/processCommentsController.js @@ -1,12 +1,19 @@ const {fetchCommentsFromServiceNow } = require('../services/servicenowService'); -const { syncCommentsGlpitoSN } = require('../services/glpiCommentService'); +const { syncCommentsGlpitoSN, syncCommentsSNtoGlpi } = require('../services/glpiCommentService'); +const TicketSyncModel = require('../models/ticketSyncModel'); const processCommentsController = async () => { - await fetchCommentsFromServiceNow(); - await syncCommentsGlpitoSN(); -} + const ticketsSynced = await TicketSyncModel.getTicketsSynced(); + + for (ticket of ticketsSynced){ + const comments = await fetchCommentsFromServiceNow(ticket); + if(comments.length > 0){await syncCommentsSNtoGlpi(comments, ticket);} + await syncCommentsGlpitoSN(ticket); + } + +} module.exports = { processCommentsController } \ No newline at end of file diff --git a/src/models/ticketGlpiModel.js b/src/models/ticketGlpiModel.js index 13f4405..0306c3f 100644 --- a/src/models/ticketGlpiModel.js +++ b/src/models/ticketGlpiModel.js @@ -249,27 +249,27 @@ class TicketGlpiModel { static async ticketExists(snTicketNumber) { try { const query = ` - SELECT id FROM glpi_tickets - WHERE name LIKE ? OR content LIKE ? - LIMIT 1 - + SELECT id FROM glpi_tickets WHERE name LIKE ? OR content LIKE ? LIMIT 1 `; - const [rows] = await glpiPool.execute(query, [ + const values = [ `%${snTicketNumber}%`, `%${snTicketNumber}%` - ]); + ]; + + const [rows] = await glpiPool.execute(query,values); + return rows.length > 0 ? rows[0].id : null; } catch (error) { - logError(error, 'Erro ao verificar ticket existente'); + logError(`Erro ao verificar ticket existente: ${error}`); return null; } } // Buscar followups (notas) de um ticket no GLPI static async getFollowupsByItemId(itemId) { try { - const query = 'SELECT id, content, date, date_creation, date_mod FROM glpi_itilfollowups WHERE items_id = ?'; + const query = 'SELECT id, content, date, date_creation, date_mod FROM glpi_itilfollowups WHERE items_id = ? AND users_id != 971'; const [rows] = await glpiPool.execute(query, [itemId]); return rows; } catch (error) { @@ -278,9 +278,20 @@ class TicketGlpiModel { } } - + static async insertComment(comment, items_id) { + try { + const query = 'INSERT INTO glpi_itilfollowups(itemtype, items_id, date, users_id, content, date_creation, date_mod, timeline_position) VALUES(?, ?, ?, ?, ?, ?, ?, ?) RETURNING id'; + const values = ['Ticket', items_id, comment.created_at, 971, comment.content , comment.created_at, comment.created_at, 1]; + + const [rows] = await glpiPool.execute(query, values); + return rows; + }catch(error){ + logError(`Erro ao inserir o ticket: ${error}`); + } + } } + /** * @module TicketGlpiModel * @description Este módulo fornece métodos para gerenciar tickets no GLPI. diff --git a/src/models/ticketSnModel.js b/src/models/ticketSnModel.js index 4edad63..a54de3d 100644 --- a/src/models/ticketSnModel.js +++ b/src/models/ticketSnModel.js @@ -88,7 +88,8 @@ class TicketSnModel { ts.opened_at, ts.tipo, ts.created_at, - ts.updated_at + ts.updated_at, + tsync.glpi_ticket_id FROM tickets_sn ts LEFT JOIN ticket_sync tsync ON ts.id = tsync.sn_ticket_id WHERE tsync.glpi_sync_status = 'pending_check' diff --git a/src/models/ticketSyncModel.js b/src/models/ticketSyncModel.js index fdfae70..ba3deee 100644 --- a/src/models/ticketSyncModel.js +++ b/src/models/ticketSyncModel.js @@ -8,6 +8,8 @@ class TicketSyncModel { // Cria um registro de sincronização para um ticket static async createSyncRecord(data) { try { + + const query = `INSERT INTO ticket_sync ( sn_ticket_id, glpi_ticket_id, @@ -19,7 +21,20 @@ class TicketSyncModel { glpi_sync_status ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id`; - await pool.query(query, data); + const values = [ + data.sn_ticket_id, + data.glpi_ticket, + data.last_sn_sync, + data.last_glpi_sync, + data.created_at, + data.updated_at, + data.sn_sync_status, + data.glpi_sync_status + + ] + + + await pool.query(query, values); } catch (error) { logError(error, '❌ Erro ao criar registro de sincronização'); @@ -29,10 +44,11 @@ class TicketSyncModel { // Buscar tickets que foram criados com sucesso para sincronizar comentários - static async getTicketsToSync() { + static async getTicketsToSync(glpi_sync_status, sn_sync_status) { try { - const query = 'SELECT id, glpi_ticket_id, sn_ticket_id FROM ticket_sync'; - const { rows } = await pool.query(query); + const query = 'SELECT id, glpi_ticket_id, sn_ticket_id FROM ticket_sync WHERE glpi_sync_status = $1 AND sn_sync_status = $2'; + const values =[glpi_sync_status , sn_sync_status]; + const { rows } = await pool.query(query, values); return rows; } catch (error) { logError(error, '❌ Erro ao buscar tickets para sincronizar'); @@ -43,15 +59,24 @@ class TicketSyncModel { // Atualiza o status de sincronização de um ticket static async updateStatus(ticketSyncId, glpiStatus, snStatus) { try { - const query = 'UPDATE ticket_sync SET glpi_sync_status = $1, sn_sync_status = $2 WHERE id = $3'; + const query = 'UPDATE ticket_sync SET glpi_sync_status = $1, sn_sync_status = $2 WHERE sn_ticket_id = $3'; await pool.query(query, [glpiStatus, snStatus, ticketSyncId]); + } catch (error) { + logError(`${error} ❌ Erro ao atualizar status de sincronização para o ID: ${ticketSyncId}`); + throw error; + } + } + + static async updateStatusAndGlpiTicket(ticketSyncId, glpiStatus, snStatus, glpiTicketId) { + try { + const query = 'UPDATE ticket_sync SET glpi_sync_status = $1, sn_sync_status = $2, glpi_ticket_id = $4 WHERE sn_ticket_id = $3'; + await pool.query(query, [glpiStatus, snStatus, ticketSyncId, glpiTicketId]); } catch (error) { logError(error, `❌ Erro ao atualizar status de sincronização para o ID: ${ticketSyncId}`); throw error; } } - // Buscar o ID de sincronização baseado no glpi_ticket_id static async getIdSyncByGlpiId(glpiTicketId) { try { @@ -77,14 +102,38 @@ class TicketSyncModel { } static async getByTicketNumber(ticketNumber) { try { - const query = 'SELECT * FROM ticket_sync WHERE ticket_number = $1'; + console.log(ticketNumber) + const query = 'SELECT * FROM ticket_sync WHERE sn_ticket_id = $1'; + const { rows } = await pool.query(query, [ticketNumber]); + return rows[0] || null; } catch (error) { - + logError(`${error}, ❌ Erro ao buscar ticket ${ticketNumber}}` ) } } + // Buscar tickets que estão sincronizados + static async getTicketsSynced() { + try { + const query = ` + SELECT + tsync.id, + tsync.glpi_ticket_id, + tsync.sn_ticket_id, + tsn.sys_id + FROM ticket_sync tsync + JOIN tickets_sn tsn ON tsync.sn_ticket_id = tsn.id + WHERE tsync.glpi_sync_status = 'synced' AND tsync.sn_sync_status = 'synced'`; + const { rows } = await pool.query(query); + return rows; + } catch (error) { + logError(error, '❌ Erro ao buscar tickets para sincronizar'); + throw error; + } + } + + } /** diff --git a/src/models/ticketUpdateModel.js b/src/models/ticketUpdateModel.js index 93df78a..8926784 100644 --- a/src/models/ticketUpdateModel.js +++ b/src/models/ticketUpdateModel.js @@ -1,7 +1,9 @@ // src/models/ticketUpdateModel.js +const ticketSyncModel = require('./ticketSyncModel'); const pool = require('../data/database'); const { logError } = require('../utils/logger'); + class TicketUpdateModel { static async insert(updateData) { @@ -17,7 +19,7 @@ class TicketUpdateModel { created_at = $6, updated_at = $7, destiny_id = $9 - RETURNING id; + RETURNING *; `; const values = [ updateData.ticket_sync_id, @@ -42,7 +44,7 @@ class TicketUpdateModel { //Buscar o ID de sincronização baseado no source_id static async getIdSyncBySourceId(sourceId) { try { - const query = 'SELECT id FROM ticket_updates WHERE source_id = $1'; + const query = 'SELECT tsync.id FROM ticket_updates tupd JOIN ticket_sync tsync ON tupd.ticket_sync_id = tsync.id WHERE source_id = $1'; const { rows } = await pool.query(query, [sourceId]); return rows[0] ? rows[0].id : null; } catch (error) { @@ -65,10 +67,9 @@ class TicketUpdateModel { throw error; } } - static async getCommentSync(glpiTicketId, sourceId) { - const ticketSyncModel = require('./ticketSyncModel'); - const ticketSyncId = await TicketUpdateModel.getIdSyncBySourceId(sourceId); + static async getCommentSync(sourceId) { + const ticketSyncId = await TicketUpdateModel.getIdSyncBySourceId(sourceId); if (!ticketSyncId) { return null; } diff --git a/src/services/glpiCommentService.js b/src/services/glpiCommentService.js index 016a5e8..3255ab9 100644 --- a/src/services/glpiCommentService.js +++ b/src/services/glpiCommentService.js @@ -6,123 +6,110 @@ const TicketUpdateModel = require('../models/ticketUpdateModel'); const { logInfo, logError } = require('../utils/logger'); const { existingCommentInServiceNow, syncCommentToServiceNow } = require('./servicenowService'); const { sanitizeGLPIComment } = require('../utils/commentSanitizer'); +const { log } = require('winston'); // Sincroniza comentários do GLPI para o Banco de Dados local -const syncCommentsGlpitoSN = async () => { - try { - - logInfo('🔄 Iniciando sincronização de comments do GLPI para o Service Now...'); - const effectiveTickets = await TicketSyncModel.getTicketsToSync(); - logInfo(`🔍 ${effectiveTickets.length} tickets encontrados para sincronização de comentários`, { step: 1 }); +const syncCommentsGlpitoSN = async (ticket) => { + try { + logInfo('🔄 Iniciando sincronização de comments do GLPI para o Service Now...'); - for (const ticket of effectiveTickets) { + const comments = await TicketGlpiModel.getFollowupsByItemId(ticket.glpi_ticket_id); + if (comments.length === 0) { + logInfo(`Nenhum comentário encontrado no GLPI para o ticket GLPI ID: ${ticket.glpi_ticket_id}`); + return; + } - logInfo(`Buscando comentários para o ticket GLPI ID: ${ticket.glpi_ticket_id}`); - const comments = await TicketGlpiModel.getFollowupsByItemId(ticket.glpi_ticket_id); - logInfo(`📝 ${comments.length} comentários encontrados para o ticket GLPI ID: ${ticket.glpi_ticket_id}`, { step: 2 }); + logInfo(`📝 ${comments.length} comentários encontrados (GLPI ID: ${ticket.glpi_ticket_id})`); + await TicketSyncModel.updateStatus(ticket.glpi_ticket_id, 'comment_collected', 'pending_cmt_check'); - for (const comment of comments) { + for (const comment of comments) { + try { + const sanitized = sanitizeGLPIComment(comment); + const existingComment = await TicketUpdateModel.getCommentSync(ticket.glpi_ticket_id, comment.id); + const existsInSN = await existingCommentInServiceNow(ticket.sn_ticket_id, sanitized); + const syncId = await TicketSyncModel.getIdSyncByGlpiId(ticket.glpi_ticket_id); - // DEBUG: Mostra antes e depois - logInfo(`🔍 Processando comentário GLPI ID: ${comment.id}`); - logInfo(`📝 CONTEÚDO ORIGINAL: ${comment.content.substring(0, 200)}...`); - - // Sanitiza o comentário - const sanitizedContent = sanitizeGLPIComment(comment); - - logInfo(`✨ CONTEÚDO SANITIZADO: ${sanitizedContent.substring(0, 200)}...`); - - const existingComment = await TicketUpdateModel.getCommentSync(ticket.glpi_ticket_id, comment.id); - - const commentExistsInSN = await existingCommentInServiceNow( - ticket.sn_ticket_id, - sanitizedContent // ✅ Usa o conteúdo sanitizado - ); - - if (existingComment && commentExistsInSN) { - logInfo(`Comentário ID ${comment.id} já sincronizado no banco de dados e no ServiceNow`, { step: 3 }); - continue; - } - - if (!existingComment && commentExistsInSN) { - // Só insere no banco, pois já está no ServiceNow - const sync_id = await TicketSyncModel.getIdSyncByGlpiId(ticket.glpi_ticket_id); - const commentData = { - ticket_sync_id: sync_id, - source_system: 'glpi', - content: sanitizedContent, // ✅ Usa o conteúdo sanitizado - created_at: new Date(comment.date_creation), - author: 'Sothis', - updated_at: new Date(comment.date_mod), - source_id: comment.id, - destiny_id: commentExistsInSN.destiny_id - }; - const insertResult = await TicketUpdateModel.insert(commentData); - if (insertResult.success) { - logInfo(`✅ Comentário ID ${comment.id} inserido no banco! Novo ID: ${insertResult.newId}`, { step: 4 }); - } else { - logError(`❌ Erro ao inserir comentário ID ${comment.id} no banco: ${insertResult.error}`, { step: 4 }); - } - continue; - } - - if (existingComment && !commentExistsInSN) { - logInfo(`Comentário ID ${comment.id} já existe no banco, mas não no ServiceNow. Sincronizando...`, { step: 3 }); - // Só sincroniza no ServiceNow - const snCommentId = await syncCommentToServiceNow(ticket.sn_ticket_id, sanitizedContent); // ✅ Usa o conteúdo sanitizado - logInfo(`✅ Comentário ID ${comment.id} sincronizado no ServiceNow!`, { step: 4 }); - const updateData = await TicketUpdateModel.updateDestinyId(comment.id, snCommentId); - if (updateData && updateData.success) { - logInfo(`✅ ID do comentário de destino atualizado no banco: ${snCommentId}`, { step: 4 }); - } - continue; - } - - // Se não existe em nenhum dos dois, sincroniza nos dois normalmente - const snCommentId = await syncCommentToServiceNow(ticket.sn_ticket_id, sanitizedContent); // ✅ Usa o conteúdo sanitizado - const sync_id = await TicketSyncModel.getIdSyncByGlpiId(ticket.glpi_ticket_id); - const commentData = { - ticket_sync_id: sync_id, - source_system: 'glpi', - content: sanitizedContent, // ✅ Usa o conteúdo sanitizado - created_at: new Date(comment.date_creation), - updated_at: new Date(comment.date_mod), - source_id: comment.id, - destiny_id: snCommentId - }; - const insertResult = await TicketUpdateModel.insert(commentData); - if (insertResult.success) { - logInfo(`✅ Comentário ID ${comment.id} sincronizado no banco e no ServiceNow! Novo ID: ${insertResult.newId}`, { step: 4 }); - } else { - logError(`❌ Erro ao sincronizar comentário ID ${comment.id}: ${insertResult.error}`, { step: 4 }); - } - - const hasError = !insertResult.success; - const snStatus = 'pending'; - const glpiStatus = hasError ? 'Error' : 'updated'; - await TicketSyncModel.updateStatus(ticket.id, glpiStatus, snStatus); - logInfo(`Status de sincronização para o ticket ID ${ticket.id} atualizado para GLPI: '${glpiStatus}', SN: '${snStatus}'.`); - } + // Caso 1: já sincronizado + if (existingComment && existsInSN) { + logInfo(`🔁 Comentário ${comment.id} já sincronizado.`); + await TicketSyncModel.updateStatus(ticket.glpi_ticket_id, 'synced', 'synced'); + continue; } - logInfo('✅ Sincronização de comentários do GLPI -> ServiceNow concluída!'); - } catch (error) { - logError(error, 'GLPI Tickets'); + // Caso 2: está no SN mas não no banco + if (!existingComment && existsInSN) { + await TicketUpdateModel.insert({ + ticket_sync_id: syncId, + source_system: 'glpi', + content: sanitized, + created_at: new Date(comment.date_creation), + updated_at: new Date(comment.date_mod), + source_id: comment.id, + destiny_id: existsInSN.destiny_id + }); + await TicketSyncModel.updateStatus(ticket.glpi_ticket_id, 'synced', 'synced'); + continue; + } + + // Caso 3: existe no banco mas falta no SN + if (existingComment && !existsInSN) { + await TicketSyncModel.updateStatus(ticket.glpi_ticket_id, 'synced', 'pending_cmt_sync'); + const snCommentId = await syncCommentToServiceNow(ticket.sn_ticket_id, sanitized); + await TicketUpdateModel.updateDestinyId(comment.id, snCommentId); + continue; + } + + // Caso 4: não existe em nenhum lugar → sincroniza ambos + await TicketSyncModel.updateStatus(ticket.glpi_ticket_id, 'pending_cmt_sync', 'pending_cmt_sync'); + const snCommentId = await syncCommentToServiceNow(ticket.sn_ticket_id, sanitized); + await TicketUpdateModel.insert({ + ticket_sync_id: syncId, + source_system: 'glpi', + content: sanitized, + created_at: new Date(comment.date_creation), + updated_at: new Date(comment.date_mod), + source_id: comment.id, + destiny_id: snCommentId + }); + await TicketSyncModel.updateStatus(ticket.glpi_ticket_id, 'synced', 'synced'); + + } catch (err) { + logError(`Erro ao sincronizar comentário ${comment.id}: ${err}`); + await TicketSyncModel.updateStatus(ticket.glpi_ticket_id, 'error', 'error'); + } } -} -const syncCommentsSNtoGlpi = async () => { - try { - logInfo('🔄 Iniciando sincronização de comments do Service Now para o GLPI...'); + logInfo('✅ Sincronização de comentários GLPI → SN concluída!'); + } catch (error) { + logError(`Erro geral na sincronização: ${error}`); + } +}; +const syncCommentsSNtoGlpi = async (comments, ticket) => { + logInfo('🔄 Iniciando sincronização de comments do ServiceNow para o GLPI...'); - + for (const comment of comments) { + try { + const commentId = await TicketGlpiModel.insertComment(comment, ticket.glpi_ticket_id); + + await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'synced', 'synced'); + await TicketUpdateModel.updateDestinyId(comment.sys_id, commentId); + + logInfo(`✅ Comentário inserido com sucesso no GLPI para o chamado: ${ticket.glpi_ticket_id}`); + + } catch (error) { + logError(`⚠️ Erro ao processar comentário do ticket ${ticket.sn_ticket_id}: ${error}`); + + try { + await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'synced', 'error'); + } catch (err2) { + logError(`Falha ao atualizar status de erro do ticket ${ticket.sn_ticket_id}: ${err2}`); + } + } } - catch (error) { - logError(error, 'ServiceNow Comments'); - } -} +}; + diff --git a/src/services/glpiTicketService.js b/src/services/glpiTicketService.js index a6e4364..fc8fd1b 100644 --- a/src/services/glpiTicketService.js +++ b/src/services/glpiTicketService.js @@ -37,29 +37,25 @@ const processSingleTicket = async (ticket) => { try { logInfo(`🔍 Processando ticket: ${ticket.ticket_number}`); - // 1. Verificar se já existe no GLPI - const existingTicket = await TicketGlpiModel.ticketExists(ticket.ticket_number); - const idSyncRecord = await TicketSyncModel.getByTicketNumber(ticket.ticket_number); - - - if (existingTicket && idSyncRecord) { - await TicketSyncModel.updateGlpiSyncStatus(idSyncRecord.id, 'synced', existingTicket); - await TicketSyncModel.updateSnSyncStatus(idSyncRecord.id, 'synced'); + // 1. Verifica se o ticket já possui um ID do GLPI no nosso banco de sincronização. + if (ticket.glpi_ticket_id) { + logInfo(`✅ Ticket ${ticket.ticket_number} já possui GLPI ID (${ticket.glpi_ticket_id}) registrado. Apenas atualizando status.`); + await TicketSyncModel.updateStatus(ticket.id, 'synced', 'synced'); return; - }else if (existingTicket && !idSyncRecord) { - await TicketSyncModel.createSyncRecord({ - sn_ticket_id: ticket.id, - glpi_ticket_id: existingTicket, - sn_sync_status: 'synced', - glpi_sync_status: 'synced', - last_glpi_sync: new Date() - }); - return; - } + // 2. Se não tem ID local, verifica se o ticket existe no GLPI (buscando pelo número). + const existingTicket = await TicketGlpiModel.ticketExists(ticket.ticket_number); - // 2. Converter dados para formato GLPI + if (existingTicket) { + logInfo(`✅ Ticket ${ticket.ticket_number} encontrado no GLPI. Atualizando registro local com o ID: ${existingTicket}`); + // Atualiza nosso registro de sincronização com o ID do GLPI encontrado. + await TicketSyncModel.updateStatusAndGlpiTicket(ticket.id, 'synced', 'synced', existingTicket); + logInfo(`✅ Ticket ${ticket.ticket_number} já existe no GLPI! GLPI ID: ${existingTicket}`); + return; + } + + // 3. Converter dados para formato GLPI const glpiData = { ticket_number: ticket.ticket_number, short_description: ticket.short_description, @@ -80,8 +76,8 @@ const processSingleTicket = async (ticket) => { // 3. Inserir no GLPI const glpiTicketId = await TicketGlpiModel.createTicket(glpiData); - // 4. Criar registro de sincronização - await TicketSyncModel.updateGlpiSyncStatus(idSyncRecord.id, 'synced', glpiTicketId); + // 4. Atualizar registro de sincronização + await TicketSyncModel.updateStatus(ticket.id, 'synced', 'synced'); logInfo(`✅ Ticket ${ticket.ticket_number} sincronizado! GLPI ID: ${glpiTicketId}`); @@ -89,16 +85,10 @@ const processSingleTicket = async (ticket) => { logError(error, `❌ Erro no ticket ${ticket.ticket_number}`); // Se temos um registro de sync, atualizar o status para error - if (syncRecord) { - await TicketSyncModel.updateGlpiSyncStatus(idSyncRecord.id, 'error'); - } else { - // Se não temos um registro, criar um com status error - try { - syncRecord = await TicketSyncModel.createSyncRecord(ticket.id); - await TicketSyncModel.updateGlpiSyncStatus(idSyncRecord.id, 'error'); - } catch (innerError) { + try { + await TicketSyncModel.updateStatus(idSyncRecord.id, 'error', 'synced'); + } catch (innerError) { logError(innerError, `❌ Erro ao registrar falha de sincronização para ticket ${ticket.id}`); - } } } }; diff --git a/src/services/servicenowService.js b/src/services/servicenowService.js index 469b99b..a989236 100644 --- a/src/services/servicenowService.js +++ b/src/services/servicenowService.js @@ -18,10 +18,8 @@ const fetchTicketsFromServiceNow = async () => { 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'); + const idTableTicketSn = await TicketSnModel.saveTicket(ticket, 'incidente'); //Salvando atualização no banco intermediario e defininfo sync status const ticketStatus = ticket.state?.display_value; @@ -42,12 +40,10 @@ const fetchTicketsFromServiceNow = async () => { dataSync.sn_sync_status ='synced' dataSync.glpi_sync_status = 'pending_check' } - - await TicketSyncModel.createSyncRecord(dataSync); - - - - + //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'); @@ -82,7 +78,7 @@ const fetchRequestsFromServiceNow = async () => { updated_at: null } - if (ticketStatus === 'Encerrado' || ticketStatus === 'Encerrado - Omitido') { + if (request.state?.display_value === 'Encerrado' || request.state?.display_value === 'Encerrado - Omitido') { dataSync.sn_sync_status ='closed' dataSync.glpi_sync_status = 'ignored' } else { @@ -103,7 +99,6 @@ const fetchRequestsFromServiceNow = async () => { } // 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); @@ -114,6 +109,7 @@ const getTicketSysId = async (ticketId) => { } } +// 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); @@ -129,21 +125,6 @@ const getTicketTypeById = async (ticketId) => { // 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=^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`; @@ -166,15 +147,9 @@ const getCommentId = async (sysId, comment) => { 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') { @@ -219,6 +194,7 @@ const createCommentInServiceNow = async (sysId, comment, ticketType) => { } } +// 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); @@ -233,7 +209,7 @@ const syncCommentToServiceNow = async (ticketId, comment) => { } // Função que verifica se o comentário já existe no ServiceNow antes de criar - const existingCommentInServiceNow = async (ticketId, comment) => { +const existingCommentInServiceNow = async (ticketId, comment) => { try { const sys_id = await getTicketSysId(ticketId); const commentExists = await getCommentId(sys_id, comment); @@ -248,26 +224,15 @@ const syncCommentToServiceNow = async (ticketId, comment) => { // 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 () => { +const fetchCommentsFromServiceNow = async (ticket) => { try { + + logInfo(`Iniciando busca de comentários do ServiceNow para o ticket SN Ticket: ${ticket.glpi_ticket_id}`); - const ticketsSyncList = await TicketSyncModel.getTicketsToSync(); + await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'synced', 'pending_cmt_check'); + + const sys_id = ticket.sys_id; - - - - - console.log(`Iniciando busca de comentários do ServiceNow para o ticket SN Ticket: ${ticketId}`); - - //Coleta o sys id no bando intermediario para busca no service now - const sys_id = await getTicketSysId(ticketId); - - //Se não encontrar é impossível atualizar - if (!sys_id) { - logError('sys_id não encontrado para ticket', { ticketId }); - return []; - } const allComments = []; const pageSize = 100; @@ -283,8 +248,7 @@ const fetchCommentsFromServiceNow = async () => { sysparm_limit: pageSize, sysparm_offset: offset }; - - logInfo(`Buscando página ${page} de comentários do ticket SN Ticket: ${ticketId}`, { ticketId, page }); + 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 @@ -295,7 +259,7 @@ const fetchCommentsFromServiceNow = async () => { if (!Array.isArray(comments) || comments.length === 0) { break; // não há mais comentários - } + } allComments.push(...comments); @@ -312,39 +276,35 @@ const fetchCommentsFromServiceNow = async () => { 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 a - // as os tickets que não são do sys_created_by: "SOTHIS.CAOA" + + // 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) { - const existingComment = await TicketUpdateModel.getCommentSync(ticketId, comment.sys_id); + 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 { - - console.log(comment); - - const ticketSnId = await TicketSyncModel.getIdSyncBySnId(ticketId); - const ticketSyncId = await TicketSyncModel.getIdSyncBySnId(ticketId); - const commentData = { - ticket_sync_id: parseInt(ticketSnId), - ticket_sync_id: ticketSyncId, + ticket_sync_id: ticket.id, source_system: 'servicenow', content: comment.value, created_at: new Date(comment.sys_created_on), @@ -354,38 +314,22 @@ const fetchCommentsFromServiceNow = async () => { destiny_id: null }; - commentInserted = await TicketUpdateModel.insert(commentData); - - if (!ticketSyncId) { - logError(`❌ TicketSyncId não encontrado para o ticket SN ID: ${ticketId}`, { step: 4 }); - continue; - } - const commentInserted = await TicketUpdateModel.insert(commentData); if (commentInserted.success) { - // Insere Glpi status como - // ding e SN status como synced - await TicketSyncModel.updateStatus(ticketGlpiId.id, ' - ding', 'synced'); - // Atualiza o status para indicar que o SN está sincronizado e o GLPI está - // dente de atualização - await TicketSyncModel.updateStatus(ticketSyncId, ' - ding', 'synced'); - logInfo(`✅ Comentário ID ${comment.sys_id} inserido no banco! Novo ID: ${commentInserted.newId}`, { step: 4 }); + 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 []; } - 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 [];