diff --git a/src/app.js b/src/app.js index d82acbc..7d1a4b5 100644 --- a/src/app.js +++ b/src/app.js @@ -1,6 +1,6 @@ // src/app.js -const { snServices } = require('./controllers/servicenowController'); -const { glpiServices } = require('./controllers/glpiController'); +const { processTicketsController } = require('./controllers/processTicketsController'); +const { processCommentsController } = require('./controllers/processCommentsController'); const { logInfo } = require('./utils/logger'); logInfo('Aplicação iniciada', { @@ -10,8 +10,8 @@ logInfo('Aplicação iniciada', { async function main() { try { - await snServices(); - await glpiServices(); + await processTicketsController(); + await processCommentsController(); } catch (error) { logInfo('Erro na aplicação:', error); } diff --git a/src/controllers/glpiController.js b/src/controllers/glpiController.js deleted file mode 100644 index a80268a..0000000 --- a/src/controllers/glpiController.js +++ /dev/null @@ -1,12 +0,0 @@ -// src/controllers/glpiController.js -const { syncTicketsToGlpi } = require('../services/glpiTicketService'); -const { syncCommentsGlpitoSN } = require('../services/glpiCommentService'); - -const glpiServices = async () => { - //await syncTicketsToGlpi(); - await syncCommentsGlpitoSN(); -} - -module.exports = { - glpiServices -} diff --git a/src/controllers/processCommentsController.js b/src/controllers/processCommentsController.js new file mode 100644 index 0000000..918b534 --- /dev/null +++ b/src/controllers/processCommentsController.js @@ -0,0 +1,12 @@ +const {fetchCommentsFromServiceNow } = require('../services/servicenowService'); +const { syncCommentsGlpitoSN } = require('../services/glpiCommentService'); + + +const processCommentsController = async () => { + await fetchCommentsFromServiceNow(); + await syncCommentsGlpitoSN(); +} + +module.exports = { + processCommentsController +} \ No newline at end of file diff --git a/src/controllers/processTicketsController.js b/src/controllers/processTicketsController.js new file mode 100644 index 0000000..dcf7f6f --- /dev/null +++ b/src/controllers/processTicketsController.js @@ -0,0 +1,19 @@ +// src/controllers/glpiController.js +const { syncTicketsToGlpi } = require('../services/glpiTicketService'); +const { fetchTicketsFromServiceNow, fetchRequestsFromServiceNow } = require('../services/servicenowService'); + + +const processTicketsController = async () => { + + //Busca Incidentes e Requisições do Service Now e salva no banco intermediario + await fetchTicketsFromServiceNow(); + await fetchRequestsFromServiceNow(); + + //Sincroniza os tickets com o GLPI + await syncTicketsToGlpi(); + +} + +module.exports = { + processTicketsController +} diff --git a/src/controllers/servicenowController.js b/src/controllers/servicenowController.js deleted file mode 100644 index 303bd11..0000000 --- a/src/controllers/servicenowController.js +++ /dev/null @@ -1,11 +0,0 @@ -const { fetchTicketsFromServiceNow, fetchRequestsFromServiceNow, fetchCommentsFromServiceNow } = require('../services/servicenowService'); - -const snServices = async () => { - //await fetchTicketsFromServiceNow(); - //await fetchRequestsFromServiceNow(); - await fetchCommentsFromServiceNow(626) -} - -module.exports = { - snServices -} \ No newline at end of file diff --git a/src/models/ticketGlpiModel.js b/src/models/ticketGlpiModel.js index 634d7a6..13f4405 100644 --- a/src/models/ticketGlpiModel.js +++ b/src/models/ticketGlpiModel.js @@ -252,6 +252,7 @@ class TicketGlpiModel { SELECT id FROM glpi_tickets WHERE name LIKE ? OR content LIKE ? LIMIT 1 + `; const [rows] = await glpiPool.execute(query, [ diff --git a/src/models/ticketSnModel.js b/src/models/ticketSnModel.js index 1a890ef..4edad63 100644 --- a/src/models/ticketSnModel.js +++ b/src/models/ticketSnModel.js @@ -24,8 +24,7 @@ class TicketSnModel { ramal = EXCLUDED.ramal, telefone = EXCLUDED.telefone, opened_at = EXCLUDED.opened_at, - tipo = EXCLUDED.tipo, - updated_at = NOW() + tipo = EXCLUDED.tipo RETURNING id, xmax = 0 as inserted`; const values = [ @@ -92,8 +91,7 @@ class TicketSnModel { ts.updated_at FROM tickets_sn ts LEFT JOIN ticket_sync tsync ON ts.id = tsync.sn_ticket_id - WHERE tsync.id IS NULL - AND ts.status NOT IN ('Encerrado', 'Encerrado - Omitido') + WHERE tsync.glpi_sync_status = 'pending_check' ORDER BY ts.opened_at `; diff --git a/src/models/ticketSyncModel.js b/src/models/ticketSyncModel.js index 6100691..fdfae70 100644 --- a/src/models/ticketSyncModel.js +++ b/src/models/ticketSyncModel.js @@ -1,9 +1,33 @@ // src/models/ticketSyncModel.js +const { query } = require('winston'); const pool = require('../data/database'); const { logError } = require('../utils/logger'); 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, + last_sn_sync, + last_glpi_sync, + created_at, + updated_at, + sn_sync_status, + glpi_sync_status + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id`; + + await pool.query(query, data); + + } catch (error) { + logError(error, '❌ Erro ao criar registro de sincronização'); + throw error; + } + } + + // Buscar tickets que foram criados com sucesso para sincronizar comentários static async getTicketsToSync() { try { @@ -51,7 +75,15 @@ class TicketSyncModel { throw error; } } + static async getByTicketNumber(ticketNumber) { + try { + const query = 'SELECT * FROM ticket_sync WHERE ticket_number = $1'; + } catch (error) { + + } + + } } diff --git a/src/services/glpiTicketService.js b/src/services/glpiTicketService.js index 1081fd3..a6e4364 100644 --- a/src/services/glpiTicketService.js +++ b/src/services/glpiTicketService.js @@ -8,7 +8,6 @@ const { logInfo, logError } = require('../utils/logger'); const syncTicketsToGlpi = async () => { try { logInfo('🔄 Iniciando sincronização para GLPI...'); - // 1. Buscar tickets pendentes const pendingTickets = await TicketSnModel.getPendingTickets(); @@ -18,7 +17,7 @@ const syncTicketsToGlpi = async () => { } logInfo(`📋 Encontrados ${pendingTickets.length} tickets para sincronizar`); - + // 2. Processar cada ticket for (const ticket of pendingTickets) { await processSingleTicket(ticket); @@ -34,21 +33,32 @@ const syncTicketsToGlpi = async () => { // Processar um ticket individual const processSingleTicket = async (ticket) => { - let syncRecord = null; try { logInfo(`🔍 Processando ticket: ${ticket.ticket_number}`); - // 1. Verificar se já existe no GLPI (evitar duplicatas) + // 1. Verificar se já existe no GLPI const existingTicket = await TicketGlpiModel.ticketExists(ticket.ticket_number); - if (existingTicket) { - logInfo(`⏭️ Ticket já existe no GLPI: ${existingTicket}`); - // Criar registro de sincronização indicando que já existe - syncRecord = await TicketSyncModel.createSyncRecord(ticket.id, existingTicket); - await TicketSyncModel.updateGlpiSyncStatus(syncRecord.id, 'duplicate', existingTicket); + const idSyncRecord = await TicketSyncModel.getByTicketNumber(ticket.ticket_number); + + + if (existingTicket && idSyncRecord) { + await TicketSyncModel.updateGlpiSyncStatus(idSyncRecord.id, 'synced', existingTicket); + await TicketSyncModel.updateSnSyncStatus(idSyncRecord.id, '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. Converter dados para formato GLPI const glpiData = { ticket_number: ticket.ticket_number, @@ -71,8 +81,7 @@ const processSingleTicket = async (ticket) => { const glpiTicketId = await TicketGlpiModel.createTicket(glpiData); // 4. Criar registro de sincronização - syncRecord = await TicketSyncModel.createSyncRecord(ticket.id, glpiTicketId); - await TicketSyncModel.updateGlpiSyncStatus(syncRecord.id, 'completed', glpiTicketId); + await TicketSyncModel.updateGlpiSyncStatus(idSyncRecord.id, 'synced', glpiTicketId); logInfo(`✅ Ticket ${ticket.ticket_number} sincronizado! GLPI ID: ${glpiTicketId}`); @@ -81,12 +90,12 @@ const processSingleTicket = async (ticket) => { // Se temos um registro de sync, atualizar o status para error if (syncRecord) { - await TicketSyncModel.updateGlpiSyncStatus(syncRecord.id, 'error'); + 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(syncRecord.id, 'error'); + await TicketSyncModel.updateGlpiSyncStatus(idSyncRecord.id, 'error'); } 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 a46a0bb..469b99b 100644 --- a/src/services/servicenowService.js +++ b/src/services/servicenowService.js @@ -14,15 +14,40 @@ const fetchTicketsFromServiceNow = async () => { 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'); + + + + //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' + } + + await TicketSyncModel.createSyncRecord(dataSync); + + + + } logSync('ServiceNow', ticketsResponse.data.result.length, 'incidentes'); @@ -40,18 +65,38 @@ const fetchRequestsFromServiceNow = async () => { 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'); + + + //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 (ticketStatus === 'Encerrado' || ticketStatus === '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'); } @@ -204,11 +249,21 @@ const syncCommentToServiceNow = async (ticketId, comment) => { // 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) => { +const fetchCommentsFromServiceNow = async () => { try { + const ticketsSyncList = await TicketSyncModel.getTicketsToSync(); + + + + + 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 []; @@ -237,6 +292,7 @@ const fetchCommentsFromServiceNow = async (ticketId) => { const comments = response?.data?.result || []; + if (!Array.isArray(comments) || comments.length === 0) { break; // não há mais comentários } @@ -264,30 +320,31 @@ const fetchCommentsFromServiceNow = async (ticketId) => { // 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" + //Trata os dados para inserir no banco intermediario a + // as 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); - + 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 ticketSyncId = await TicketSyncModel.getIdSyncBySnId(ticketId); const commentData = { ticket_sync_id: parseInt(ticketSnId), + ticket_sync_id: ticketSyncId, source_system: 'servicenow', content: comment.value, created_at: new Date(comment.sys_created_on), @@ -296,12 +353,25 @@ const fetchCommentsFromServiceNow = async (ticketId) => { source_id: comment.sys_id, 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 pending e SN status como synced - await TicketSyncModel.updateStatus(ticketGlpiId.id, 'pending', 'synced'); + // 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 }); } else { logError(`❌ Erro ao inserir comentário ID ${comment.sys_id} no banco: ${commentInserted.error}`, { step: 4 });