const TicketSnModel = require('../models/ticketSnModel'); const TicketSyncModel = require('../models/ticketSyncModel'); const SyncControlModel = require('../models/syncControlModel'); const { fetchTicketsFromServiceNow: fetchTicketsApi, fetchRequestsFromServiceNow: fetchRequestsApi } = require('../services/servicenowService'); const { logInfo, logError, logSync } = require('../utils/logger'); /** * Processa e salva um lote de tickets (incidentes ou requisições). * @param {Array} tickets - A lista de tickets da API do ServiceNow. * @param {('incidente'|'requisicao')} type - O tipo de ticket. */ const processAndSaveTickets = async (tickets, type) => { if (!tickets || tickets.length === 0) { logInfo(`Nenhum ticket do tipo '${type}' para processar.`); return ''; // Retorna string vazia se não houver tickets } logInfo(`💾 Iniciando salvamento de ${tickets.length} ${type}s no banco...`); let latestUpdateTimestamp = ''; for (const ticket of tickets) { try { // 1. Salva o ticket na tabela 'tickets_sn' const { id: idTableTicketSn, wasInserted } = await TicketSnModel.saveTicket(ticket, type); // 2. Prepara os dados para a tabela de sincronização 'ticket_sync' // Para requisições, o status está diretamente em ticket.state const ticketStatus = type === 'incidente' ? ticket.state?.display_value : ticket.state; console.log(ticketStatus) const dataSync = { sn_ticket_id: idTableTicketSn, last_sn_sync: new Date(), glpi_ticket_id: null, created_at: new Date(), updated_at: new Date() }; // 3. Define o status inicial de sincronização if (ticketStatus === 'Encerrado' || ticketStatus === 'Encerrado - Omitido') { dataSync.sn_sync_status = 'closed'; dataSync.glpi_sync_status = 'ignored'; // Ignora no GLPI se já veio fechado do SN } else { dataSync.sn_sync_status = 'collected'; // Status que indica que foi coletado do SN dataSync.glpi_sync_status = 'pending_check'; // Status que indica que precisa ser checkado no GLPI } dataSync.source_last = 'SNOW'; // A última atualização veio do ServiceNow // 4. Cria o registro de sincronização se ele não existir const exists = await TicketSyncModel.getIdSyncBySnId(idTableTicketSn); if (!exists) { await TicketSyncModel.createSyncRecord(dataSync); } else if (wasInserted === false) { // Se o ticket foi atualizado (não inserido), significa que uma mudança veio do SN. // Então, definimos o SN como a última fonte da verdade. await TicketSyncModel.updateSourceLast(idTableTicketSn, 'SNOW'); logInfo(`Ticket ${ticket.number?.value} atualizado pelo ServiceNow. 'source_last' definido como 'SNOW'.`); } else { logInfo(`Registro de sincronização para o ticket ${ticket.number?.value} já existe. Nenhuma ação necessária.`); } // Se já existe, não faz nada, pois a lógica de atualização de status será em outro processo. // Guarda o timestamp mais recente do lote para usar como a próxima marca d'água const currentUpdate = ticket.sys_updated_on?.value; if (currentUpdate && currentUpdate > latestUpdateTimestamp) { latestUpdateTimestamp = currentUpdate; } } catch (error) { logError(error, `Erro ao processar o ticket ${ticket.number?.value} do tipo ${type}.`); } } logSync('ServiceNow', tickets.length, `${type}s`); return latestUpdateTimestamp; }; /** * Controller para buscar tickets e requisições do ServiceNow e salvá-los localmente. */ const processSyncController = async () => { try { // 1. Pega a marca d'água da última execução bem-sucedida const watermark = await SyncControlModel.getWatermark('servicenow'); logInfo(`📡 Buscando tickets do ServiceNow atualizados desde: ${watermark}`); // Busca incidentes const incidents = await fetchTicketsApi(watermark); const latestIncidentUpdate = await processAndSaveTickets(incidents, 'incidente'); // Busca requisições logInfo('📡 Buscando requisições do ServiceNow...'); const requests = await fetchRequestsApi(watermark); const latestRequestUpdate = await processAndSaveTickets(requests, 'requisicao'); // 2. Determina a nova marca d'água (o timestamp mais recente entre todos os tickets processados) const newWatermark = latestIncidentUpdate > latestRequestUpdate ? latestIncidentUpdate : latestRequestUpdate; // 3. Se processamos algum ticket, atualiza a marca d'água para a próxima execução if (newWatermark) { // Adiciona 1 segundo para evitar reprocessar o último ticket em caso de timestamps iguais const nextWatermark = new Date(new Date(newWatermark).getTime() + 1000); await SyncControlModel.setWatermark('servicenow', nextWatermark); } else { logInfo("Nenhum ticket novo ou atualizado encontrado. A marca d'água não foi alterada."); } } catch (error) { logError(error, 'Erro no processo de sincronização de tickets do ServiceNow.'); } }; module.exports = { processSyncController };